@polymorphism-tech/morph-spec 8.37.0 → 8.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +19 -6
- package/README.md +2 -2
- package/bin/morph-spec.js +16 -7
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +3 -3
- package/framework/MORPH.md +25 -22
- package/framework/agents.json +76 -27
- package/framework/docs/CLI.md +55 -18
- package/framework/evals/rubrics/code-quality.md +5 -0
- package/framework/evals/rubrics/condensed.md +6 -2
- package/framework/evals/rubrics/contracts.md +20 -0
- package/framework/evals/rubrics/test-coverage.md +17 -6
- package/framework/hooks/README.md +9 -3
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +52 -0
- package/framework/hooks/claude-code/core/telemetry-log.js +172 -87
- package/framework/hooks/claude-code/core/validate-completion.js +16 -90
- package/framework/hooks/claude-code/core/worktree-context.js +10 -0
- package/framework/hooks/shared/command-tokens.js +96 -0
- package/framework/hooks/shared/git-exclude.js +44 -0
- package/framework/hooks/shared/git-paths.js +52 -0
- package/framework/hooks/shared/graph-freshness.js +380 -0
- package/framework/hooks/shared/graph-refresh.js +416 -0
- package/framework/hooks/shared/morph-cli.js +17 -5
- package/framework/hooks/shared/redact-secrets.js +55 -0
- package/framework/hooks/shared/telemetry-logger.js +25 -2
- package/framework/hooks/shared/transcript-usage.js +80 -0
- package/framework/rubrics/backend.json +2 -2
- package/framework/rubrics/default.json +2 -2
- package/framework/rubrics/frontend.json +2 -2
- package/framework/rules/csharp-standards.md +1 -0
- package/framework/rules/morph-workflow.md +1 -0
- package/framework/rules/nextjs-standards.md +1 -0
- package/framework/schemas/config.schema.json +41 -1
- package/framework/schemas/feature.schema.json +5 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +36 -11
- package/framework/skills/core/morph-brainstorming/SKILL.md +16 -7
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +39 -10
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +72 -15
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +51 -13
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +34 -9
- package/framework/standards/STANDARDS.json +187 -12
- package/framework/standards/ai-agents/rag-custom-pgvector.md +7 -3
- package/framework/standards/ai-agents/testing-ai.md +2 -1
- package/framework/standards/architecture/code-comments.md +64 -0
- package/framework/standards/backend/dotnet/testing.md +22 -0
- package/framework/standards/frontend/admin-app/data-table.md +562 -0
- package/framework/standards/frontend/admin-app/feedback.md +236 -0
- package/framework/standards/frontend/admin-app/forms.md +232 -0
- package/framework/standards/frontend/admin-app/page-structure.md +203 -0
- package/framework/standards/frontend/nextjs/components.md +37 -58
- package/framework/standards/frontend/nextjs/data-fetching.md +1 -0
- package/framework/standards/frontend/nextjs/forms.md +31 -30
- package/framework/standards/frontend/nextjs/state-management.md +2 -1
- package/framework/standards/frontend/nextjs/testing.md +18 -0
- package/framework/templates/context/design-system.md +3 -3
- package/framework/templates/docs/proposal.md +11 -0
- package/framework/templates/docs/spec.md +15 -0
- package/framework/templates/docs/ui-mockups.md +115 -98
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +12 -6
- package/framework/templates/feature/decisions.md +124 -122
- package/package.json +1 -1
- package/src/commands/graph/affected.js +4 -3
- package/src/commands/graph/build.js +109 -18
- package/src/commands/graph/explain.js +8 -5
- package/src/commands/graph/status.js +14 -8
- package/src/commands/observability/cost.js +148 -18
- package/src/commands/project/doctor.js +82 -10
- package/src/commands/project/update.js +25 -0
- package/src/commands/project/worktree.js +73 -90
- package/src/commands/state/advance.js +18 -0
- package/src/commands/state/approve.js +15 -0
- package/src/commands/state/archive.js +62 -9
- package/src/commands/state/finish.js +62 -14
- package/src/commands/state/gate-check.js +8 -1
- package/src/commands/state/gate-decision.js +8 -4
- package/src/commands/state/report-check.js +90 -9
- package/src/commands/state/usecase.js +66 -0
- package/src/commands/tasks/dag.js +17 -2
- package/src/commands/validation/validate-feature.js +6 -1
- package/src/commands/validation/verify.js +20 -1
- package/src/dashboard/scanner/agents-scanner.js +5 -1
- package/src/dashboard/scanner/hooks-scanner.js +8 -1
- package/src/dashboard/scanner/standards-scanner.js +2 -1
- package/src/lib/ai/comment-stripper.js +101 -4
- package/src/lib/evaluator-report.js +25 -0
- package/src/lib/generators/handoff-generator.js +4 -18
- package/src/lib/git/default-branch.js +52 -0
- package/src/lib/git/tree-fingerprint.js +58 -0
- package/src/lib/git/tree-state.js +167 -0
- package/src/lib/graph/god-nodes.js +12 -3
- package/src/lib/graph/graph-health.js +48 -2
- package/src/lib/graph/graph-loader.js +99 -25
- package/src/lib/graph/graphifyignore.js +145 -28
- package/src/lib/graph/sanitize.js +117 -11
- package/src/lib/risk-detector.js +90 -0
- package/src/lib/specs/acervo-format.js +70 -0
- package/src/lib/specs/acervo-lifecycle.js +264 -0
- package/src/lib/specs/adr-index.js +169 -0
- package/src/lib/specs/revocations.js +156 -0
- package/src/lib/specs/screen-checklist.js +357 -0
- package/src/lib/specs/usecase-check.js +20 -16
- package/src/lib/specs/usecase-promoter.js +103 -38
- package/src/lib/specs/usecase-registry.js +7 -7
- package/src/lib/tasks/tasks-json-parser.js +2 -3
- package/src/lib/telemetry/metrics.js +386 -0
- package/src/lib/telemetry/telemetry-reader.js +161 -51
- package/src/lib/telemetry/verify-event.js +79 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +172 -0
- package/src/lib/validators/code-budget/comment-scanner.js +75 -0
- package/src/lib/validators/code-budget/index.js +9 -0
- package/src/lib/validators/reuse/reuse-validator.js +4 -2
- package/src/lib/validators/reuse/symbol-scanner.js +27 -22
- package/src/lib/validators/validation-runner.js +21 -5
- package/src/lib/validators/verify-runner.js +11 -0
- package/src/lib/worktree/finish-backup.js +739 -0
- package/src/lib/worktree/ignored-triage.js +15 -8
- package/src/lib/worktree/worktree-audit.js +5 -0
- package/src/scripts/setup-infra.js +29 -18
- package/src/utils/agents-installer.js +281 -12
- package/src/utils/hooks-installer.js +75 -15
package/CLAUDE.md
CHANGED
|
@@ -49,7 +49,7 @@ No build step — pure ESM JavaScript, runs directly via Node.js 18+.
|
|
|
49
49
|
- **`framework/`** — Metadata installed into target projects (READ-ONLY at runtime):
|
|
50
50
|
- `MORPH.md` — NLH charter (the single source of behavioral truth)
|
|
51
51
|
- `CLAUDE.md` — Runtime instructions injected into target's `.claude/CLAUDE.md`
|
|
52
|
-
- `agents.json` (v5.
|
|
52
|
+
- `agents.json` (v5.3.0) — 8 agents: 7 domain personas + 1 stage persona (`scout`, read-only), no tiers; `maxTurns` per persona (ADR-004)
|
|
53
53
|
- `phases.json` — 5 phases (proposal, uiux, plan, implement, review)
|
|
54
54
|
- `standards/STANDARDS.json` — index of available standards
|
|
55
55
|
- `hooks/claude-code/core/` — 7 fail-open guardrail hooks
|
|
@@ -106,11 +106,15 @@ When modifying framework components, verify the full scope of related files.
|
|
|
106
106
|
|
|
107
107
|
| When you modify... | Also verify... |
|
|
108
108
|
|----------------------------------|-------------------------------------------------------------------------------------------|
|
|
109
|
-
| `framework/agents.json` | agents-installer.js (test/utils/agents-installer.test.js), doctor.js (REQUIRED_AGENTS)
|
|
109
|
+
| `framework/agents.json` | agents-installer.js (test/utils/agents-installer.test.js), doctor.js (REQUIRED_AGENTS). **`maxTurns` (ADR-004):** UMA régua de validade, `maxTurnsError` em `agents-installer.js`, com QUATRO leitores — `installAgents` (recusa antes de escrever), `checkAgentsDrift` (`invalid`), `validateAgentsJson` do `doctor` (error) e o Check 6 do `validate-framework.js` (`test/framework/validate-framework-agents.test.js`); uma segunda noção de "valor válido" faz o mesmo arquivo passar num e ser recusado no outro. Sem o campo, o frontmatter sai byte a byte igual ao de antes (canário COMPAT). O corpo gerado anuncia o teto ao sub-agent, e a cláusula 5 dos templates de dispatch depende disso. O drift lê o registry MCP do PACOTE (`mcpRegistryDir`): a cópia instalada não tem `mcp/`, e ler dela dava toda persona com `mcp` como stale numa instalação limpa. O marcador `PARCIAL` (1ª linha) que a cláusula 5 e o corpo da persona mandam gravar é lido por `report-check.js` (`PARTIAL_MARKER` → `report-incomplete`, ação `resume`): mudar a grafia num lado só devolve o parcial lido como `ok`. **`surfaces` (ADR-005):** mesma régua única em quatro leitores (`surfacesError`), e `personaStandardIds` é a leitura única de "todo standard da persona" para o Check 1, o drift e os scanners do dashboard (`agents-scanner.js`, `standards-scanner.js`) — iterar só `standards` deixa id de superfície sem validar e a aresta do grafo sumida. Sem o campo, o `.md` sai byte a byte o de antes: `test/fixtures/agents/ui-designer-5.3.0.{json,md}` é a persona do 5.3.0 e o render do installer de main, comparados com `strictEqual` — nunca regere a fixture para fazer o teste passar. A chave da superfície é a que o checklist de tela do `morph-uiux` escreve em `Superfície` e que o `morph-plan` §5a usa para somar a lista em `tasks[].standards` |
|
|
110
110
|
| `framework/standards/**` | STANDARDS.json entry exists (o **digest** e o que `standards --find` devolve — corrigir a prosa e esquecer o digest e o caso mais comum), agents reference standard, .md file exists. **Bloco de codigo `csharp` num standard e afirmacao verificavel:** extraia com `node scripts/extract-doc-csharp.mjs <std.md> "## Secao" out.cs` e compile numa sonda com o pin + `TreatWarningsAsErrors` (sem ele `MEAI001` vira aviso e o build fica verde sobre codigo que o consumidor nao compila). Medido: um exemplo carimbado sem compilar tinha 3 erros independentes |
|
|
111
|
+
| `isImplementer` (Check 6 de `scripts/validate-framework.js`) | toda persona com Write/Edit no escopo efetivo de tools tem de alcançar, pelos `standards`, as âncoras `comment-policy` e `test-budget` do STANDARDS.json (`IMPLEMENTER_ANCHORS`). Renomear a chave da âncora no registry derruba o `validate`, e persona implementadora nova sem os dois standards também. Installer e `doctor` não cobram isso de propósito: um `agents.json` instalado antigo continua valendo |
|
|
111
112
|
| `scripts/generate-standards-registry.js` | `framework/standards/STANDARDS.json` (REGENERE, nunca patch textual — tres features escrevem no mesmo arquivo), `src/lib/standards/verified-against.js` (`parseVerifiedAgainst`/`deriveProvedBy`: `verifiedAgainst` e `provedBy` sao DERIVADOS a cada run e NAO entram em `PRESERVE_FIELDS`), `test/framework/standards-registry.test.js`. **Renomear um standard e uma PARADA, nao um exit 0**: path sumido + path novo faz o gerador recusar nomeando os dois conjuntos e prescrevendo a edicao do campo `path` da entrada antiga — ele nao adivinha o par. `MORPH_STANDARDS_BASE` e o unico seam de teste do script e nada em producao o define |
|
|
112
|
-
| `framework/hooks/claude-code/**` | hooks-installer.js MORPH_HOOKS array, hook follows fail-open pattern
|
|
113
|
-
| `framework/
|
|
113
|
+
| `framework/hooks/claude-code/**` | hooks-installer.js MORPH_HOOKS array, hook follows fail-open pattern. O `timeout` de cada entrada é em SEGUNDOS, a unidade que o Claude Code lê — em milissegundos ele passa do default de 600 e prende a sessão por horas (canário em `test/hooks/hooks-installer.test.js`); binding `async: true` não tem timeout aplicado |
|
|
114
|
+
| `framework/hooks/shared/telemetry-logger.js` (esquema v2 do `events.jsonl`) | os leitores do MESMO evento: `telemetry-reader.js` (`ingestedBytes`), `src/lib/telemetry/metrics.js` (`cost --metrics`), `verify-event.js`, `report-check.js` e os hooks que gravam. `isFailedToolCall` é a régua única de "a chamada falhou: não leu nem mudou nada" — um segundo teste de `PostToolUseFailure` volta a contar os bytes de um Read que falhou e a edição que não entrou. Evento v1 (sem `schemaVersion`) nunca vira zero: cai em "sem agente", `unknown-*` ou "sem duração". O esquema mora em `framework/docs/CLI.md` (`cost --metrics`); campo novo aqui entra lá. Os bindings que só registram (`PostToolUseFailure`, `PostToolBatch`, `SubagentStop`) são `async: true` em `hooks-installer.js`; hook que decide nunca é async (canário em `test/hooks/hooks-installer.test.js`) |
|
|
115
|
+
| `framework/hooks/shared/command-tokens.js` (`producerOf`, `exitCodeMasked`) | `telemetry-log.js` (grava `exitMasked`), `metrics.js` (o rerun agrupa pelo comando produtor e manda o exit mascarado para `unknown-*`) e os guards de Bash que leem `tokenize`. A tabela de `test/hooks/command-tokens.test.js` é o contrato: cada forma que a cláusula 3 do dispatch manda evitar (`\| tail`, `; echo`, `\|\| true`, `&` de fundo) tem linha lá, ao lado de uma parecida que não mascara (`cd src; dotnet test`, `> log 2>&1`) |
|
|
116
|
+
| `framework/hooks/shared/redact-secrets.js` | `telemetry-logger.js` (redige os campos de texto livre, `FREE_TEXT_FIELDS`) e a lista de formas redigidas em `framework/docs/CLI.md`. Regra nova entra em `test/hooks/redact-secrets.test.js` com um caso parecido que NÃO pode ser redigido (`docker run -u 1000:1000`, "Bearer authentication", `git push origin HEAD:refs/heads/main`): regra frouxa apaga o comando que a métrica de rerun lê |
|
|
117
|
+
| `framework/skills/core/**` | MORPH.md skills table (canário `test/framework/morph-skills-table.test.js`: toda skill `morph-*` do disco na tabela do §7, e nenhuma fantasma), skills-installer.js, SKILL.md frontmatter |
|
|
114
118
|
| `framework/phases.json` | output-schema.js (phase mappings), test/framework/phases.test.js |
|
|
115
119
|
| `framework/templates/**` | REGISTRY.json (template registered), valid Handlebars helpers |
|
|
116
120
|
| `framework/templates/REGISTRY.json` (o campo `kind`) | os QUATRO checks de `scripts/validate-framework.js` que leem `kind`/`path`: **4** (resolve arquivo × diretório), **9** (shape exigido por kind), **10** (só lê `path` que é ARQUIVO — `readFileSync` num diretório derruba o validador inteiro com EISDIR, não vira erro de validação) e **12** (cobertura por prefixo; projeto registrado sem arquivo rastreado é erro). `test/framework/validate-framework-registry.test.js` cobra os quatro |
|
|
@@ -119,23 +123,32 @@ When modifying framework components, verify the full scope of related files.
|
|
|
119
123
|
| `.github/workflows/*.yml` (o job `ai-kit`) | os DOIS workflows carregam o MESMO job — `ci.yml` (toda PR) e `release.yml`, onde `publish` declara `needs: [test, ai-kit]` e continua **sem matrix** (matrix no job que publica roda `npm publish` 1x por OS). Tres detalhes sao load-bearing: `working-directory: framework/templates/dotnet/ai-kit` (o `dotnet test` resolve o `global.json` pelo CWD, nunca pelo `.sln` do argumento — da raiz ele FALHA, enquanto o `dotnet build` funciona, que e o que torna a armadilha facil), `global-json-file` em vez de `dotnet-version` (a banda do SDK vem do pin, nunca digitada no YAML) e o passo `generate-ai-pin-props.js --check` antes do restore (sem ele o job compila props dessincronizadas do pin e reporta verde). `test/framework/ai-kit-ci-packaging.test.js` cobra a forma do job E mede o tarball |
|
|
120
124
|
| `src/lib/standards/verified-against.js` | os leitores do MESMO fato "quao velho e este standard": `scripts/check-freshness.js` (hard fail por `Last-verified`), `test/framework/standard-header.test.js` (campos obrigatorios no header) e `framework/ai-pin.json` (a data de referencia). Uma segunda copia do regex e como o contador de `// VERIFY:` ficou inerte: `/^\s*\/\/\s*VERIFY:/` exigia inicio de linha e dois-pontos e enxergava 0 de 10 marcadores reais. **`deriveProvedBy` mora aqui pela mesma razao**, e o quarto leitor e `scripts/generate-standards-registry.js`, que DERIVA `verifiedAgainst`/`provedBy` a cada run — **TRES** tipografias da clausula sao reais no acervo e a regua e a SOMA de paths (27 em 16 entradas), nunca a contagem de entradas: a crase abre DEPOIS de "provado por", ANTES dela, e a clausula tambem aparece em negrito no MEIO da frase (`**provado por**`). Ignorar a segunda perdia 4 das 16 provas; ignorar a terceira perdia 2 paths de `llm-runtime-defaults` **sem mover contador nenhum** — a entrada seguia entre as 16 e o exit era 0. Por isso `test/framework/standards-registry.test.js` pina os paths ESCRITOS A MAO, e nunca chama `deriveProvedBy` para gerar a expectativa do acervo real |
|
|
121
125
|
| `src/core/paths/output-schema.js`| Run `npm run generate` to update phase-utils.js |
|
|
126
|
+
| `src/lib/specs/acervo-format.js` (o formato do acervo) | as formas que TODO leitor e escritor do acervo compartilham — id de UC, nome canônico `UC-{nn}-{slug}.md` (vem de `OUTPUT_SCHEMA.usecases.filenamePattern`), `superseded` e o corte de linha de tabela GFM — com seis leitores: `usecase-registry.js`, `usecase-promoter.js`, `usecase-check.js`, `acervo-lifecycle.js`, `revocations.js` e `tasks-json-parser.js`. Uma segunda cópia é como dois leitores começam a discordar do mesmo arquivo: um UC que um enxerga e o outro não. `supersessionProblems` (`acervo-lifecycle.js`) é a regra ÚNICA do frontmatter de revogação: o escritor do `finish` só produz o que ela aceita, e `dag`/`usecase check` a rodam enquanto o `spec.md` ainda pode ser corrigido (ele congela no Gate 2). O índice de ADRs (`adr-index.js`) é um arquivo por ADR, como o acervo, para que features paralelas não se toquem no merge. O exemplo da seção de revogação em `framework/templates/docs/spec.md` e nas skills é lido pelo código em `test/framework/acervo-lifecycle-docs.test.js` |
|
|
122
127
|
| `framework/MORPH.md` | All core skills (cross-references), CLAUDE.md (slash command list), e o **teto de 38 400 chars** (`test/framework/resident-size.test.js`). MORPH.md vive colado no teto: linha nova ali só entra liberando pelo menos o mesmo número de caracteres **na mesma seção**, com o antes/depois medido — `MORPH_MD_LIMIT` não sobe |
|
|
123
128
|
| a divisão loop × suíte (`verify {f} {task}` recorta sozinho; `--filter` força nós, `--full` desliga) | os QUATRO textos que a declaram e não podem discordar: `framework/docs/CLI.md` (seção "Suítes de teste" — a tabela canônica, a forma de `project.tests[]` e a subseção *O recorte por task*, com os limites), `morph-implement` §9, `morph-apply` §3a (o `verify` do loop e os `outputs` gravados ANTES dele — o recorte os lê) e `morph-review` (o Gate 3 roda **sem** filtro e `verify {feature}` nunca recorta). Uma superfície que ensine filtrar no Gate 3 devolve o F30 pela porta da frente; canário: `test/framework/verify-stack-docs-prose.test.js` |
|
|
124
129
|
| `src/lib/worktree/ignored-triage.js` | `worktree.js` (`classifyIgnoredLeftovers`), `test/lib/worktree/ignored-triage.test.js`, e os canários de `test/commands/finish.test.js` que pinam "o exemplar único nunca morre" — mudar um bucket muda o que o `finish` apaga |
|
|
130
|
+
| `src/lib/worktree/finish-backup.js` (backups do finish e a retenção) | os QUATRO pontos que escrevem ou leem `.git/morph-spec/finish-backup/`: `finish.js` (o shed da raiz e a poda do fim, `enforceBackupRetention`), `worktree.js` (o backup do desmonte e a poda do fim do `worktree remove`), `worktree-audit.js` (`auditFinishBackups` → `doctor --worktrees`) e `doctor.js` (a linha `.git/morph-spec` do padrão, nunca `✗`, e o `--prune`, a ÚNICA poda que inclui legado). `resolveBackupRetention` é a autoridade do `finishBackup.retention` de `framework/schemas/config.schema.json`: política que não se lê PULA a poda, nunca cai no padrão, porque a operação apaga. Backup sem manifesto é legado, e a poda automática nunca o apaga nem o conta. Um finish grava as duas pastas com o MESMO carimbo, e o carimbo da execução vai em `protectStamps`. Só `task/` é namespace. Apagar é PROVE-or-DECLINE, como a triagem de `ignored-triage.js` (que decide o que entra no backup) |
|
|
125
131
|
| `src/lib/git/pr-index.js` | os QUATRO leitores do mesmo fato "esta branch tem PR": `fleet-scanner.js` (coluna PR), `branch-classifier.js` (o que NÃO apagar), `worktree-audit.js` (o que NÃO sugerir remover) e `pending-prs.js` (a síntese "aguardando merge"). `null` é UNKNOWN, nunca "sem PR" |
|
|
126
132
|
| `src/lib/git/branch-classifier.js` | `src/commands/project/branch.js`, `worktree-audit.js` (`unintegratedWork` reusa `classifyBranch` — o `doctor` e o `branch prune` não podem discordar sobre a mesma branch), `test/commands/branch-prune.test.js`, e `lifecycle-audit.js` (`abandoned-scaffold` conta commit próprio pelo mesmo `resolveBaseRef` + `commitsAhead`) |
|
|
127
133
|
| `src/lib/git/pending-prs.js` | os TRÊS consumidores da mesma síntese "aguardando merge": `list.js` (linha sintetizada), `fleet-scanner.js` (entrada `pr-pending`) e `doctor.js` (`auditPullRequests`). Uma feature visível numa superfície e invisível na outra é o bug que este módulo existe para impedir |
|
|
128
134
|
| `src/lib/git/gh.js` | `pr-index.js`, `pr-files.js` e `finish.js` — as DUAS formas de invocar o `gh` viraram uma; uma segunda cópia de `resolveGh` volta a tornar o caminho de sucesso do índice intestável |
|
|
135
|
+
| `src/lib/git/tree-fingerprint.js` (a impressão da árvore) | `verify.js` (grava `fingerprint` no evento `Verify`; `findRepeatedVerify`, de `verify-event.js`, compara com o último verify de feature COMPLETO — rodada com `persisted: false` nunca é a prova que uma completa substitui) e `metrics.js` (`verify.unchangedRepeats`, pela mesma regra). `.morph/` fica fora, exceto `config/config.json`: o próprio verify escreve feature.json e logs a cada rodada, e contá-los faria duas rodadas sobre o mesmo código parecerem diferentes. `repeatOf` é informação e nenhum gate o lê: ligá-lo ao Gate 3 é aceitar a prova de uma rodada anterior no lugar da atual |
|
|
136
|
+
| `src/lib/git/tree-state.js` (o bloco `tree` do `report-check`) | `report-check.js` (toda saída, inclusive o batch `report-check <f> T1 T2`, e a foto `--before-eval` em `4-review/tree-before-eval.json`) e as skills que leem os campos: `morph-implement`/`morph-apply` (pós-dispatch e retomada do PARCIAL) e `morph-review` (higiene contra `tree.sinceBeforeEval`). Campo renomeado aqui sem as skills é instrução que lê `undefined`: `test/framework/report-check-wiring.test.js` confere que todo `tree.*` citado existe na saída real. A foto compara CONTEÚDO (blob por arquivo), não o `git status`: a mutação que o avaliador commitou também aparece. Fora de git o bloco é `null`, nunca árvore vazia. `MUTATION_MARKER` tem a mesma grafia que as skills mandam gravar |
|
|
129
137
|
| `src/lib/worktree/feature-truth.js` | The four leaders reading the same "which tree owns this feature" predicate: `fleet-scanner.js`, `list.js`, `handoff.js`, and the mutator guard (`approve.js`/`advance.js`/`score.js`/`gate-decision.js`/`gate-check.js`) |
|
|
130
138
|
| `src/lib/tasks/test-runner.js` | `verify-runner.js` (o nó `tests` repassa o `--timeout` e lê `timedOut`), `src/commands/project/worktree.js` (as constantes `WORKTREES_DIR`/`LEGACY_WORKTREES_DIR` que a descoberta ignora — duplicar as strings aqui é como o bug volta), `test/lib/test-runner.test.js` (o canário do ignore de worktrees e o do timeout escalado). Um worktree é o checkout de OUTRA feature: varrê-lo dobra o relógio e reporta sobre código fora da árvore avaliada. `stripAnsi` roda antes de TODO extrator de contagem (ponto único em `parseTestCount`) e das strings de `isZeroTestRun` — uma cor SGR no meio do sumário derrotava os regexes ancorados (#110). O extrator `dotnet` lê os DOIS sumários (#115): a linha VSTest e o bloco MTP (cabeçalho `Test run summary:`/`Resumo da execução de teste:` na coluna 0, `total:` indentado), em pt-BR e en-US — UM bloco por invocação de `dotnet test` (uma solução de dois projetos dá um bloco agregado, medido), então somar blocos é somar links `&&`; um terceiro locale é `null`. `mtpSummaryTotals` (um total por bloco) é o que `judgeNarrowedRun` lê para achar, numa cadeia MTP recortada, o link que rodou 0 (exit 8 ignorado) escondido pela soma. `ZERO_RUN_SIGNATURES` mistura o medido e o documentado — medidos (capturas em `test/fixtures/test-output/`): o exit 8 do MTP e a string pt-BR do VSTest; só documentados, sem captura: a string en-US do VSTest e as de vitest/jest. O exit 9 fica FORA de propósito: é piso de projeto, não zero testes. Leitor: `judgeNarrowedRun` (`verify-runner.js`). `extractCsprojTargets`, `isInside` e `canonicalPath` também são lidos por `test-scope.js` (`canonicalPath` dá a todo path de `outputs` a grafia do DISCO — no win32 um `Foo.test.ts` mal grafado passa no `isFile`, e sob `--linux`, case-sensitive, o runner não o acha enquanto um irmão mantém a contagem acima de zero); `dotnet-test-introspection.js` lê só `isInside`. Um segundo canonicalizador é como os dois lados passam a discordar sobre o mesmo path |
|
|
139
|
+
| `src/lib/specs/screen-checklist.js` (checklist de tela, #129) | o sinal mora aqui (`collectScreenChecklist`) e tem QUATRO leitores: `gate-check.js` na `uiux` (`problems` → pausa) e no `review` (`uncovered`: item decidido sem linha na seção `## Checklist de tela` que `evaluator-report.js` lê — última ocorrência; tela toda "não se aplica" não deve nada), e `approve.js`/`advance.js` com assinatura `auto` (`uiuxAutoApprovalBlock`). Política só em `risk-detector.js`: `uiuxChecklistPause` é o predicado ÚNICO do gate-check pausar e do auto recusar — uma segunda regra no advance fez approve e advance discordarem da mesma feature — e `gateDecisions.uiux.pause === false` destrava os dois só para o mockup que a decisão viu: `persistGateDecision` (`gate-decision.js`, o escritor único de `gateDecisions`) grava `mockupsHash` e `uiuxAutoApprovalBlock` o compara com o arquivo atual — os dois leem `mockupsHash` daqui, e um segundo cálculo do hash (outra normalização de BOM/CRLF) faz toda decisão parecer de outro mockup. `--force` não destrava em nenhum dos dois. Mockup legado (`## S\d+:` e nenhuma `## Tela:`, o template anterior) é isento nas duas fases com `exempt` = `LEGACY_MOCKUP_EXEMPTION` à vista no `getRiskSignals`; qualquer outro heading é "nenhuma tela declarada", senão trocar o heading pularia o checklist (fixture: `test/fixtures/mockups/legacy-ui-mockups.md`, o template de main). Os rótulos dos itens casam com DOIS textos que o parser não vê, os dois com canário em `test/lib/specs/screen-checklist.test.js`: o template do prompt da `morph-uiux` §3 e `framework/templates/docs/ui-mockups.md`; mais o formato de linha que o prompt do avaliador (`morph-review` §4) e a rubrica `contracts.md` ("Contrato de tela") prescrevem. A `Superfície` é validada contra as chaves de `surfaces` da `ui-designer` (`knownSurfaces`) — é a chave que o `morph-plan` §5a usa para somar standards |
|
|
131
140
|
| `src/lib/tasks/test-scope.js` (o recorte por task, #87) | Este DECIDE, `verify-runner.js` executa: `applyTaskScope` roda UMA vez em `runVerify`, antes do build, e `preDecideRun` (pula `out-of-scope`) e `judgeRun` → `judgeNarrowedRun` (nunca `compareTestCount`; zero ou desconhecido é vermelho) são a cópia única para host e `--linux` — o container recebe o recorte pelo `node.command`. Importa `effectiveGroups` (`test-nodes.js`), `extractCsprojTargets`/`isInside`/`canonicalPath` (`test-runner.js`), `normalizePath` (`traceability-check.js` — um normalizador só para `outputs`), `samePathCase` (`docker-audit.js` — só a CAIXA vem do disco: um path que atravessa symlink/junction fica como escrito, senão o arquivo muda de nó) e a introspecção .NET abaixo. `runs[].selection` sai só no `--json`: `nodeStatuses()` não o persiste. Piso `--minimum-expected-tests` de projeto MTP (#114): quando `floorOverride` o lê de UM lugar, o link recortado ganha `"-p:TestingPlatformCommandLineArguments=<valor com o piso em 1>"` (o `-p:` troca o valor inteiro — medido, sem recompilar); qualquer outra forma roda inteiro, e também o comando que já declara a propriedade (o `-p:` do recorte vem por último e ganharia) e o link que roda mais de um projeto (o `-p:` é global à invocação e trocaria os argumentos do outro). Forma de comando nova se lê e se MEDE (`test/fixtures/test-output/README.md`) antes de estreitar; o resto roda INTEIRO com motivo. Canários: `test/lib/tasks/test-scope.test.js`, o bloco `runVerify — task-scope recorte` de `test/lib/verify-runner.test.js`, `test/commands/verify-filter.test.js` e a prosa em `test/framework/verify-stack-docs-prose.test.js` |
|
|
132
141
|
| `src/lib/tasks/dotnet-test-introspection.js` | Só LÊ (dividido de `test-scope.js`): o varredor C# que apaga literais (`blankCSharpLiterals`), o tripwire contra o texto CRU + o balanço de chaves (`csharpClasses`), `classNamesOf`, `dotnetTestMode` (lê o `global.json` por `jsoncToJson` de `src/lib/ai/comment-stripper.js`), o piso `--minimum-expected-tests` / `<VSTestTestCaseFilter>` do projeto, `floorOverride` (#114: o valor do `-p:` que rebaixa o piso a 1 e repassa o resto — só com UM `<TestingPlatformCommandLineArguments>` literal no csproj, sem atributo, num `<PropertyGroup>` sem atributo direto sob `<Project>` (`enclosingElements` anda nas tags: `Condition` no GRUPO, `<Target>` e `<Choose>` injetavam um filtro que o MSBuild não usaria), sem `TreatAsLocalProperty`, sem `<Import>`, sem a propriedade em nenhum outro arquivo que liga o projeto, sem piso no testconfig e sem `` $ @ % ; , & " ' ` \ ! `` no valor; `null` roda inteiro; `bindingMsbuildFiles` é a lista única de arquivos MSBuild que `msbuildDeclares` também lê — o `.csproj.user` e `Directory.Build.props/.targets/.rsp` + `Directory.Packages.props` até a raiz do DISCO, porque o MSBuild não para no repositório), `ignoresExitCode` (o MESMO scan de csproj/Directory.Build.*/testconfig: `--ignore-exit-code` ou `TESTINGPLATFORM_EXITCODE_IGNORE` desarma a prova do recorte MTP que resta quando o sumário é ilegível, o exit code — desde a #115 a contagem do sumário, pt-BR/en-US, prova primeiro, e o scan fica porque decide ANTES da rodada), o projeto de teste dono de um `.cs` (`dotnetTestProjectOf` — só `.cs`/`*.csproj`, via `nearestCsprojOf`), `nearestProjectOf` + `isDotnetTestProject` (qualquer arquivo; cobrem `.csproj`/`.fsproj`/`.vbproj` — `test-scope.js` roda o nó INTEIRO por QUALQUER arquivo não-`.cs` que o nó pode rodar cujo projeto mais próximo é de teste: `.feature`, `.verified.txt`, `.razor`, dado de teste), `isNonCSharpSource`/`isDotnetSource`, `readsLikeTestSource` (o marcador FROUXO no texto cru atrás de `unrecognisedTestFile` e do segundo braço do F2 — falso positivo só custa relógio) e `CS_TEST_ATTRIBUTE` (nome inteiro numa seção de atributo que FECHA — `, TestContext` num construtor e uma coleção `[\n Env.Test,\n]` compravam um verde; o resíduo sondado mora no JSDoc de `csharpClasses`). **Falha para o lado SEGURO:** varredura desbalanceada, literal sem fim, buraco de interpolação cortado ou palavra-chave de tipo que não virou declaração → o nó roda INTEIRO; um erro que ESTREITA roda menos testes e sai verde. `test-scope.js` reexporta `dotnetTestMode`/`classNamesOf`, e `test/lib/tasks/dotnet-test-introspection.test.js` pina que são as MESMAS funções. A garantia e o que ela NÃO cobre moram no JSDoc de `csharpClasses`: mexeu no varredor, reescreva-o junto e prove por mutação que o tripwire ainda morde |
|
|
133
142
|
| `src/lib/tasks/linux-runner.js` | `verify-runner.js` (`runNodesInLinux` faz as MESMAS pré-decisões do laço local e julga o resultado pelo MESMO `judgeRun` — uma segunda cópia dessa regra é como container e host passam a discordar da mesma suíte), `framework/hooks/shared/e2e-check.js` (`defaultExec`/`isDockerAvailable` são reusados, nunca recriados), `src/lib/worktree/dirs.js` (nomes do contêiner de worktree nas exclusões do `tar`), `framework/schemas/config.schema.json` (`linuxImage`) e `test/lib/tasks/linux-runner.test.js` (o canário do bind mount: montar a árvore em `/work` é v9fs case-insensitive e não reproduz F35) |
|
|
134
143
|
| `--record-baseline` (`recordTestBaselines` em `src/lib/tasks/test-nodes.js`) | `verify-runner.js` (`baselineTargetProblem` recusa ANTES do build; `recordBaselineFrom` só grava com todos os `runs[]` verdes), `src/commands/validation/verify.js` (não persiste stamp: é manutenção de config, não prova de gate), `framework/schemas/config.schema.json` (`expectedCount`) e `atomicWriteJson` de `state-manager.js` — o número é MEDIDO por comando, nunca digitado (D8). Com `{task}` é erro de uso ANTES do build (`baselineTargetProblem`, #87), e `recordTestBaselines` recusa de novo qualquer medição com `selection` — a segunda barreira mora no gravador |
|
|
135
144
|
| `src/lib/tasks/eval-node.js` | os leitores do MESMO nó `runner: "evals"`: `test-nodes.js` (o enum de `TEST_RUNNERS` e a validação fail-closed do bloco `evals`), `verify-runner.js` (`preDecideRun` faz o pré-voo — os três estados de cache reprovam ANTES de `dotnet test`; `judgeEvalRun` lê o sumário depois), `verify.js` (`evalsStatusOf` deriva `nodes.evals` do stamp) e `framework/schemas/config.schema.json`. O pin tem UM leitor (`readAiPin`), chamado com duas raízes (`framework/` e `.morph/framework/`) — um segundo parser de `ai-pin.json` é como as duas verdades começam. **`null` é DESCONHECIDO, nunca 0**: sumário ilegível não pode fabricar queda contra `expectedCount`, e miss de cache é `fail` nomeado, JAMAIS `skip`. A unidade do nó é o CENÁRIO: só `judgeEvalRun` compara com `expectedCount` — `judgeRun` passa baseline `null` para `runner: "evals"` (#110 parte 2: os `[Fact]` do extrator contra a baseline de cenários reprovavam um 24/24), e sem sumário legível a contagem é `null`, nunca a do extrator — nem compara nem vira baseline. O sumário (`morph-eval-{id}-{pid}.json` no temp) é APAGADO antes e depois da rodada: um arquivo herdado sob o mesmo pid era lido como desta. `--linux` com nó de eval é erro de uso: o canal `MORPH_EVAL_*` carrega caminhos absolutos do host e o container roda uma cópia em `/work` |
|
|
136
145
|
| `nodeStatuses()` em `src/commands/validation/verify.js` | os TRÊS blocos `nodes` de `framework/schemas/feature.schema.json`, todos `additionalProperties: false` — `verification.nodes`, `verifications.feature.nodes` e `taskVerifications.*.nodes`. A função é escritora ÚNICA dos três: um campo novo aqui sem os três no schema faz o histórico violar o próprio contrato. Canário: "os DOIS escritores do mapa de nós concordam" em `test/commands/verify.test.js` |
|
|
137
|
-
| `src/lib/graph/**` | `test/lib/graph/*.test.js`, the committed fixture `test/fixtures/graph/namespace-hierarchy.graph.json` (ADR-003 regression — never hand-edit it), `doctor.js` (`codebase graph` check), `architecture-scanner.js` (centrality section), `setup-infra.js` step 18b |
|
|
146
|
+
| `src/lib/graph/**` | `test/lib/graph/*.test.js`, the committed fixture `test/fixtures/graph/namespace-hierarchy.graph.json` (ADR-003 regression — never hand-edit it), `doctor.js` (`codebase graph` check), `architecture-scanner.js` (centrality section), `setup-infra.js` step 18b. `isTestPath` (`sanitize.js`) é o predicado ÚNICO de teste: `god-nodes.js`, `recoverDispatch`, `graph status\|explain` e `symbol-scanner.js` (`isTestFile`, que o `code-budget` também lê) o importam, e uma segunda régua faz o grafo e o validador discordarem sobre o mesmo arquivo. A fixture `test-doubles.graph.json` saiu do graphify real (passo a passo em `docs/specs/graphify-integration/fixture-test-doubles/`) — regerar, nunca editar à mão |
|
|
147
|
+
| `framework/hooks/shared/graph-freshness.js` (a regra única de "grafo em dia") | os três lados que perguntam: `graph-refresh.js` (decide se roda e se o disparo solta processo), `graph-loader.js` `getFreshness` (`graph status\|explain\|affected` e o `doctor`) e `graph build\|refresh` (gravam a rodada em `<git-common-dir>/morph-spec/graph-refresh-state.json`). O registro da rodada existe porque o `graphify update` 0.9.50 não recarimba `built_at_commit` quando a topologia não muda: sem ele, o grafo parecia velho para sempre e cada disparo rodava o graphify de novo (~80 s no MORPH_GHLBrain). As regras do `.graphifyignore` entram por CONTEÚDO (hash sem comentário, BOM e CRLF), nunca pelo relógio, e `ignoreTextOf` é a ÚNICA leitura de "as regras desta árvore": numa worktree sem arquivo próprio, as da raiz — as mesmas que o `graph refresh` põe ali durante a rodada (`prepareIgnore` em `build.js`). O arquivo temporário leva `TEMP_IGNORE_MARKER` na 1ª linha; nas duas triagens de sujeira (`finish` e `worktree remove`) `withoutLeftoverIgnore` o tira da conta ANTES das recusas e `dropLeftoverIgnore` o apaga só DEPOIS delas — apagar antes é mutação atrás de uma precondição. Mudar a grafia do marcador num lado só devolve o `worktree-dirty` de uma rodada morta no meio. Mora em `hooks/shared` porque hook não importa `src/` |
|
|
148
|
+
| `framework/hooks/shared/graph-refresh.js` (o refresh em background) | os dois disparos (`graph-refresh-kick`, depois de Bash/PowerShell com `git`/`gh`/`morph-spec`, e o SessionStart de `worktree-context.js`), `graph build\|refresh` (o MESMO lock por repositório) e `worktree.js` (`seedGraphFromRoot`: a worktree nasce com o grafo da raiz e o registro dela — em dia no HEAD só se a raiz estava). **Só a raiz**: o worker destacado nasce com `cwd` no git common dir e nunca recebe o caminho de uma worktree — no win32 um diretório em uso não se apaga, e o `finish` de uma worktree com refresh correndo falhava no meio. A worktree anda por `morph-spec graph refresh`, em primeiro plano. `runGraphify` roda o graphify com `cwd` no git common dir, nunca na árvore (ele lê o alvo, o ignore e o git pelo argumento — medido no 0.9.50). O disparo lê o HEAD pelo disco (`readPrimaryHead`, `git-paths.js`) e não solta processo quando o grafo não pede refresh: ele roda a cada comando git do agente. O step 18b de `setup-infra.js` não instala o hook git do graphify: ele não roda em worktree, não tem `post-merge` e carimba como em dia um grafo sem os arquivos do merge |
|
|
149
|
+
| `framework/hooks/shared/git-exclude.js` | ÚNICO escritor dos blocos do morph-spec no `info/exclude` do COMMON dir — o `.git/worktrees/<id>/info/exclude` o git não lê para worktree vinculada. Leitores que passam o seu marcador: `worktree.js`, `handoff-generator.js` e `graph-refresh.js`. Resolve o common dir pelo disco, sem subprocesso: um `git` gerado por um processo destacado abria janela de console no win32 |
|
|
138
150
|
| `src/lib/validators/reuse/**` | `validation-runner.js` (case + push universal), `framework/rubrics/*.json` (o id `reuse` mapeado numa categoria, senão o achado não pesa no score), `test/fixtures/graph/prospectpro-reuse.graph.json` (recorte do grafo real — regerar com `build-reuse-fixture.mjs`, nunca editar à mão) |
|
|
151
|
+
| `src/lib/validators/code-budget/**` | `validation-runner.js` (case + push universal fora do `light`), `framework/rubrics/*.json` (o id `code-budget` em `code_quality` nas TRÊS, senão o aviso não pesa), `framework/docs/CLI.md` (o que conta como teste e os limites 1,5×/40 linhas) e `test/integration/code-budget-verify.test.js`. Lê `addedLinesAgainst` (`default-branch.js`), `isTestFile`/`isProjectSource` (`symbol-scanner.js`, a mesma régua de teste do `reuse`) e `stripComments` com `js` (`comment-stripper.js`). Sempre `warning`. Não conseguir medir é skip `unavailable` com razão, e o `validation-runner` não põe validador pulado em `results`: pulado não conta como verde |
|
|
139
152
|
| o ciclo de reuso (`reuse-map.md`) | `output-schema.js` (+ `npm run generate`), `framework/schemas/tasks.schema.json` + `tasks-json-parser.js` (campo `reuse`), `morph-plan` §1c/§5b, template de dispatch de `morph-implement`/`morph-apply`, personas de `agents.json`, `evals/rubrics/code-quality.md` + `condensed.md`, `morph-review` (insumo do avaliador), `test/framework/graph-fallback.test.js` |
|
|
140
153
|
| `framework/mcp/registry.json` | mcp-installer.js, claude-config-detector.js, agents-installer.js (personas' `mcp` field), `scripts/validate-framework.js` Check 8, `test/framework/mcp-registry.test.js` |
|
|
141
154
|
| Any `framework/` file that calls `morph-spec graph` | The fallback to grep must be declared in the SAME file — `test/framework/graph-fallback.test.js` enforces it (ADR-002: a skill that requires the graph with no fallback is a bug) |
|
|
@@ -146,7 +159,7 @@ When modifying framework components, verify the full scope of related files.
|
|
|
146
159
|
| `src/lib/risk-detector.js` | os DOIS lados da mesma decisão de gate: `gate-check.js` (`collectSignals` COLETA do disco; nenhum sinal que descreva um FATO pode entrar em `OVERRIDABLE_SIGNAL_KEYS` — `gate`/`workType`/`e2e*` são o precedente), `src/commands/validation/verify.js` (o stamp `verification` é a ÚNICA coisa que o gate lê: campo novo aqui só vira sinal se o stamp o gravar), `framework/schemas/feature.schema.json` (`verification` é `additionalProperties: false`), `framework/MORPH.md` §5 (tabela de sinais — ela se declara fonte única) e `framework/docs/CLI.md`. Política escrita no coletor cria duas fontes para o mesmo fato — foi assim que `gate-check` e `approve` divergiram uma vez sobre o score do Gate 3. **`verificationScope` é sinal, não auditoria**: a dispensa de e2e (`project`/`scope`) só conta vinda de um stamp de escopo `feature` — um `verify {f} {task}` de task `group: docs` carimba a razão de dispensa e compraria o Gate 3 da feature inteira. **E o escopo de task pausa o ship por si só** (#87): no `review`, `verificationScope` string ≠ `'feature'` pausa ANTES do bloco de e2e, mesmo com todos os nós verdes — o recorte automático faz TODO `verify {f} {task}` rodar menos que a suíte; `null` (stamp legado) não pausa por esta regra. O par mora em `verify.js`: `--filter` de escopo de feature não persiste o stamp (`reason: 'filtered'`), nem `--skip-tests`/`--skip-build` (`'skip-tests'`/`'skip-build'`, #113 — nó pulado não reprova o rollup e nenhuma regra do gate lê `tests`/`build` pulados; a lista é `PARTIAL_RUN_FLAGS`) |
|
|
147
160
|
| `src/lib/installers/effects.js` | os NOVE pontos de emissão que atravessam o caminho de `update`: `update.js` (clean, payloads, spawn de `generate-refs.js`, `gitIndex`), `skills-installer.js`, `agents-installer.js`, `setup-infra.js` (`installRulesForStack`, `updateClaudeMdAgentsBlock`), `hooks-installer.js` (`installClaudeHooks`, `installGlobalStatusline`), `file-copier.js` (`updateGitignore`, `ensureGitattributes`), `claude-md-injector.js`, `version-checker.js` (`saveProjectMorphVersion`) e `templates-reconcile.js`. **O caminho real também emite** — o resumo de uma execução real sai de `list()`, e é por isso que um ponto de emissão esquecido some das DUAS saídas ao mesmo tempo. Canários: `test/lib/installers/effects.test.js` (igualdade dry × real do MESMO roteiro) e `test/commands/update-dry-run.test.js` — que tem DOIS canários e precisa dos dois: **igualdade** prova que os dois lados percorrem o mesmo caminho, **diferencial** (mudar `--templates` muda a previsão) prova que o caminho lê a flag. Só igualdade aprova uma previsão e uma execução que ignoram a MESMA flag — foi medido nesta feature, por mutação. Um laço de limpeza de órfão novo tem de nascer calado em `fx.dryRun` (D6): o clean o subsome. E a lista de `frameworkDirsToClean` é pinada **à mão** no teste (`DIRETORIOS_APAGADOS_POR_INTEIRO`, dez caminhos, `deepEqual` ordenado): derivar a expectativa chamando a própria função deixou a suíte inteira cega — medido, dava para remover 2 das 10 entradas sem matar nenhum dos 207 arquivos de teste |
|
|
148
161
|
| `src/lib/ai/**` (`doctor --ai`) | `src/commands/project/doctor.js` (`doctorAiCommand` + a guarda de uso indevido), `bin/morph-spec.js` (`--ai`), `framework/docs/CLI.md`. **`--ai` NUNCA entra no `doctor --full` nem no default** — o renderer do `--full` manda tudo que não é `ok`/`warn` para `✗` e `test/commands/doctor.test.js` exige zero `✗` neste repositório. **Nada alcançável por `--ai` pode escrever**: `test/commands/doctor-ai.test.js` varre o grafo de imports E o corpo de `doctorAiCommand` (com os imports dinâmicos fixados nos dois permitidos), reprovando `writeFileSync`/`spawn`/`fs-extra` em CÓDIGO — comentários apagados antes, porque os módulos EXPLICAM por escrito que não fazem isso e medir a menção seria medir uma cópia do objeto. `null` é DESCONHECIDO e vira `skip` **com razão obrigatória**; `[]` é "perguntei e não achei" e vira `ok` |
|
|
149
|
-
| `src/lib/ai/registry-audit.js` | o leitor de PRODUÇÃO do mesmo documento: `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs` (`CommentHandling = Skip`, `AllowTrailingCommas`, `AllowedAliasProperties` com `pricing`/`pricingWaiver`) e o exemplar `model-registry.v2.json` que o framework manda COPIAR. A auditoria tem de enxergar a **mesma forma** que o leitor de produção enxerga: com `JSON.parse` estrito, duas das cinco checagens saíam `skip` sobre o exemplar canônico — e `skip` não muda exit code, então o silêncio era total. A tolerância a JSONC vive em `src/lib/ai/comment-stripper.js`, ÚNICA (o `code-audit.js` usa o mesmo varredor) — e a paridade é nos DOIS sentidos: `jsoncToJson` **lança** no comentário de bloco não fechado, porque o `JsonCommentHandling.Skip` do .NET lança, e ser mais frouxo que o kit é aprovar documento que a app recusa carregar. Ao mascarar strings, **as aspas delimitadoras ficam**: apagá-las junto faz um elemento-string virar espaço puro e o detector de vírgula sobrando comer um SEPARADOR real (`{"tags":["a","b"]}` saía inválido). Canário obrigatório: array terminado em **string**, com e sem vírgula sobrando — uma suíte só com arrays de número passa dos dois lados dessa inversão. A **REGRA DURA** do exemplar — *todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum* — é do alias, não de um mapa no topo: ler só o mapa dava 6 falsos positivos sobre um documento correto. O canário **copia o exemplar do disco**, nunca uma paráfrase dele |
|
|
162
|
+
| `src/lib/ai/registry-audit.js` | o leitor de PRODUÇÃO do mesmo documento: `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs` (`CommentHandling = Skip`, `AllowTrailingCommas`, `AllowedAliasProperties` com `pricing`/`pricingWaiver`) e o exemplar `model-registry.v2.json` que o framework manda COPIAR. A auditoria tem de enxergar a **mesma forma** que o leitor de produção enxerga: com `JSON.parse` estrito, duas das cinco checagens saíam `skip` sobre o exemplar canônico — e `skip` não muda exit code, então o silêncio era total. A tolerância a JSONC vive em `src/lib/ai/comment-stripper.js`, ÚNICA (o `code-audit.js` usa o mesmo varredor, e o `code-budget` também, o único com a opção `js` — template e regex literal de TS/JS; sem ela o varredor é o de antes) — e a paridade é nos DOIS sentidos: `jsoncToJson` **lança** no comentário de bloco não fechado, porque o `JsonCommentHandling.Skip` do .NET lança, e ser mais frouxo que o kit é aprovar documento que a app recusa carregar. Ao mascarar strings, **as aspas delimitadoras ficam**: apagá-las junto faz um elemento-string virar espaço puro e o detector de vírgula sobrando comer um SEPARADOR real (`{"tags":["a","b"]}` saía inválido). Canário obrigatório: array terminado em **string**, com e sem vírgula sobrando — uma suíte só com arrays de número passa dos dois lados dessa inversão. A **REGRA DURA** do exemplar — *todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum* — é do alias, não de um mapa no topo: ler só o mapa dava 6 falsos positivos sobre um documento correto. O canário **copia o exemplar do disco**, nunca uma paráfrase dele |
|
|
150
163
|
| `src/lib/worktree/dirs.js` | ÚNICA fonte do nome do container (`worktrees/`, sem ponto — Jest esconde diretório com ponto e reporta 0 testes sem erro). `worktree.js` re-exporta; `graphifyignore.js`, `file-copier.js`, `architecture-scanner.js`, `test-runner.js`, `worktree-audit.js`, `state-scanner.js`, `tasks/linux-runner.js` (exclusões do `tar` do `--linux`) importam. `test/lib/worktree/dirs.test.js` cobra cada leitor |
|
|
151
164
|
|
|
152
165
|
### Test Coverage
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
|
|
4
4
|
|
|
5
5
|
**Package:** `@polymorphism-tech/morph-spec`
|
|
6
|
-
**Version:** 8.
|
|
6
|
+
**Version:** 8.38.0
|
|
7
7
|
**Requires:** Node.js 22+, Claude Code
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -379,4 +379,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
|
|
|
379
379
|
|
|
380
380
|
---
|
|
381
381
|
|
|
382
|
-
*morph-spec v8.
|
|
382
|
+
*morph-spec v8.38.0 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/bin/morph-spec.js
CHANGED
|
@@ -44,7 +44,7 @@ import { retypeCommand } from '../src/commands/state/retype.js';
|
|
|
44
44
|
import { handoffCommand } from '../src/commands/state/handoff.js';
|
|
45
45
|
import { listCommand } from '../src/commands/state/list.js';
|
|
46
46
|
import { deleteCommand } from '../src/commands/state/delete.js';
|
|
47
|
-
import { usecaseReserveCommand } from '../src/commands/state/usecase.js';
|
|
47
|
+
import { usecaseReserveCommand, usecaseCheckCommand } from '../src/commands/state/usecase.js';
|
|
48
48
|
|
|
49
49
|
// === Tasks ===
|
|
50
50
|
import { dagCommand } from '../src/commands/tasks/dag.js';
|
|
@@ -137,7 +137,7 @@ program
|
|
|
137
137
|
.option('--mcp', 'Check MCP server configuration')
|
|
138
138
|
.option('--ai', 'Auditar um projeto .NET com agentes: pin × projeto, API renomeada, tarifa e `api` por alias, prompt sem fonte')
|
|
139
139
|
.option('--worktrees', "Reconcile git's worktree registry × the directories on disk × known features")
|
|
140
|
-
.option('--prune', "With --worktrees: drop git records for worktree directories that are already gone,
|
|
140
|
+
.option('--prune', "With --worktrees: drop git records for worktree directories that are already gone, clear any feature's root worktree registration pointing at one of them, and apply the finish-backup retention (.git/morph-spec/finish-backup) — the only path that also prunes backups written before it (no manifest)")
|
|
141
141
|
.option('--json', 'With --worktrees: output as JSON')
|
|
142
142
|
.option('--reset', 'Remove morph-managed entries from .claude/settings.local.json')
|
|
143
143
|
.action(doctorCommand);
|
|
@@ -285,7 +285,7 @@ program
|
|
|
285
285
|
// ─── Use cases ────────────────────────────────────────────────────────────
|
|
286
286
|
const usecaseCommand = program
|
|
287
287
|
.command('usecase')
|
|
288
|
-
.description('Project-wide UC-{nn} numbering (acervo + every worktree of this repo)');
|
|
288
|
+
.description('Project-wide use cases: UC-{nn} numbering (acervo + every worktree of this repo) and the acervo lifecycle check');
|
|
289
289
|
|
|
290
290
|
usecaseCommand
|
|
291
291
|
.command('reserve')
|
|
@@ -295,6 +295,12 @@ usecaseCommand
|
|
|
295
295
|
.option('--json', 'Output as JSON')
|
|
296
296
|
.action((options) => usecaseReserveCommand(options));
|
|
297
297
|
|
|
298
|
+
usecaseCommand
|
|
299
|
+
.command('check <feature>')
|
|
300
|
+
.description('Check a feature against the acervo lifecycle: tasks citing a superseded UC, malformed supersession frontmatter, spec.md revocation rows the finish could not apply (plus the task/use-case plumbing dag checks, when tasks.json exists)')
|
|
301
|
+
.option('--json', 'Output as JSON')
|
|
302
|
+
.action((feature, options) => usecaseCheckCommand(feature, options));
|
|
303
|
+
|
|
298
304
|
// ─── Tasks ────────────────────────────────────────────────────────────────
|
|
299
305
|
program
|
|
300
306
|
.command('dag <feature>')
|
|
@@ -568,10 +574,11 @@ program
|
|
|
568
574
|
|
|
569
575
|
// ─── Sub-agent report protocol ────────────────────────────────────────────
|
|
570
576
|
program
|
|
571
|
-
.command('report-check <feature> [
|
|
572
|
-
.description('Audit sub-agent report files against the work that actually landed. Answers "did the WORK land even though the REPORT did not" — exits 0 only when everything is intact')
|
|
577
|
+
.command('report-check <feature> [tasks...]')
|
|
578
|
+
.description('Audit sub-agent report files against the work that actually landed. Answers "did the WORK land even though the REPORT did not" — exits 0 only when everything is intact. Several task ids audit a parallel batch in one call')
|
|
579
|
+
.option('--before-eval', 'Save a snapshot of the tree before dispatching the evaluator; later runs report what changed since')
|
|
573
580
|
.option('--json', 'Output as JSON')
|
|
574
|
-
.action((feature,
|
|
581
|
+
.action((feature, tasks, options) => reportCheckCommand(feature, tasks.length > 1 ? tasks : tasks[0], options));
|
|
575
582
|
|
|
576
583
|
// ─── Dashboard ────────────────────────────────────────────────────────────
|
|
577
584
|
program
|
|
@@ -594,7 +601,9 @@ program
|
|
|
594
601
|
.command('cost [feature]')
|
|
595
602
|
.description('Cost-facing view over .morph/logs/events.jsonl: task duration, bytes re-ingested by kind (proxy, not tokens), dispatch counts')
|
|
596
603
|
.option('--by <mode>', 'Group by: task|phase (default: feature-wide summary; with --all-trees, applies to the combined aggregate)')
|
|
597
|
-
.option('--all-trees', 'Aggregate
|
|
604
|
+
.option('--all-trees', 'Aggregate across every git worktree with a log plus the logs finish saved in .git/morph-spec/finish-backup — per-tree breakdown + combined total')
|
|
605
|
+
.option('--metrics', 'Loop measurements: turns, tool calls (orchestrator x sub-agent), time per command class, identical reruns, rereads, feature verify count, git per task, reuse validator per tree')
|
|
606
|
+
.option('--top <n>', 'With --metrics: how many reread files to list (default 10)', (v) => Number.parseInt(v, 10))
|
|
598
607
|
.option('--json', 'Output the aggregate as JSON')
|
|
599
608
|
.action((feature, options) => costCommand(feature, options));
|
|
600
609
|
|
package/claude-plugin.json
CHANGED
package/docs/CHEATSHEET.md
CHANGED
package/docs/QUICKSTART.md
CHANGED
package/framework/CLAUDE.md
CHANGED
|
@@ -66,14 +66,14 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
|
|
|
66
66
|
| `fleet` | Visão agregada de toda feature ativa (raiz + worktrees) |
|
|
67
67
|
| `claim [f] [--release] [--list]` | Posse advisory — confirme antes de `finish`/`archive`/`delete` se outra sessão tiver claim vivo |
|
|
68
68
|
| `dag <f>` | DAG de `tasks.json`: sequencial/paralelo, recomenda isolamento |
|
|
69
|
-
| `graph status\|build\|refresh` | Grafo de codebase. `status` reporta frescor
|
|
69
|
+
| `graph status\|build\|refresh` | Grafo de codebase. `status` reporta frescor e se há refresh em background |
|
|
70
70
|
| `graph affected\|explain <sym>` | Blast radius e vizinhança saneados. Sem grafo, caia para grep — nunca é erro |
|
|
71
71
|
| `verify <f> [task] [--json]` | Build+testes+validadores+e2e, antes do juiz LLM |
|
|
72
72
|
| `e2e up\|status\|down <f>` | Stack de verificação (docker compose) |
|
|
73
73
|
| `doctor --worktrees [--prune]` | Reconcilia git × disco × features; só `--prune` remove algo |
|
|
74
|
-
| `report-check <f> [task] [--json]` | Audita relatório de sub-agent vs. trabalho entregue |
|
|
74
|
+
| `report-check <f> [task...] [--json]` | Audita relatório de sub-agent vs. trabalho entregue |
|
|
75
75
|
| `telemetry [--feature] [--limit]` | Caminho de execução da sessão a partir de `events.jsonl` |
|
|
76
|
-
| `cost [f] [--by task\|phase]` | Custo (bytes/duração)
|
|
76
|
+
| `cost [f] [--by task\|phase] [--metrics]` | Custo (bytes/duração) e medições do loop (turnos, rerun, releitura) do mesmo log |
|
|
77
77
|
|
|
78
78
|
Prefixo de todo comando: `morph-spec`. Flags/exemplos: `.morph/framework/CLI.md`.
|
|
79
79
|
|
package/framework/MORPH.md
CHANGED
|
@@ -216,37 +216,40 @@ Gates são pontos de **pausa potencial**. Em `trust: "auto"` (default), auto-pas
|
|
|
216
216
|
**Sinais de risco que forçam pausa em auto** (`gate-check` decide via `shouldPauseGate` — fonte única; sinal que só a sessão conhece escala via `gate-decision` manual):
|
|
217
217
|
|
|
218
218
|
| Sinal | Threshold | Quando aplica |
|
|
219
|
-
|
|
220
|
-
| `designSystemDivergence` | true | Brainstorming detecta fontes conflitantes
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| `designSystemDivergence` | true | Brainstorming detecta fontes conflitantes |
|
|
221
221
|
| `standardsViolations` | array.length > 0 | Mandate ou implementation viola padrões proibidos |
|
|
222
222
|
| `evalScore` | < 9 | score composto da feature (morph-review) abaixo do mínimo |
|
|
223
|
-
| `verificationStatus` | === 'fail' | `verify` vermelho — build, testes, validadores **ou e2e
|
|
223
|
+
| `verificationStatus` | === 'fail' | `verify` vermelho — build, testes, validadores **ou e2e**. Vale em **qualquer** gate |
|
|
224
224
|
| `verificationStale` | true | Stamp do `verify` com sha != HEAD: o verde não cobre mais o código. Só no **ship** |
|
|
225
225
|
| `e2eSkipClass` (derivada) | environment/requested/unknown | e2e pulou sem dispensa legítima (só `project`/`scope`). Só no **ship** |
|
|
226
226
|
| `verificationScope` | !== `feature` (presente) | Stamp do `verify` é de escopo parcial (`task:{id}`) — não cobre a feature inteira. Só no **ship** |
|
|
227
227
|
| `mutationProof` | seção ausente | Relatório do avaliador sem `## Mutações` numa feature com código de produção. Só no **ship** |
|
|
228
|
+
| `evalScoreSource` | ≠ avaliador | Score que não veio do `evaluator-report`. Só no **ship** |
|
|
229
|
+
| `promptModel` | prompt/modelo tocado | Sem nó `evals` declarado, ou eval pulado neste verify. Só no **ship** |
|
|
230
|
+
| `mediaClaims` | seção ausente | Feature que gera mídia, relatório sem `## Mídia gerada`. Só no **ship** |
|
|
231
|
+
| `screenChecklist` | item em aberto | **uiux**: tela sem checklist completo, ou nenhuma `## Tela:`. **ship**: item decidido sem linha em `## Checklist de tela` |
|
|
228
232
|
| `taskCount` | > 20 | Escopo grande — vale revisar antes de implementar |
|
|
229
233
|
| `workType` | === 'hotfix' | Gate 3 pausa SEMPRE (ver "Gates por tipo de trabalho") |
|
|
230
234
|
|
|
231
235
|
| Gate | Sinais | Skill responsável |
|
|
232
|
-
|
|
233
|
-
| 1 — Proposta | `designSystemDivergence`, `standardsViolations`
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| 1 — Proposta | `designSystemDivergence`, `standardsViolations` | `morph-brainstorming` |
|
|
234
238
|
| 2 — Plano | Gate 1 + `taskCount`, `standardsViolations` em mandate | `morph-plan` |
|
|
235
|
-
|
|
|
239
|
+
| uiux — Design | `screenChecklist` | `morph-uiux` |
|
|
240
|
+
| 3 — Review | `evalScore` composite, `standardsViolations` em implementation, `verificationStale`, `verificationScope`, `e2eSkipClass`, `mutationProof`, `evalScoreSource`, `promptModel`, `mediaClaims`, `screenChecklist` | `morph-review` |
|
|
236
241
|
|
|
237
|
-
`
|
|
242
|
+
**Gates por tipo de trabalho:** `chore`/`hotfix` pulam Gates 1 e 2 — carimbados por política no nascimento (`approvedBy: "policy:{tipo}"`, nunca `"auto"`); a barra do morph-eval e o Gate 3 valem para todos os tipos. `hotfix`: Gate 3 **sempre humano** (mesmo em `trust: auto`: a skill pausa, o `risk-detector` força e o `approve ... review --mode auto` recusa) e exige `4-review/review-report.md` em vez de `evaluator-report.md`. Demais tipos exigem `evaluator-report.md`, **enforced server-side** (`morph-spec approve {feature} review` recusa exit 1 sem ele; `--force` não dispensa) — ou `--self-assessed` registrado (`approvalGates.review.selfAssessed`, marcado no `morph-spec list`).
|
|
238
243
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
Para alterar o modo: `.morph/config/config.json → trust` (`"auto"` ou `"manual"`; campo ausente = `"auto"`).
|
|
244
|
+
Modo: `.morph/config/config.json → trust` (`"auto"`, o default, ou `"manual"`).
|
|
242
245
|
|
|
243
246
|
**Comportamento padrão fora de gate:** autonomia total — só pausa nestes três pontos. Ambiguidade de escopo fora do mandate: registre em decisions.md, resolva conservadoramente, sinalize na entrega da task.
|
|
244
247
|
|
|
245
|
-
**A aprovação de gate é unificada nos comandos da CLI — nunca edite o estado à mão.** Em `trust: "auto"` (sem `AskUserQuestion`): `morph-spec approve <feature> <gate> --mode auto`; `morph-spec advance <feature>` aprova o gate corrente **e** cria a pasta da próxima fase (`--design` = ramo opcional de uiux). Scores por task: `morph-spec score <feature> <task> <score> [--dims a,c,q,t]` — nunca edite `taskScores` à mão. **O hook state-sync.js** cobre o caminho manual: `AskUserQuestion` respondida com label "aprovado" → registra o gate no `feature.json` autoritativo com `approvedBy: "manual"`. **A feature é identificada pelo nome que a pergunta cita** — grava na árvore
|
|
248
|
+
**A aprovação de gate é unificada nos comandos da CLI — nunca edite o estado à mão.** Em `trust: "auto"` (sem `AskUserQuestion`): `morph-spec approve <feature> <gate> --mode auto`; `morph-spec advance <feature>` aprova o gate corrente **e** cria a pasta da próxima fase (`--design` = ramo opcional de uiux). Scores por task: `morph-spec score <feature> <task> <score> [--dims a,c,q,t]` — nunca edite `taskScores` à mão. **O hook state-sync.js** cobre o caminho manual: `AskUserQuestion` respondida com label "aprovado" → registra o gate no `feature.json` autoritativo com `approvedBy: "manual"`. **A feature é identificada pelo nome que a pergunta cita** — grava na árvore dona dela, nunca na "feature ativa" da sessão. Pergunta sem nome numa árvore com mais de uma feature: o hook **recusa gravar** e avisa — aprove por `morph-spec approve`.
|
|
246
249
|
|
|
247
|
-
**Registre quem aprovou (`approvedBy`):** objeto `{ approved, timestamp, approvedBy }`. `"auto"` — política `trust: auto` sem decisão humana. `"manual"` — humano aprovou (via `AskUserQuestion` ou, em headless, texto iniciado em "aprovado"); em `trust: manual` todo gate é `"manual"` — nunca `"auto"` num gate humano.
|
|
250
|
+
**Registre quem aprovou (`approvedBy`):** objeto `{ approved, timestamp, approvedBy }`. `"auto"` — política `trust: auto` sem decisão humana. `"manual"` — humano aprovou (via `AskUserQuestion` ou, em headless, texto iniciado em "aprovado"); em `trust: manual` todo gate é `"manual"` — nunca `"auto"` num gate humano.
|
|
248
251
|
|
|
249
|
-
**O hook gate-guard.js** confere, sem bloquear, se a ação num gate (pausar vs auto-aprovar) bateu com a decisão persistida. **Default:** `gate-check <feature> <gate> [--signals <json>] [--json]` decide via `shouldPauseGate`; `--signals` sobrescreve só o que o disco não expõe (`designSystemDivergence`, `standardsViolations`, `evalScore`). **Escape manual** (sinal que `shouldPauseGate` não representa
|
|
252
|
+
**O hook gate-guard.js** confere, sem bloquear, se a ação num gate (pausar vs auto-aprovar) bateu com a decisão persistida. **Default:** `gate-check <feature> <gate> [--signals <json>] [--json]` decide via `shouldPauseGate`; `--signals` sobrescreve só o que o disco não expõe (`designSystemDivergence`, `standardsViolations`, `evalScore`). **Escape manual** (sinal que `shouldPauseGate` não representa): `gate-decision <feature> <gate> --pause <bool> [--reason <txt>] [--signals <json>]`. Ambos gravam `gateDecisions[gate] = { pause, reason, signals, trust, decidedAt }` server-side (`.morph/framework/schemas/feature.schema.json`). **Nunca edite `gateDecisions` à mão** — colide com state-sync. Divergência → o guard avisa no contexto.
|
|
250
253
|
|
|
251
254
|
---
|
|
252
255
|
|
|
@@ -280,20 +283,19 @@ canal autoritativo. O sub-agent **sempre** grava o relatório num arquivo canôn
|
|
|
280
283
|
- Task de implementação: `.morph/features/{feature}/3-implement/reports/{taskId}.md` (um arquivo por task concluída, mesmo quando uma sessão de sub-agent cobre várias tasks de um fluxo).
|
|
281
284
|
- Avaliador do Gate 3 (`morph-evaluator`): `.morph/features/{feature}/4-review/evaluator-report.md` — relatório BRUTO; a `morph-review` o lê e sintetiza o `review-report.md` do Gate 3 (nunca colidem: um é saída crua do sub-agent, o outro é curadoria do orquestrador).
|
|
282
285
|
|
|
283
|
-
O orquestrador **lê o arquivo** ao retomar controle
|
|
284
|
-
de retorno da tool (que, se chegou inteira, só economiza a leitura).
|
|
286
|
+
O orquestrador **lê o arquivo** ao retomar controle; a mensagem, se chegou inteira, só poupa a leitura.
|
|
285
287
|
|
|
286
288
|
### Protocolo de auditoria do relatório
|
|
287
289
|
|
|
288
290
|
**"Relatório ausente = o agente falhou" é a leitura errada** — ele pode ter feito o trabalho e
|
|
289
|
-
perdido o meio de gravá-lo (ex.: o worktree sumiu
|
|
291
|
+
perdido o meio de gravá-lo (ex.: o worktree sumiu). E relatório PRESENTE também não
|
|
290
292
|
basta: um gravado cedo demais fica completo e desatualizado.
|
|
291
293
|
|
|
292
294
|
A pergunta que decide não é "o relatório chegou?", é **"o TRABALHO entrou, e é isto que ele diz?"** —
|
|
293
295
|
respondida por evidência, não pela palavra do agente:
|
|
294
296
|
|
|
295
297
|
```
|
|
296
|
-
morph-spec report-check <feature> [task] --json
|
|
298
|
+
morph-spec report-check <feature> [task...] --json
|
|
297
299
|
```
|
|
298
300
|
|
|
299
301
|
| veredito | o que significa | ação |
|
|
@@ -316,29 +318,29 @@ aceito em silêncio. Segunda ausência sem trabalho entregue é falha real: esca
|
|
|
316
318
|
|
|
317
319
|
**Contrato do relatório final:** as sete cláusulas que todo prompt de dispatch inclui vivem nos DOIS templates — `morph-implement` §4 e `morph-apply` §3b — e não são repetidas aqui: duas cópias da mesma regra divergem, e quem manda é a que o sub-agent recebe. **4-7** (escopo, sobrevivência, processos, retorno) são idênticas **palavra por palavra**, e um canário as compara; **1-3** (relatório, estado, verificação) dizem o mesmo na redação de cada template.
|
|
318
320
|
|
|
321
|
+
**Teto de turnos:** `maxTurns` por persona no `agents.json` (sem o campo, 20; implementadores 40), anunciado ao sub-agent no corpo da persona. O corte é cego: a cláusula 5 manda `wip` por marco e, a ~70% do teto, relatório `PARCIAL` sem score (`report-incomplete`): retome o agente, não carimbe DEGRADADO.
|
|
322
|
+
|
|
319
323
|
Sub-agent deve ser autossuficiente com o que você passou — se precisar de mais contexto, a tarefa não estava bem especificada; refine antes de despachar.
|
|
320
324
|
|
|
321
325
|
**Quando NÃO despachar:** tarefas curtas onde o overhead do dispatch custa mais que a economia de contexto principal; quando o feedback precisaria ser discutido em tempo real (dispatch é assíncrono); quando a task envolve mudança de arquitetura que o Orchestrator precisa validar antes de executar.
|
|
322
326
|
|
|
323
|
-
**Após
|
|
327
|
+
**Após o retorno:** o auto-score é **triagem, não aprovação** — < 7, re-despache com o feedback (ou corrija inline); a task só avança com `morph-eval` ≥ 9 (§0).
|
|
324
328
|
|
|
325
329
|
> **Anti-padrão:** implementar múltiplas tasks de domínios diferentes inline em sequência ("eu já sei o padrão de cada uma") é o sinal de que a delegação foi ignorada. Feature cruzando ≥2 domínios → o default é despachar especialistas, não o Orchestrator virar faz-tudo.
|
|
326
330
|
|
|
327
331
|
**Dispatch em paralelo (multi-agent):** fluxos **genuinamente independentes** (ex: backend ‖ frontend, convergindo só no fim) → despache múltiplos sub-agents **ao mesmo tempo**, um por fluxo, em vez de série. `morph-spec dag {feature}` analisa o DAG (`dependencies`/`group`), **pondera o esforço** (raízes/fluxos só de tasks S não ganham dispatch próprio) e imprime a decisão; regras e cap de agentes simultâneos em `morph-apply` ("Análise de DAG"). Colisão de `outputs` entre fluxos → `dag` recomenda `isolation: "worktree"`, cada agente num worktree próprio, orquestrador integra por merge (`morph-apply` §3b-w). Sem colisão: working tree compartilhada, verificação única pós-batch.
|
|
328
332
|
|
|
329
|
-
O padrão de dispatch e o template de prompt para sub-agents estão detalhados em `morph-implement` §4.
|
|
330
|
-
|
|
331
333
|
---
|
|
332
334
|
|
|
333
335
|
## 7. Skills disponíveis
|
|
334
336
|
|
|
335
|
-
Skills são documentos de instrução que você invoca sob demanda.
|
|
337
|
+
Skills são documentos de instrução que você invoca sob demanda.
|
|
336
338
|
Não invoque uma skill fora do momento indicado.
|
|
337
339
|
|
|
338
340
|
Path de cada skill: `.claude/skills/{nome}/SKILL.md`.
|
|
339
341
|
|
|
340
342
|
| Skill | Quando invocar |
|
|
341
|
-
|
|
343
|
+
|---|---|
|
|
342
344
|
| **morph-proposal** | **Orquestrador**: brainstorming → Gate 1 → plan → Gate 2 (entry point) |
|
|
343
345
|
| **morph-apply** | **Orquestrador**: DAG-adaptive implement loop → review → Gate 3 |
|
|
344
346
|
| morph-brainstorming | Invocada pela morph-proposal — entendimento de negócio + stack-scan |
|
|
@@ -350,6 +352,7 @@ Path de cada skill: `.claude/skills/{nome}/SKILL.md`.
|
|
|
350
352
|
| morph-review | Invocada pela morph-apply — produz review-report + Gate 3 |
|
|
351
353
|
| morph-hotfix | **Orquestrador**: ADW cirúrgico de hotfix (scout → fix → gate humano → ship) |
|
|
352
354
|
| morph-standards | Quando precisar localizar ou aplicar um standard específico |
|
|
355
|
+
| morph-media | Produzir imagem/vídeo por IA — modelo, prompt versionado, custo, gate humano entre candidatas |
|
|
353
356
|
|
|
354
357
|
Slash commands (`/morph-proposal`, `/morph-apply`, `/morph-hotfix` — linhas acima) são atalhos que invocam a `Skill()` correspondente; o agente pode chamá-la direto quando o usuário pede "use /morph-proposal".
|
|
355
358
|
|