O @alembic/forge é a ponte entre uma frase vaga e uma run verificável. Um front-end de 7 passos (grill → research → prototype → prd → issues → goal → review) materializa um diretório de escopo com GOAL.md, validation-contract.md e alembic.plan.ts — e o Scope Gate (loadScope) copia exatamente esse trio para DENTRO do run-dir, para que toda run carregue o próprio escopo consigo. Nesta lição: os 7 passos no código real, a materialização do run-dir e o caminho pelo qual um escopo vira run.
O pipeline canônico não é convenção de doc — é um array. DEFAULT_FORGE_STEPS (packages/forge/src/front-end/index.ts:21–29) registra os 7 passos na ordem, e runForgeFrontEnd (index.ts:42–55) os executa sequencialmente, parando no primeiro erro ("the first failure short-circuits the pipeline"). Cada passo é uma função pura sobre o contexto (ForgeStep, front-end/types.ts:53–56) que escreve seu artefato em scopeDir — o orquestrador nunca muta o contexto.
// trecho real, não editado export const DEFAULT_FORGE_STEPS: readonly ForgeStepRegistry[] = [ { name: 'grill', run: grillStep }, { name: 'research', run: researchStep }, { name: 'prototype', run: prototypeStep }, { name: 'prd', run: prdStep }, { name: 'issues', run: issuesStep }, { name: 'goal', run: goalStep }, { name: 'review', run: reviewStep }, ];
| # | Passo | Artefato | Fonte (steps.ts) |
|---|---|---|---|
| 1 | grill | SCOPE.md — escopo afiado + critérios de aceite | grillStep :29–51 |
| 2 | research | RESEARCH.md — contexto e restrições | researchStep :55–73 |
| 3 | prototype | PROTOTYPE.md — decisão consciente de prototipar (ou não) | prototypeStep :77–95 |
| 4 | prd | PRD.md — problema, solução, fases, validação | prdStep :99–121 |
| 5 | issues | issues.jsonl — uma issue por assertion do contrato | issuesStep :123–140 |
| 6 | goal | GOAL.md + validation-contract.md + alembic.plan.ts | goalStep :142–165 |
| 7 | review | REVIEW.md — checklist de prontidão + próximo comando | reviewStep :195–213 |
Pense como… um cartório de obra: ninguém levanta parede sem a pasta carimbada — projeto, memorial, ART, alvará — e cada carimbo depende do anterior. Onde quebra: no cartório os carimbos são burocracia sobre papel; aqui cada "carimbo" é um arquivo que o passo seguinte LÊ de verdade (o Scope Gate consome o trio do passo 6).
ctx.offline || !ctx.adapters (ex.: grillStep, steps.ts:33): offline usa o fallback determinístico; online chama runModelPrompt com um system prompt próprio por passo. O formato do artefato é o mesmo nos dois modos — o escopo nunca depende de rede para existir.O primeiro dos 5 gates da run é o Scope Gate: loadScope (packages/forge/src/scope.ts:37). Ele lê e valida os insumos (goal não pode ser vazio, scope.ts:44–47; contrato é parseado por Zod, scope.ts:62–67) e então materializa o layout completo do run-dir via createNewScope (scope.ts:151–269): o runId é content-addressed — runIdFor({ goal, contract, planPath }) (scope.ts:158), um run-<sha256 dos 16 primeiros hex> sobre JSON canônico (packages/swarm/src/ids.ts:19–30) — e GOAL.md, contrato e plan module são copiados para dentro do diretório.
// trecho real, não editado const planExt = extname(input.planPath); const planDestName = `alembic.plan${planExt}`; const planDest = fs.joinPath(runDir, planDestName); const planCopyResult = await copyText(input.planPath, planDest); if (!planCopyResult.ok) return err(planCopyResult.error);
Além das cópias, o gate escreve meta.json (goal + contrato + planPath + runId, scope.ts:202–215), semeia LOOP-LOG.md e review.md (scope.ts:230–244) e cria os subdiretórios council/ units/ workflows/ park/ course/ reports/ (scope.ts:246–253) — as gavetas que os gates seguintes (Council, Proof, Validator, Publish) vão preencher.
Com --resume <run-id>, loadScope desvia para loadExistingScope (scope.ts:77–148): reutiliza o run-dir, mas ANTES compara goal, planPath e contrato contra o meta.json gravado na criação. Qualquer divergência é um err imediato — "resume mismatch: GOAL.md differs from original run" (scope.ts:106–108), idem para plan (:109–111) e contrato (:112–114). Um resume não pode trocar de escopo por acidente; journals e cache existentes são preservados.
scope.ts:119–122) — se alguém apagou alembic.plan.ts de dentro da run, o gate recusa em vez de re-copiar silenciosamente. A fonte de verdade do escopo de uma run retomada é o RUN-DIR, não o diretório original.O caminho completo tem dois comandos. alembic forge "<prompt>" roda o front-end via runForgeFrontEndCli (apps/cli/src/commands.ts:2469–2533): cria scopeDir = <dataDir>/forge/<slug> (:2480), chama ANTES o planf3 (createPlan, :2487–2490 — lição 15) para gerar o plano HTML, injeta os artefatos extraídos como planArtifacts e então executa os 7 passos. Depois, alembic run --goal GOAL.md --plan alembic.plan.ts --yes entrega o trio ao Scope Gate e a run nasce.
O forge também abriga lifecycle.ts — a máquina Stage/Gate do founder-core reescrita como primitivo PURO: estágios ordenados, cada um guardado por checks numéricos (gte|lte|eq|gt|lt), e checkLifecycleGate devolvendo Result onde a fonte original lançava exceção (packages/forge/src/lifecycle.ts:5–24). Sem relógio, sem aleatório — plan-VM-safe por construção, o mesmo contrato de determinismo da lição 14.
--resume <run-id> depois de editar o GOAL.md original. O que acontece?scope.ts:106–108 compara o goal lido com o meta.json da run e recusa a divergência — um resume nunca troca de escopo por acidente. O mesmo cheque cobre planPath (:109–111) e contrato (:112–114). Quer o goal novo? É outra run (e, como o id é content-addressed, será OUTRO run-dir).goalStep (steps.ts:142–165) escreve os três de uma vez — o contrato como { version: '1', assertions } e o plan module lido do planf3 ou o starter determinístico com fases scope/build/proof. O grill só afia o escopo (SCOPE.md); o review só faz o checklist e imprime o próximo comando.Do prompt cru à run, com inspeção de cada artefato no meio:
# 1) forjar o escopo (7 passos; offline usa fallback determinístico) alembic forge "adicionar exportação CSV ao cockpit" # 2) inspecionar o que cada passo escreveu ls ~/.alembic/forge/<slug>/ cat ~/.alembic/forge/<slug>/GOAL.md cat ~/.alembic/forge/<slug>/validation-contract.md # 3) virar run: o Scope Gate copia o trio para o run-dir alembic run --goal <scope>/GOAL.md --plan <scope>/alembic.plan.ts --yes # 4) conferir a materialização (meta.json + 6 subdirs) ls <dataDir>/runs/run-<hash>/ cat <dataDir>/runs/run-<hash>/meta.json # 5) retomar SEM trocar de escopo (mismatch ⇒ err) alembic run --goal <scope>/GOAL.md --plan <scope>/alembic.plan.ts --resume run-<hash> --yes # fontes desta lição sed -n '21,29p' packages/forge/src/front-end/index.ts sed -n '151,269p' packages/forge/src/scope.ts
Regra de leitura: o diretório de escopo é rascunho; o run-dir é o contrato. Depois do Scope Gate, é o RUN-DIR que responde "o que esta run prometeu?".
front-end/index.ts:21–29, 47–53).steps.ts:33).scope.ts:186–200).runId = runIdFor({goal, contract, planPath}) — identidade por conteúdo (scope.ts:158, ids.ts:30).meta.json e falha fechado no mismatch (scope.ts:106–114).factoryPhaseSchema.