# WP1: packages/forge — Forge Pipeline Package ## Context Port the Forge progressive refinement pipeline from Python (~/src/mosaic-stack/forge/) to TypeScript as `packages/forge` in this monorepo. The pipeline markdown assets (stages, agents, personas, rails, gates, templates) are already copied to `packages/forge/pipeline/`. This task is the TypeScript implementation layer. **Board decisions that constrain this work:** - Abstract TaskExecutor interface — packages/forge must NOT hard-import packages/coord. Define an abstract interface; coord satisfies it. - Clean index.ts exports, no internal path leakage, no hardcoded paths - 85% test coverage on TS implementation files (markdown assets excluded) - Test strategy for non-deterministic AI orchestration: fixture-based integration tests - OpenBrain is OUT OF SCOPE - ESM only, zero Python **Dependencies available:** - `@mosaicstack/macp` (packages/macp) is built and provides: GateEntry, GateResult, Task types, credential resolution, gate running, event emission ## Source Files (Python → TypeScript) ### 1. types.ts Define all Forge-specific types: ```typescript // Stage specification interface StageSpec { number: string; title: string; dispatch: 'exec' | 'yolo' | 'pi'; type: 'research' | 'review' | 'coding' | 'deploy'; gate: string; promptFile: string; qualityGates: (string | GateEntry)[]; } // Brief classification type BriefClass = 'strategic' | 'technical' | 'hotfix'; type ClassSource = 'cli' | 'frontmatter' | 'auto'; // Run manifest (persisted to disk) interface RunManifest { runId: string; brief: string; codebase: string; briefClass: BriefClass; classSource: ClassSource; forceBoard: boolean; createdAt: string; updatedAt: string; currentStage: string; status: 'in_progress' | 'completed' | 'failed' | 'interrupted' | 'rejected'; stages: Record; } // Abstract task executor (decouples from packages/coord) interface TaskExecutor { submitTask(task: ForgeTask): Promise; waitForCompletion(taskId: string, timeoutMs: number): Promise; } // Persona override config interface ForgeConfig { board?: { additionalMembers?: string[]; skipMembers?: string[]; }; specialists?: { alwaysInclude?: string[]; }; } ``` ### 2. constants.ts **Source:** Top of `~/src/mosaic-stack/forge/lib` (ALL_STAGES, LABELS, STAGE_SPECS equivalent) + `~/src/mosaic-stack/forge/pipeline/orchestrator/stage_adapter.py` (STAGE_TIMEOUTS) ```typescript export const STAGE_SEQUENCE = [ '00-intake', '00b-discovery', '01-board', '01b-brief-analyzer', '02-planning-1', '03-planning-2', '04-planning-3', '05-coding', '06-review', '07-remediate', '08-test', '09-deploy', ]; export const STAGE_TIMEOUTS: Record = { '00-intake': 120, '00b-discovery': 300, '01-board': 120, '02-planning-1': 600, // ... etc }; export const STAGE_LABELS: Record = { '00-intake': 'INTAKE', // ... etc }; ``` Also: STRATEGIC_KEYWORDS, TECHNICAL_KEYWORDS for brief classification. ### 3. brief-classifier.ts **Source:** `classify_brief()`, `parse_brief_frontmatter()`, `stages_for_class()` from `~/src/mosaic-stack/forge/lib` - Auto-classify brief by keyword analysis (strategic vs technical) - Parse YAML frontmatter for explicit `class:` field - CLI flag override - Return stage list based on classification (strategic = full pipeline, technical = skip board, hotfix = skip board + brief analyzer) ### 4. stage-adapter.ts **Source:** `~/src/mosaic-stack/forge/pipeline/orchestrator/stage_adapter.py` - `mapStageToTask()`: Convert a Forge stage into a task compatible with TaskExecutor - Stage briefs written to `{runDir}/{stageName}/brief.md` - Result paths at `{runDir}/{stageName}/result.json` - Previous results read from disk at runtime (not baked into brief) - Per-stage timeouts from STAGE_TIMEOUTS - depends_on chain built from stage sequence ### 5. board-tasks.ts **Source:** `~/src/mosaic-stack/forge/pipeline/orchestrator/board_tasks.py` - `loadBoardPersonas()`: Read all .md files from `pipeline/agents/board/` - `generateBoardTasks()`: One task per persona + synthesis task - Synthesis depends on all persona tasks with `depends_on_policy: 'all_terminal'` - Persona briefs include role description + brief under review - Synthesis script merges independent reviews into board memo ### 6. pipeline-runner.ts **Source:** `~/src/mosaic-stack/forge/pipeline/orchestrator/pipeline_runner.py` + `~/src/mosaic-stack/forge/lib` (cmd_run, cmd_resume, cmd_status) - `runPipeline(briefPath, projectRoot, options)`: Full pipeline execution - Creates run directory at `{projectRoot}/.forge/runs/{runId}/` - Generates tasks for all stages, submits to TaskExecutor - Tracks manifest.json with stage statuses - `resumePipeline(runDir)`: Pick up from last incomplete stage - `getPipelineStatus(runDir)`: Read manifest and report **Key difference from Python:** Run output goes to PROJECT-scoped `.forge/runs/`, not inside the Forge package. ### 7. Persona Override System (NEW — not in Python) - Base personas read from `packages/forge/pipeline/agents/` - Project overrides read from `{projectRoot}/.forge/personas/{role}.md` - Merge strategy: project persona content APPENDED to base persona (not replaced) - Board composition configurable via `{projectRoot}/.forge/config.yaml` - If no project config exists, use defaults (all base personas, no overrides) ## Package Structure ``` packages/forge/ ├── src/ │ ├── index.ts │ ├── types.ts │ ├── constants.ts │ ├── brief-classifier.ts │ ├── stage-adapter.ts │ ├── board-tasks.ts │ ├── pipeline-runner.ts │ └── persona-loader.ts ├── pipeline/ # Already copied (WP4) — markdown assets │ ├── stages/ │ ├── agents/ │ ├── rails/ │ ├── gates/ │ └── templates/ ├── __tests__/ │ ├── brief-classifier.test.ts │ ├── stage-adapter.test.ts │ ├── board-tasks.test.ts │ ├── pipeline-runner.test.ts │ └── persona-loader.test.ts ├── package.json ├── tsconfig.json └── vitest.config.ts ``` ## Package.json ```json { "name": "@mosaicstack/forge", "version": "0.0.1", "type": "module", "exports": { ".": "./src/index.ts" }, "dependencies": { "@mosaicstack/macp": "workspace:*" }, "devDependencies": { "vitest": "workspace:*", "typescript": "workspace:*" } } ``` Only dependency: @mosaicstack/macp (for gate types, event emission). ## Test Strategy (Board requirement) **Deterministic code (brief-classifier, stage-adapter, board-tasks, persona-loader, constants):** - Standard unit tests with known inputs/outputs - 100% of classification logic, stage mapping, persona loading covered **Non-deterministic code (pipeline-runner):** - Fixture-based integration tests using a mock TaskExecutor - Mock executor returns pre-recorded results for each stage - Tests verify: manifest progression, stage ordering, dependency enforcement, resume behavior, error handling - NO real AI calls in tests **Markdown assets:** Excluded from coverage measurement (configure vitest to exclude `pipeline/` directory). ## ESM Requirements - `"type": "module"` in package.json - NodeNext module resolution in tsconfig - `.js` extensions in all imports - No CommonJS ## Key Design: Abstract TaskExecutor ```typescript // In packages/forge/src/types.ts export interface TaskExecutor { submitTask(task: ForgeTask): Promise; waitForCompletion(taskId: string, timeoutMs: number): Promise; getTaskStatus(taskId: string): Promise; } // In packages/coord (or wherever the concrete impl lives) export class CoordTaskExecutor implements TaskExecutor { // ... uses packages/coord runner } ``` This means packages/forge can be tested with a mock executor and deployed with any backend. ## Asset Resolution Pipeline markdown assets (stages, personas, rails) must be resolved relative to the package installation, NOT hardcoded paths: ```typescript // Use import.meta.url to find package root const PACKAGE_ROOT = new URL('..', import.meta.url).pathname; const PIPELINE_DIR = path.join(PACKAGE_ROOT, 'pipeline'); ``` Project-level overrides resolved relative to projectRoot parameter.