@polymorphism-tech/morph-spec 8.35.1 → 8.36.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 +9 -7
- package/README.md +2 -2
- package/bin/morph-spec.js +2 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +24 -23
- package/framework/docs/CLI.md +184 -10
- package/framework/evals/proof-rubrics/eval-de-modelo.md +16 -0
- package/framework/evals/rubrics/condensed.md +3 -1
- package/framework/evals/rubrics/test-coverage.md +8 -0
- package/framework/hooks/shared/phase-utils.js +1 -0
- package/framework/phases.json +1 -1
- package/framework/rules/morph-workflow.md +1 -1
- package/framework/schemas/config.schema.json +7 -0
- 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 +21 -10
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- 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 +20 -3
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +41 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +11 -2
- 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 +1 -1
- package/framework/standards/STANDARDS.json +2 -2
- package/framework/standards/ai-agents/prompt-sources.md +4 -3
- package/framework/standards/ai-agents/testing-ai.md +14 -13
- package/framework/templates/dotnet/ai-kit/README.md +14 -8
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +11 -3
- package/package.json +1 -1
- package/src/commands/project/doctor.js +51 -2
- package/src/commands/tasks/dag.js +13 -1
- package/src/commands/validation/verify.js +47 -18
- package/src/core/paths/output-schema.js +7 -0
- package/src/lib/detectors/index.js +225 -217
- package/src/lib/risk-detector.js +38 -18
- package/src/lib/specs/done-criteria-check.js +200 -0
- package/src/lib/specs/usecase-check.js +129 -5
- package/src/lib/tasks/dotnet-test-introspection.js +517 -0
- package/src/lib/tasks/test-nodes.js +190 -20
- package/src/lib/tasks/test-runner.js +122 -2
- package/src/lib/tasks/test-scope.js +567 -0
- package/src/lib/validators/traceability-check.js +9 -2
- package/src/lib/validators/verify-runner.js +150 -18
- package/src/lib/worktree/docker-audit.js +5 -1
|
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.36.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-implement
|
|
@@ -116,7 +116,8 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
116
116
|
recuperável). O auto-score é **triagem, não aprovação** — se < 7, re-despache com o feedback
|
|
117
117
|
específico (ou corrija inline) antes de prosseguir. Em qualquer caso, a task só avança quando
|
|
118
118
|
`morph-eval` der score ≥ 9 (barra única do MORPH.md §0). Integre o relatório ao recap.md e vá
|
|
119
|
-
para o passo 7
|
|
119
|
+
para o passo 7 — se a task deve evidência de runtime (`evidencias/` em `outputs`), confira o
|
|
120
|
+
arquivo como manda o passo 5 antes do `done`.
|
|
120
121
|
|
|
121
122
|
**Se não — siga para o passo 5.**
|
|
122
123
|
|
|
@@ -124,6 +125,20 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
124
125
|
Se encontrar ambiguidade de escopo não coberta pelo mandate, registre no decisions.md
|
|
125
126
|
e resolva da forma mais conservadora — não pause para perguntar, a menos que seja bloqueante.
|
|
126
127
|
|
|
128
|
+
**Task com `3-implement/evidencias/{ID}.md` em `outputs`** (critério sobre saída de LLM —
|
|
129
|
+
`morph-plan` §5): rode o eval ANTES de marcar `done` — e só o eval. Com nó `runner: "evals"`
|
|
130
|
+
declarado, `morph-spec verify {feature} --filter {id-do-nó-evals} --skip-e2e --no-persist`:
|
|
131
|
+
escopo de feature porque no de task o nó `evals` é pulado; `--filter` só estreita o nó de testes
|
|
132
|
+
(build e validadores rodam; o e2e subiria o stack, daí `--skip-e2e`); `--no-persist` garante que
|
|
133
|
+
esta execução nunca escreve no `feature.json` — é sonda, não prova para gate nenhum.
|
|
134
|
+
É a única exceção ao passo 9. Sem nó `evals`, execute o eval direto e registre
|
|
135
|
+
"nenhum nó — execução manual". Grave o arquivo com: o critério (literal); o id do nó e o
|
|
136
|
+
cenário (ou aquele rótulo); o modelo efetivo; a versão ou o hash do prompt, nunca o prompt cru;
|
|
137
|
+
entrada e saída sem dado pessoal (`ai-agents-testing-ai`, "Captura do que foi ao provedor"); o
|
|
138
|
+
veredito com a razão. Não é o `reports/{ID}.md`: aquele diz o que o sub-agente fez; este prova,
|
|
139
|
+
rodando, que a saída do modelo atende ao critério. Vale também para task despachada — sem o
|
|
140
|
+
arquivo, ela não vai para `done` (passo 8).
|
|
141
|
+
|
|
127
142
|
6. Ao concluir a tarefa, **append** um bloco a `recap.md` em `.morph/features/{feature}/3-implement/`
|
|
128
143
|
usando este template **obrigatório** (campos não-opcionais — sem standard, a entrada não conta):
|
|
129
144
|
|
|
@@ -197,12 +212,14 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
197
212
|
> tasks.json + recap.md + `taskScores` do feature.json. **NÃO escreva o trace
|
|
198
213
|
> manualmente** — a escrita manual foi removida da cerimônia por task.
|
|
199
214
|
|
|
200
|
-
9. Quando invocada por `morph-apply`, retornar o controle à skill orquestradora — ela decide qual task vem em seguida via `getReadyTasks(data)`. Não rode a suíte de testes inteira do projeto por conta própria ao fim da task quando invocada pela `morph-apply` — a verificação determinística (`morph-spec verify {feature} {task}
|
|
215
|
+
9. Quando invocada por `morph-apply`, retornar o controle à skill orquestradora — ela decide qual task vem em seguida via `getReadyTasks(data)`. Não rode a suíte de testes inteira do projeto por conta própria ao fim da task quando invocada pela `morph-apply` — a verificação determinística (`morph-spec verify {feature} {task}`) é do orquestrador, logo após seu retorno (ver morph-apply §3a). **Única exceção:** a execução de evidência do passo 5 — só o nó `evals`, filtrado, com `--skip-e2e`, nunca persistida (`--no-persist`); ela não é a verificação da task e não substitui a do orquestrador. **A divisão:** no loop por task o recorte é **automático** — o `group` da task escolhe as suítes e os arquivos de teste dos `outputs` (passo 8) estreitam o comando dentro de cada uma (o relógio de uma task não paga a suíte inteira); `--filter {ids}` só força a escolha de suítes (um nó não casado aparece como `skip` nomeando o filtro, e um filtro que não casa nada é erro de uso, exit 2) e `--full` desliga o recorte. A suíte **inteira** roda no Gate 3 e no CI, sem filtro — `verify {feature}` nunca recorta. A suíte completa de um projeto grande não cabe numa invocação de Bash do harness — rode em background ou por partes, nunca "suba o limite" (não há constante de timeout global no CLI). Detalhe e forma de `project.tests[]`: `.morph/framework/CLI.md`, seção "Suítes de teste". Quando invocada manualmente, verificar se há próxima task ready e iniciá-la imediatamente aplicando esta skill desde o passo 1 (auto-advance). Pause apenas se encontrar ambiguidade bloqueante não coberta pelo mandate.
|
|
201
216
|
|
|
202
217
|
## Output esperado
|
|
203
218
|
- Código implementado conforme o standard consultado (direto ou via sub-agent)
|
|
204
219
|
- Quando despachada: `.morph/features/{feature}/3-implement/reports/{taskId}.md` — o relatório
|
|
205
220
|
autoritativo do sub-agent, canal durável (passo 4)
|
|
221
|
+
- Na task com `3-implement/evidencias/{taskId}.md` em `outputs`: esse arquivo — a evidência de
|
|
222
|
+
runtime do eval (passo 5), distinta do relatório do sub-agent em `reports/`
|
|
206
223
|
- `.morph/features/{feature}/3-implement/recap.md` atualizado com entrada da task concluída
|
|
207
224
|
- `.morph/features/{feature}/2-plan/tasks.json` com status da task atualizado para `done`
|
|
208
225
|
- `.morph/features/{feature}/2-plan/decisions.md` atualizado se houver decisão não trivial
|
|
@@ -4,7 +4,7 @@ description: Conduz a AUTORIA de mídia gerada por IA — escolher o modelo pelo
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: quando a tarefa é PRODUZIR imagem ou vídeo por IA (catálogo, marketing, asset de LP) e alguém precisa decidir modelo, prompt e aprovação
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.36.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- dotnet
|
|
10
10
|
- nextjs
|
|
@@ -4,7 +4,7 @@ description: Guia a produção dos artefatos de planejamento de uma feature (use
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar a fase plan de uma feature, após o Gate 1 (proposal aprovado)
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.36.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -98,6 +98,27 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
98
98
|
|
|
99
99
|
**A regra que sustenta a camada inteira: pós-condição precisa ser observável em teste.** "O sistema fica consistente" não passa. "A resposta contém todos os veterinários não arquivados" passa. Antes de escrever cada pós-condição, pergunte: *que asserção eu escreveria para verificar isto?* Se não há resposta, a pós-condição está mal formulada — reescreva ou descarte. Sem esse filtro a camada vira prosa e não chega ao `doneCriteria`.
|
|
100
100
|
|
|
101
|
+
**Artefato não é comportamento.** Pós-condição que cita arquivo, pasta, `grep`, "contém a
|
|
102
|
+
string" ou "o binário" descreve o TEXTO do repositório, não o que o sistema faz — e chega
|
|
103
|
+
literal ao `doneCriteria`, onde vira um teste que lê arquivo. Duas saídas, nesta ordem:
|
|
104
|
+
1. **Reescreva como efeito observável em runtime** — o que o sistema faz de diferente quando
|
|
105
|
+
o texto está errado ("publicar tenant com `criterion_overrides` é recusado, nomeando a
|
|
106
|
+
chave", não "nenhum arquivo em `tenants/**` contém a chave").
|
|
107
|
+
2. **Se o artefato É o entregável** (standard, faxina sem efeito de runtime), justifique
|
|
108
|
+
**na linha logo abaixo** da pós-condição:
|
|
109
|
+
|
|
110
|
+
```markdown
|
|
111
|
+
- **P4** — Nenhum arquivo em `tenants/**` contém a chave `criterion_overrides`
|
|
112
|
+
> [!note] Por que artefato: a chave saiu do contrato; nenhum leitor de runtime a consulta.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Nunca na mesma linha: o texto da pós-condição viaja literal ao `doneCriteria` e a
|
|
116
|
+
justificativa iria junto. Task sem `usecase` justifica no próprio `doneCriteria`
|
|
117
|
+
(`… — Por que artefato: …`). O `morph-spec dag` aponta o vocabulário sem justificativa.
|
|
118
|
+
|
|
119
|
+
Pós-condição sobre o que um **modelo** responde ("o texto pode nomear a pior campanha") é
|
|
120
|
+
observável — em runtime, nunca no texto do prompt. O passo 5 diz que forma a task toma.
|
|
121
|
+
|
|
101
122
|
**Identificador sequencial, reservado — nunca leia o disco à mão.** Rode `morph-spec usecase reserve --count {n} --feature {feature}` (`n` = quantos UCs esta feature vai criar; pode reservar de novo se descobrir mais no meio do plano). Um scan manual do maior `UC-{nn}` só enxerga a ÁRVORE CORRENTE — cada worktree tem a própria cópia de `.morph/features/`, então dois worktrees planejando ao mesmo tempo calculam o mesmo maior número e mintam o MESMO `UC-{nn}` sem saber um do outro; na promoção do `finish`, o segundo a fechar sobrescreve por ID e **apaga o use case do primeiro** — já causou perda de dados em produção. `usecase reserve` aloca atomicamente a partir de um contador compartilhado em `<git-common-dir>/morph-spec/uc-registry.json` (visível a todo worktree do repo, mesmo mecanismo do `claim`/fleet), semeado do maior número já visto no acervo + nas features da árvore corrente + no próprio contador — nunca regride. Fora de um repositório git, cai para o scan local antigo e avisa que o paralelismo entre worktrees não está protegido.
|
|
102
123
|
|
|
103
124
|
**Seções obrigatórias:** `Pré-condições` (o que já é verdade antes), `Cenário principal` (o caminho feliz, numerado, **curto** — não é o lugar para exaustão de casos) e `Pós-condições` (**P1**, **P2**… — o que passa a ser verdade, cada uma testável; é a seção que viaja **literal** ao `doneCriteria`, ver abaixo).
|
|
@@ -232,12 +253,28 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
232
253
|
|
|
233
254
|
**O `doneCriteria` carrega a pós-condição LITERAL.** Este é o elo que amarra a cadeia inteira: a pós-condição escrita em linguagem de negócio chega **sem tradução** ao critério que o `morph-spec verify` e o `morph-eval` cobram. Copie o texto da pós-condição, prefixado pelo seu rótulo (`"P2 — Cada veterinário traz..."`). Reescrever com outras palavras aqui é exatamente o ponto em que o requisito se perde hoje.
|
|
234
255
|
|
|
235
|
-
**`morph-spec dag {feature}` cobra o encanamento.** Ele lê os use cases junto com o `tasks.json` e reporta: referência a um `UC-{nn}` inexistente (**erro** — trava), pós-condição que nenhuma task cita (aviso) e task que referencia um UC mas parafraseou em vez de copiar (aviso). A comparação normaliza caixa e espaço, então prefixar o rótulo não quebra o casamento — mas a redação e os acentos precisam bater.
|
|
256
|
+
**`morph-spec dag {feature}` cobra o encanamento.** Ele lê os use cases junto com o `tasks.json` e reporta: referência a um `UC-{nn}` inexistente (**erro** — trava), pós-condição que nenhuma task cita (aviso) e task que referencia um UC mas parafraseou em vez de copiar (aviso). A comparação normaliza caixa e espaço, então prefixar o rótulo não quebra o casamento — mas a redação e os acentos precisam bater. E um quarto achado, léxico e também só aviso: `doneCriteria` com vocabulário de artefato (`grep`, "contém a string", "o binário", "nenhum arquivo", "arquivo/pasta/diretório em", glob `**/`) sem `Por que artefato:` (passo 2). O `dag` aponta o vocabulário; se a pós-condição é observável em teste continua sendo seu no Gate 2 e do evaluator.
|
|
236
257
|
|
|
237
258
|
Cada pós-condição de cada use case deve estar coberta por **pelo menos uma** task. Antes de fechar o `tasks.json`, percorra os `UC-*.md` e confira: sobrou pós-condição sem task? Ou o plano está incompleto, ou a pós-condição não era necessária — resolva antes do Gate 2, não depois.
|
|
238
259
|
|
|
239
260
|
**Preencha `outputs` (paths planejados) em cada task** — o `morph-spec dag` usa os outputs para detectar colisão de arquivos entre fluxos paralelos e recomendar `isolation: "worktree"`. Task sem `outputs` entra como footprint desconhecido (não força isolamento, mas o dag não consegue protegê-la de colisão).
|
|
240
261
|
|
|
262
|
+
**Critério sobre saída de LLM vira eval de runtime — nunca "suíte estrutural".** Duas etapas,
|
|
263
|
+
como na `morph-review`: (1) **filtro mecânico** — o `mandate.md` cita ao menos um standard
|
|
264
|
+
`ai-agents`; senão, a regra não se aplica. (2) **determinação sua, por task** — o
|
|
265
|
+
`doneCriteria` julga o que o modelo *responde* (conteúdo, tom, escolha)? Se sim:
|
|
266
|
+
- a prova é um **eval de runtime**; a evidência (entrada, modelo, saída, veredito) vai em
|
|
267
|
+
`.morph/features/{feature}/3-implement/evidencias/{taskId}.md`, e esse path entra em
|
|
268
|
+
`outputs` — é por ele que implementador e avaliador sabem que ela é devida. Com nó
|
|
269
|
+
`runner: "evals"` (standard `ai-agents-evals-with-cache`), o cenário mora no dataset do nó
|
|
270
|
+
e a evidência aponta para ele;
|
|
271
|
+
- **no máximo 1 teste de estrutura** da fonte do prompt (chaves que o consumidor lê, ausência
|
|
272
|
+
da instrução revogada) — nunca vocabulário do que o modelo deveria dizer;
|
|
273
|
+
- escreva as duas coisas na `description` da task: é ela que chega ao sub-agente.
|
|
274
|
+
|
|
275
|
+
Medido no acervo: 12 suítes, 106 testes de `Contains` sobre migrations de prompt — todos sobre
|
|
276
|
+
versões já superadas, verdes com qualquer resposta do modelo.
|
|
277
|
+
|
|
241
278
|
**5c. Cross-check do `1-design/` — todo aviso de capacidade tem destino.** Se o diretório existe,
|
|
242
279
|
rode antes de fechar o array `tasks`:
|
|
243
280
|
|
|
@@ -299,7 +336,7 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
299
336
|
- `.morph/features/{feature}/2-plan/spec.md` — especificação técnica completa
|
|
300
337
|
- `.morph/features/{feature}/2-plan/mandate.md` — contrato de implementação
|
|
301
338
|
- `.morph/features/{feature}/2-plan/reuse-map.md` — veredito REUSAR/ESTENDER/CRIAR por conceito, com justificativa de uma linha em cada CRIAR (passo 1c)
|
|
302
|
-
- `.morph/features/{feature}/2-plan/tasks.json` — task list estruturada válida contra schema, com o `doneCriteria` carregando a pós-condição literal e, por task, `agent`/`standards` carimbados a partir de `agents.json` (passo 5a)
|
|
339
|
+
- `.morph/features/{feature}/2-plan/tasks.json` — task list estruturada válida contra schema, com o `doneCriteria` carregando a pós-condição literal e, por task, `agent`/`standards` carimbados a partir de `agents.json` (passo 5a) e, nas tasks de critério sobre saída de LLM, o path de `3-implement/evidencias/{taskId}.md` em `outputs` (passo 5)
|
|
303
340
|
- Gate 2 registrado aprovado em `.morph/features/{feature}/feature.json` (autoritativo)
|
|
304
341
|
|
|
305
342
|
Ao registrar a aprovação do Gate 2, atualize o frontmatter de cada use case de `status: draft` para `status: approved`. O ciclo fecha na integração: `morph-spec finish` promove os use cases para o acervo permanente `.morph/specs/usecases/` já com `status: implemented`. O status é **descritivo** — quem trava fase é o gate, não este campo.
|
|
@@ -317,5 +354,6 @@ Ao registrar a aprovação do Gate 2, atualize o frontmatter de cada use case de
|
|
|
317
354
|
- `.morph/features/{feature}/0-proposal/proposal.md` — escopo da feature
|
|
318
355
|
- `.morph/features/{feature}/0-proposal/stack-scan.md` — contexto técnico detectado
|
|
319
356
|
- `.morph/framework/schemas/tasks.schema.json` — schema e enums de tasks.json
|
|
357
|
+
- `.morph/framework/standards/ai-agents/evals-with-cache.md` e `.morph/framework/standards/ai-agents/testing-ai.md` — o nó `runner: "evals"` e a fronteira eval × unit; insumos da regra de critério sobre saída de LLM (passo 5)
|
|
320
358
|
- `morph-spec dag {feature}` — valida o tasks.json e imprime a análise de DAG
|
|
321
359
|
- `.morph/framework/MORPH.md` §5 — regras de pausa de gate (trust mode)
|
|
@@ -4,7 +4,7 @@ description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encad
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.36.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-proposal
|
|
@@ -4,7 +4,7 @@ description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar a fase review, após todas as tasks de tasks.json estarem com status `done`
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.36.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -59,7 +59,7 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
59
59
|
que este passo trata — prossiga com o dispatch abaixo.
|
|
60
60
|
|
|
61
61
|
**Despache o avaliador independente** — o score do Gate 3 nunca é auto-atribuído por quem implementou:
|
|
62
|
-
- Rode `morph-spec verify {feature} --json` (escopo feature) e capture o JSON — é a saída real de build+testes+validadores+e2e que você anexa ao prompt do avaliador independente (`morph-evaluator`). **Sem `--filter`, sempre:** o `--filter` é do loop por task, onde o relógio de uma task não paga a suíte inteira; a prova que o avaliador lê tem de cobrir **todos** os nós de `nodes.tests.runs[]` (uma suíte filtrada aparece como `skip` nomeando o filtro — Gate 3 aberto sobre isso é verde sobre código não testado). Se a suíte inteira não couber numa chamada, rode-a em background; não a encolha com `--filter`.
|
|
62
|
+
- Rode `morph-spec verify {feature} --json` (escopo feature) e capture o JSON — é a saída real de build+testes+validadores+e2e que você anexa ao prompt do avaliador independente (`morph-evaluator`). **Sem `--filter`, sempre:** o `--filter` é do loop por task, onde o relógio de uma task não paga a suíte inteira; a prova que o avaliador lê tem de cobrir **todos** os nós de `nodes.tests.runs[]` (uma suíte filtrada aparece como `skip` nomeando o filtro — Gate 3 aberto sobre isso é verde sobre código não testado). Se a suíte inteira não couber numa chamada, rode-a em background; não a encolha com `--filter`. `verify {feature}` (sem task) **nunca recorta** — o recorte automático é só do escopo de task —, e um `verify {feature} --filter …` nem persiste o stamp (`persisted: false`, `reason: 'filtered'`). Rode-o **depois** da última task: o `gate-check {feature} review` pausa quando o último stamp `verification` é de escopo `task:{id}`, porque aquela rodada cobriu só a task.
|
|
63
63
|
- **Confira o nó `e2e` do verify antes de despachar.** Ele carrega o estado do stack:
|
|
64
64
|
- `pass` → a aplicação subiu e está saudável; o avaliador pode navegá-la. **Passe a URL no prompt**
|
|
65
65
|
— cada feature roda no seu bloco de portas, não em `localhost:3000`; pegue de
|
|
@@ -83,6 +83,10 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
83
83
|
recriado não aparece no diff como problema. Junto dele, repasse os achados do validador `reuse`
|
|
84
84
|
do nó `validators` do verify. Sem `reuse-map.md` (plano anterior a este passo), diga isso ao
|
|
85
85
|
avaliador em vez de deixá-lo supor que não havia conceito a reusar.
|
|
86
|
+
- **Anexe as evidências de runtime** (`.morph/features/{feature}/3-implement/evidencias/*.md`),
|
|
87
|
+
quando existirem — insumo, não veredito. São o que as tasks de critério sobre saída de LLM
|
|
88
|
+
gravaram rodando o eval (`morph-plan` §5); não confunda com `3-implement/reports/`, que diz o
|
|
89
|
+
que o sub-agente fez.
|
|
86
90
|
- Gere o **diff da implementação** (`git diff` do range da feature, ou a lista de arquivos do recap.md com seus conteúdos atuais).
|
|
87
91
|
- **Blast radius dos símbolos do diff (se houver grafo).** Para cada símbolo público que o diff tocou, rode `morph-spec graph affected <símbolo> --prod --json` e inclua o resultado no contexto do avaliador. É o que permite perguntar *"o que esta mudança pode ter quebrado FORA do diff"* sem que o avaliador leia o repositório inteiro. **Fallback:** `no-graph`, `not-found` ou `ambiguous` — siga sem o blast radius e **registre a ausência na seção de método do review-report**; o gate não depende disso. **Trate o resultado como possivelmente stale:** o rebuild do hook é assíncrono, então logo depois de um commit o grafo ainda é o anterior. E lembre que em frontend ele responde "quem importa", não "quem chama".
|
|
88
92
|
- Despache o subagent registrado `morph-evaluator` (gerado de `agents.json` — a persona do avaliador é o system prompt dele, com Read/Glob/Grep + Bash somente para verificação + Playwright para navegar, sem Write/Edit), com contexto fresco:
|
|
@@ -90,6 +94,7 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
90
94
|
dispatch Agent (subagent_type: "morph-evaluator") with prompt:
|
|
91
95
|
contexto: mandate.md + spec.md + reuse-map.md + diff da implementação
|
|
92
96
|
+ saída real dos testes + nó e2e do verify (stack no ar? em qual URL?)
|
|
97
|
+
+ 3-implement/evidencias/*.md (quando existir)
|
|
93
98
|
instrução: |
|
|
94
99
|
Você NÃO participou da implementação — avalie o que está no diff,
|
|
95
100
|
contra o mandate. NÃO leia `4-review/evaluator-report.md` neste
|
|
@@ -137,6 +142,10 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
137
142
|
.morph/framework/evals/proof-rubrics/fiacao.md
|
|
138
143
|
.morph/framework/evals/proof-rubrics/guard-cego.md
|
|
139
144
|
.morph/framework/evals/proof-rubrics/eval-de-modelo.md
|
|
145
|
+
Para a eval-de-modelo: se existir
|
|
146
|
+
`.morph/features/{feature}/3-implement/evidencias/`, LEIA antes de pontuar `eval`:
|
|
147
|
+
confronte o veredito com a saída registrada, e degrade o cenário que a
|
|
148
|
+
evidência aponta. Evidência é insumo, não veredito.
|
|
140
149
|
E, SÓ quando a feature chama LLM, mais uma — que impõe teto em
|
|
141
150
|
code-quality, não em test-coverage:
|
|
142
151
|
.morph/framework/evals/proof-rubrics/observabilidade-genai.md
|
|
@@ -4,7 +4,7 @@ description: Varre o codebase existente, detecta stack atual, compara com o stac
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar uma feature em projeto brownfield (codebase já existente)
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.36.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-stack-scan
|
|
@@ -4,7 +4,7 @@ description: Localiza um standard específico via busca server-side (morph-spec
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: quando precisar consultar um standard específico durante a implementação
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.36.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|
|
@@ -4,7 +4,7 @@ description: Conduz a fase opcional de UI/UX — design system, mockups, compone
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao tomar o ramo uiux (morph-spec advance --design), depois do Gate 1 e antes do morph-plan
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.36.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-uiux
|
|
@@ -941,7 +941,7 @@
|
|
|
941
941
|
"tenant": "#fonte-2-yaml-por-tenant",
|
|
942
942
|
"reconciliacao": "#reconciliação"
|
|
943
943
|
},
|
|
944
|
-
"digest": "WHERE an agent prompt lives, and how to prove which version actually ran. Two legitimate sources, and the one-sentence rule that picks between them: if the text varies per customer, the prompt is not a record — it is the OUTPUT OF A COMPOSITION, and what you version is the source. (1) Versioned table agent_prompts (agent_key + version + is_active, INSERT + flip, never UPDATE, unique partial index, IMemoryCache with a short TTL) for single-tenant. (2) Repo YAML per tenant + a product frame with named holes -> a deterministic composer -> a rendered spec published to a jsonb column, for product x tenant; editing prompts then costs a repo cycle, which is a product trade-off, not a free upgrade. RECONCILIATION is the section that was missing: versioning and publishing do NOT prove the active version ran (measured: v7/v8 ignored in 6 of 9 executions). The execution record must carry the EFFECTIVE version, plus the hash of the rendered blob on the composed path; the two field-proven techniques are byte-identity equivalence between source and published blob, and prompt-as-text
|
|
944
|
+
"digest": "WHERE an agent prompt lives, and how to prove which version actually ran. Two legitimate sources, and the one-sentence rule that picks between them: if the text varies per customer, the prompt is not a record — it is the OUTPUT OF A COMPOSITION, and what you version is the source. (1) Versioned table agent_prompts (agent_key + version + is_active, INSERT + flip, never UPDATE, unique partial index, IMemoryCache with a short TTL) for single-tenant. (2) Repo YAML per tenant + a product frame with named holes -> a deterministic composer -> a rendered spec published to a jsonb column, for product x tenant; editing prompts then costs a repo cycle, which is a product trade-off, not a free upgrade. RECONCILIATION is the section that was missing: versioning and publishing do NOT prove the active version ran (measured: v7/v8 ignored in 6 of 9 executions). The execution record must carry the EFFECTIVE version, plus the hash of the rendered blob on the composed path; the two field-proven techniques are byte-identity equivalence between source and published blob, and a single structural prompt-as-text test asserting the ABSENCE of a revoked instruction.",
|
|
945
945
|
"verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08). **Verificação documental**, sem cláusula `provado por` — o ai-kit não traz loader de prompt, e os dois mecanismos descritos aqui são destilados de três produtos em produção. As **quatro** superfícies de API que este standard cita foram medidas por reflexão, e vivem em **três assemblies diferentes** — o carimbo diz onde remedir, então o nome importa: `IChatClient.AsAIAgent(instructions:, name:)` é `Microsoft.Extensions.AI.ChatClientExtensions`, em **`Microsoft.Agents.AI` 1.20.0** (medida em 2026-09-08); `AIAgent.RunAsync` (as oito sobrecargas) e `AIAgent.CreateSessionAsync` são de `Microsoft.Agents.AI.AIAgent`, na assembly **`Microsoft.Agents.AI.Abstractions` 1.20.0** — **remedidas** em 2026-09-09, porque a leitura de 2026-09-08 estava errada no *quê*, e a de 2026-09-09 estava errada no *onde* (as duas retificações estão no callout abaixo); e `builder.AddAIAgent` (§Keyed singleton) é `Microsoft.Agents.AI.Hosting.HostApplicationBuilderAgentExtensions` — cinco sobrecargas, na assembly **`Microsoft.Agents.AI.Hosting` 1.20.0.0**, que vem do pacote **`Microsoft.Agents.AI.Hosting` `1.20.0-preview.260831.1`**: ele é **prerelease** e está **FORA do `ai-pin.json`** (apurado em `scripts/api-probe/companions.json`, 2026-09-08) — quem adotar esta seção registra a dependência prerelease em `decisions.md`, como manda `ai-agents-production`. **Duas ressalvas de escopo**, porque um carimbo que não as diz promete mais do que mediu: `Microsoft.Agents.AI.Abstractions` e `Microsoft.Extensions.AI.Abstractions` são nomes de **assembly** (elas viajam dentro dos pacotes `Microsoft.Agents.AI` e `Microsoft.Extensions.AI`), não ids de pacote; e `IMemoryCache`/`DbContext`, usados no padrão de cache e na §Keyed singleton, são superfícies de ASP.NET Core e EF Core — **fora do pin, não medidas aqui, verificação documental**.",
|
|
946
946
|
"provedBy": []
|
|
947
947
|
},
|
|
@@ -1210,7 +1210,7 @@
|
|
|
1210
1210
|
"golden": "#golden-com-parcimônia",
|
|
1211
1211
|
"checklist": "#checklist-verifiable-by-morph-eval"
|
|
1212
1212
|
},
|
|
1213
|
-
"digest": "Only what is agent-specific — backend-dotnet-testing keeps xUnit, FakeTimeProvider and isolation. The single point where code touches a provider is an IChatClient, so everything follows from replacing it: a SCRIPTED and DUMB fake (a queue of hand-written responses, capture of messages/options/call count) that THROWS when the queue runs out — a silent empty response makes a test pass by accident. Two independent products converged on this shape; the kit compiles the third. Run the REAL function-invocation loop on top of the fake: tool choice, arguments, guard and ceiling are only observable through the loop. The seam keeps classes sealed (a one-operation interface, or an internal constructor taking alias -> IChatClient). ZERO real provider calls inside dotnet test, verified by a sweep of tests/ for API keys, environment reads and HTTP clients — and the standard records the price of leaving the real harness outside the suite: 11 scenarios that were never run end to end. Eval means DETERMINISTIC eval over the full real path with the model scripted; a hard gate only over deterministic properties. Raw prompt only in dev/harness with opt-in, hash in production. Golden proves FORM, not content (counter-example: 13.6 KB frozen to prove two lines of concatenation, deliberately deleted).",
|
|
1213
|
+
"digest": "Only what is agent-specific — backend-dotnet-testing keeps xUnit, FakeTimeProvider and isolation. The single point where code touches a provider is an IChatClient, so everything follows from replacing it: a SCRIPTED and DUMB fake (a queue of hand-written responses, capture of messages/options/call count) that THROWS when the queue runs out — a silent empty response makes a test pass by accident. Two independent products converged on this shape; the kit compiles the third. Run the REAL function-invocation loop on top of the fake: tool choice, arguments, guard and ceiling are only observable through the loop. The seam keeps classes sealed (a one-operation interface, or an internal constructor taking alias -> IChatClient). ZERO real provider calls inside dotnet test, verified by a sweep of tests/ for API keys, environment reads and HTTP clients — and the standard records the price of leaving the real harness outside the suite: 11 scenarios that were never run end to end. Eval means DETERMINISTIC eval over the full real path with the model scripted; a hard gate only over deterministic properties. Raw prompt only in dev/harness with opt-in, hash in production. Golden proves FORM, not content (counter-example: 13.6 KB frozen to prove two lines of concatenation, deliberately deleted). A versioned prompt gets AT MOST ONE structural test (keys the consumer reads, absence of a revoked instruction); what the model answers is proven at runtime by evals-with-cache, never by Contains over the prompt source.",
|
|
1214
1214
|
"verifiedAgainst": "Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs` e `.../src/Morph.AiKit/Providers/IProviderClientFactory.cs` — o fake roteirizado e a costura que dispensa rede são o que o kit compila e exercita. Harness de turno completo e evals com cache de resposta são **verificação documental** — o ai-kit não os traz.",
|
|
1215
1215
|
"provedBy": [
|
|
1216
1216
|
"templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs",
|
|
@@ -317,7 +317,7 @@ deles pode estar servindo algo antigo.
|
|
|
317
317
|
| Técnica | O que prova | Onde vive |
|
|
318
318
|
|---|---|---|
|
|
319
319
|
| **Equivalência por byte-identidade** entre a composição da fonte e o blob publicado | Que a publicação não divergiu da fonte versionada | Teste, sobre o compositor puro |
|
|
320
|
-
| **Prompt como texto**:
|
|
320
|
+
| **Prompt como texto**: no máximo **um** teste de estrutura (chaves + ausência de instrução revogada), ver `ai-agents-testing-ai` | Que as chaves batem, e que a instrução revogada não voltou — nunca presença de conteúdo que o modelo "deveria" seguir | Teste, sobre a migration ou o YAML |
|
|
321
321
|
|
|
322
322
|
A segunda asserção — **ausência** — é a que ninguém escreve e a que mais pega regressão: uma
|
|
323
323
|
instrução removida do prompt em produção continua viva no registro antigo, e o rollback a traz de
|
|
@@ -344,7 +344,7 @@ rastreabilidade (hash em span, prompt cru só em harness) está em `ai-agents-ob
|
|
|
344
344
|
| Buraco de política que some em silêncio quando o tenant não o preenche | O prompt publicado fica com um pedaço a menos e ninguém sabe | Buraco não preenchido é erro de publicação |
|
|
345
345
|
| Registrar a execução sem a versão efetiva do prompt | "v7 e v8 ignoradas em 6 de 9 execuções", e sem como descobrir | Grave a versão usada naquele turno |
|
|
346
346
|
| No caminho composto, gravar só a versão e não o hash do blob | Versão não identifica um texto derivado de duas fontes | Grave versão **e** hash |
|
|
347
|
-
|
|
|
347
|
+
| Mais de um teste de estrutura por fonte de prompt, ou teste de presença de conteúdo | Congela a versão, não o comportamento do modelo | No máximo **um** teste: chaves + **ausência** de instrução revogada (a que o rollback traz de volta) |
|
|
348
348
|
|
|
349
349
|
---
|
|
350
350
|
|
|
@@ -367,7 +367,8 @@ rastreabilidade (hash em span, prompt cru só em harness) está em `ai-agents-ob
|
|
|
367
367
|
- [ ] O registro de execução carrega a **versão efetiva** do prompt daquele turno
|
|
368
368
|
- [ ] No caminho composto, carrega também o **hash do blob renderizado**
|
|
369
369
|
- [ ] Existe teste de equivalência por byte-identidade entre a fonte e o blob publicado
|
|
370
|
-
- [ ]
|
|
370
|
+
- [ ] Cada fonte de prompt tem **no máximo 1** teste de estrutura (chaves + ausência de instrução
|
|
371
|
+
revogada), ver `ai-agents-testing-ai`
|
|
371
372
|
|
|
372
373
|
---
|
|
373
374
|
|
|
@@ -168,18 +168,16 @@ referencia, e conferir a **assinatura** do membro que já quebrou uma vez. É ba
|
|
|
168
168
|
pega a classe de defeito que custou uma chamada paga sem resposta. Ponte com
|
|
169
169
|
`ai-agents-llm-runtime-defaults`, seção de pin de SDK.
|
|
170
170
|
|
|
171
|
-
### Prompt versionado
|
|
171
|
+
### Prompt versionado: no máximo um teste de estrutura
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
Precedente de campo: treze arquivos e cerca de 2.500 linhas fazendo exatamente isso. Ponte com
|
|
182
|
-
`ai-agents-prompt-sources`, seção de reconciliação.
|
|
173
|
+
O que se quer provar de um prompt é o que o **modelo faz** com ele — e isso só se observa em
|
|
174
|
+
runtime: eval com cache (`ai-agents-evals-with-cache`), com a evidência da execução registrada.
|
|
175
|
+
Sobre a fonte (migration, YAML), **no máximo um** teste por prompt, e só de forma: as
|
|
176
|
+
**chaves/placeholders** que o consumidor lê e a **ausência** de instrução revogada (a regressão
|
|
177
|
+
que o rollback traz de volta — `ai-agents-prompt-sources`, reconciliação). Nunca presença de
|
|
178
|
+
frases que o modelo "deveria" seguir. Contraexemplo medido: 12 suítes, 106 `Contains` sobre a
|
|
179
|
+
migration do prompt, todos sobre versões superadas e verdes com qualquer resposta do modelo — o
|
|
180
|
+
mesmo padrão que este standard já contou como precedente ("treze arquivos, ~2.500 linhas").
|
|
183
181
|
|
|
184
182
|
---
|
|
185
183
|
|
|
@@ -196,6 +194,7 @@ Precedente de campo: treze arquivos e cerca de 2.500 linhas fazendo exatamente i
|
|
|
196
194
|
| Prompt cru persistido em produção | Vazamento esperando indexação | Hash em produção; cru só em dev/harness com opt-in |
|
|
197
195
|
| Gravar telemetria de turno cancelado | Registro de algo que não aconteceu | Não grave |
|
|
198
196
|
| Golden de blob de prompt | 13,6 KB congelados para provar duas linhas | Asserte as duas linhas |
|
|
197
|
+
| Suíte estrutural sobre o texto do prompt | Congela a versão, não o comportamento | 1 teste de estrutura + eval de runtime |
|
|
199
198
|
| Abrir a classe (`sealed` removido) para poder testar | Costura de teste virando decisão de arquitetura | Interface de uma operação, ou construtor `internal` |
|
|
200
199
|
| Fábrica que guarda e reaproveita clientes por fora | Duas caches da mesma coisa; cliente vazado entre testes | Ownership decidido num lugar |
|
|
201
200
|
|
|
@@ -215,7 +214,8 @@ Precedente de campo: treze arquivos e cerca de 2.500 linhas fazendo exatamente i
|
|
|
215
214
|
- [ ] Turno cancelado não grava telemetria
|
|
216
215
|
- [ ] Nenhum golden de blob de prompt; goldens existentes provam **forma**
|
|
217
216
|
- [ ] Existe teste de compatibilidade binária do par SDK × ponte
|
|
218
|
-
- [ ]
|
|
217
|
+
- [ ] Cada fonte de prompt tem **no máximo 1** teste de estrutura (chaves + ausência de instrução
|
|
218
|
+
revogada); critério sobre saída do modelo é eval de runtime
|
|
219
219
|
- [ ] Nenhuma classe deixou de ser `sealed` por causa de teste
|
|
220
220
|
|
|
221
221
|
---
|
|
@@ -230,7 +230,8 @@ Precedente de campo: treze arquivos e cerca de 2.500 linhas fazendo exatamente i
|
|
|
230
230
|
- `ai-agents-conversational-agent-with-phases` — compositor, allow-list e teto: o que o harness exercita
|
|
231
231
|
- `ai-agents-cost-and-budget` — a conta que o harness confere com uso roteirizado
|
|
232
232
|
- `ai-agents-guardrails` — os guards que só o loop real prova
|
|
233
|
+
- `ai-agents-evals-with-cache` — o eval de runtime que prova o que o modelo faz com o prompt
|
|
233
234
|
|
|
234
235
|
---
|
|
235
236
|
|
|
236
|
-
*MORPH-SPEC by Polymorphism Tech — ai-agents/testing-ai.md v1.
|
|
237
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/testing-ai.md v1.1 (2026-09-14)*
|
|
@@ -48,14 +48,20 @@ dotnet test --solution Morph.AiKit.sln -c Release --no-build
|
|
|
48
48
|
> `verify-por-stack`; aqui isto é **registro de fronteira**, não conserto.
|
|
49
49
|
|
|
50
50
|
> **Descoberta parcial não pode passar verde.** O `.csproj` de teste declara
|
|
51
|
-
> `TestingPlatformCommandLineArguments` com o piso `--minimum-expected-tests
|
|
52
|
-
>
|
|
53
|
-
>
|
|
54
|
-
>
|
|
55
|
-
>
|
|
56
|
-
>
|
|
57
|
-
>
|
|
58
|
-
>
|
|
51
|
+
> `TestingPlatformCommandLineArguments` com o piso `--minimum-expected-tests N`
|
|
52
|
+
> (o número vive no `.csproj`): uma execução que rode **menos de N casos, mas
|
|
53
|
+
> pelo menos um,** sai com o código **9**; **zero** casos continua saindo **8**
|
|
54
|
+
> (`ZeroTests`) — o piso não supera esse veredito (medido duas vezes, com piso
|
|
55
|
+
> 275 e com piso 1). Sem piso, uma execução que descobre **1 de 115** imprime
|
|
56
|
+
> *"Aprovado!"* e sai **0** — medido. O piso mora no `.csproj`, e não no passo
|
|
57
|
+
> do workflow, para valer em toda invocação (CI, máquina do dev e o nó `tests`
|
|
58
|
+
> do `morph-spec verify`); por isso nenhum chamador repassa a flag. Ao adicionar
|
|
59
|
+
> testes, suba o piso — `test/framework/ai-kit-ci-packaging.test.js` reprova um
|
|
60
|
+
> piso que ficou para trás da contagem de `[Fact]`/`[Theory]`.
|
|
61
|
+
> **Consequência para o recorte por task:** com este piso, `morph-spec verify
|
|
62
|
+
> {f} {task}` roda o nó deste projeto **inteiro** — `--minimum-expected-tests 1`
|
|
63
|
+
> na linha de comando não sobrescreve o valor do projeto (medido: o recorte
|
|
64
|
+
> verde sai 9 do mesmo jeito) —, então o loop por task não estreita este projeto.
|
|
59
65
|
|
|
60
66
|
> **`dotnet test` precisa rodar COM O CWD DENTRO deste diretório.** O opt-in do
|
|
61
67
|
> Microsoft.Testing.Platform (que `xunit.v3` 4.x usa) vive em `global.json`, e o
|
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
A opcao `minimum-expected-tests N` (dois hifens a frente, como toda opcao da
|
|
17
17
|
plataforma) faz o runner sair com o codigo 9
|
|
18
|
-
(MinimumExpectedTestsPolicyViolation)
|
|
19
|
-
|
|
20
|
-
(ZeroTests)
|
|
18
|
+
(MinimumExpectedTestsPolicyViolation) quando rodaram MENOS de N casos, mas
|
|
19
|
+
pelo menos um: uma execucao verde de menos de N casos sai 9. ZERO casos
|
|
20
|
+
continua saindo 8 (ZeroTests) — o piso NAO supera o veredito 8; a ordem
|
|
21
|
+
medida e ZeroTests antes da politica (duas vezes em 2026-09-14, com piso
|
|
22
|
+
275 e com piso 1; capturas em test/fixtures/test-output/ na fonte do
|
|
23
|
+
morph-spec). Medido em 2026‑09‑08 contra o SDK .NET 10:
|
|
21
24
|
piso 221 → 221 aprovados, exit 0
|
|
22
25
|
piso 9999 → 115 aprovados, exit 9
|
|
23
26
|
|
|
@@ -27,6 +30,11 @@
|
|
|
27
30
|
de workflow. Uma unica verdade: nenhum chamador repassa a flag na linha de
|
|
28
31
|
comando, porque repassa-la duplicaria a opcao.
|
|
29
32
|
|
|
33
|
+
Consequencia para o recorte por task (`morph-spec verify {f} {task}`): com
|
|
34
|
+
este piso, o no de teste deste projeto roda INTEIRO. A mesma opcao com valor
|
|
35
|
+
1 na linha de comando NAO sobrescreve o valor do projeto (medido: o recorte
|
|
36
|
+
verde sai 9 do mesmo jeito), entao o loop por task nao estreita este projeto.
|
|
37
|
+
|
|
30
38
|
O numero e o total de CASOS EXECUTADOS (275), nao de atributos nem de casos
|
|
31
39
|
DESCOBERTOS (276): os [Theory] expandem, e um caso IGNORADO nao conta para o piso — a
|
|
32
40
|
suite descobre 276 e executa 275, porque o semeador da cache exemplar de eval
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join, relative } from 'path';
|
|
1
|
+
import { join, relative, resolve } from 'path';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { platform, homedir } from 'os';
|
|
@@ -20,9 +20,27 @@ import { checkMissingAgents, checkAgentsDrift } from '../../utils/agents-install
|
|
|
20
20
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
21
21
|
import { auditGraph } from '../../lib/graph/graph-health.js';
|
|
22
22
|
import { auditFeatureLifecycle } from '../../core/state/lifecycle-audit.js';
|
|
23
|
-
import { validateTestNodes, morphConfigPath } from '../../lib/tasks/test-nodes.js';
|
|
23
|
+
import { validateTestNodes, morphConfigPath, discoverFrontendPackages } from '../../lib/tasks/test-nodes.js';
|
|
24
|
+
import { samePathCase } from '../../lib/worktree/docker-audit.js';
|
|
24
25
|
|
|
25
26
|
const isWindows = platform() === 'win32';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolved-path equality for a `cwd` string against the project root —
|
|
30
|
+
* `"./src/web"`, `"src/web/"` and (on win32) `"src\\web"` must compare equal
|
|
31
|
+
* to `"src/web"`. Case-insensitive on win32 only, through `samePathCase` of
|
|
32
|
+
* `src/lib/worktree/docker-audit.js` — the one copy of that rule (#87 F5): NTFS
|
|
33
|
+
* itself is case-insensitive, so two spellings that differ only in case are
|
|
34
|
+
* the same directory there and nowhere else.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} root - Project root the cwd is relative to
|
|
37
|
+
* @param {string|null|undefined} cwd
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function normCwd(root, cwd) {
|
|
41
|
+
return samePathCase(resolve(root, String(cwd ?? '.')).replace(/\\/g, '/'));
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
/**
|
|
27
45
|
* Check which required Claude Code plugins are installed.
|
|
28
46
|
* @param {Object} pluginsData - The `plugins` object from installed_plugins.json
|
|
@@ -403,6 +421,37 @@ async function doctorFullCommand(frameworkRoot) {
|
|
|
403
421
|
broken.length > 0
|
|
404
422
|
? broken.map((n) => n.reason ?? `invalid-test-node: ${n.id}`).join('; ')
|
|
405
423
|
: '');
|
|
424
|
+
|
|
425
|
+
// #87 PR B: a vitest/jest package.json declared tests[] never mentions is
|
|
426
|
+
// exactly the shape F30 exploited — a real suite sitting outside the gate.
|
|
427
|
+
// WARN-only, never ✗: the fail-closed barrier is `verify`, this just informs.
|
|
428
|
+
// Compared by RESOLVED path, not raw string: "./src/web", "src/web/" and
|
|
429
|
+
// (win32) "src\\web" all address the same node as "src/web" and must not
|
|
430
|
+
// produce a spurious warning just because the spelling differs.
|
|
431
|
+
const declaredCwds = new Set(declared.map((n) => normCwd(frameworkRoot, n.cwd)));
|
|
432
|
+
const uncoveredPkgs = discoverFrontendPackages(frameworkRoot, testsConfig)
|
|
433
|
+
.filter((pkg) => !declaredCwds.has(normCwd(frameworkRoot, pkg.cwd)));
|
|
434
|
+
if (uncoveredPkgs.length > 0) {
|
|
435
|
+
check(
|
|
436
|
+
`config tests[] cobre package.json de frontend — sem nó: ${uncoveredPkgs.map((p) => `${p.cwd} (${p.runner})`).join(', ')}`,
|
|
437
|
+
false, true,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
} else if (typeof testsConfig?.project?.testCommand === 'string' && testsConfig.project.testCommand.trim()) {
|
|
441
|
+
// Mesma checagem, para o caso testCommand-only: o comando é OPACO (uma
|
|
442
|
+
// string de shell), então cobertura aqui é só "o comando MENCIONA o
|
|
443
|
+
// runner do package.json" — não confirmar isso seria ruído para quem já
|
|
444
|
+
// roda vitest por dentro do testCommand.
|
|
445
|
+
const testCommand = testsConfig.project.testCommand;
|
|
446
|
+
const uncoveredPkgs = discoverFrontendPackages(frameworkRoot, testsConfig)
|
|
447
|
+
.filter((pkg) => !testCommand.includes(pkg.runner));
|
|
448
|
+
if (uncoveredPkgs.length > 0) {
|
|
449
|
+
console.log(chalk.cyan('\n .morph/config/config.json — project.testCommand'));
|
|
450
|
+
check(
|
|
451
|
+
`config testCommand cobre package.json de frontend — sem nó: ${uncoveredPkgs.map((p) => `${p.cwd} (${p.runner})`).join(', ')}`,
|
|
452
|
+
false, true,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
406
455
|
}
|
|
407
456
|
}
|
|
408
457
|
|
|
@@ -5,6 +5,7 @@ import { parseTasksJsonLenient } from '../../lib/tasks/tasks-json-parser.js';
|
|
|
5
5
|
import { planDispatch } from '../../lib/tasks/dispatch-planner.js';
|
|
6
6
|
import { renderMermaidFlowchart } from '../../lib/tasks/mermaid-renderer.js';
|
|
7
7
|
import { checkUseCases } from '../../lib/specs/usecase-check.js';
|
|
8
|
+
import { checkArtifactCriteria } from '../../lib/specs/done-criteria-check.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* `morph-spec dag <feature>` — analyze the tasks.json DAG and print the
|
|
@@ -51,10 +52,16 @@ export async function dagCommand(featureName, options = {}) {
|
|
|
51
52
|
// against a plan that points at a use case nobody can read.
|
|
52
53
|
const ucCheck = checkUseCases(data, join(planDir, 'usecases'));
|
|
53
54
|
|
|
55
|
+
// Artifact-postcondition warning (issue #87). Lexical, not wiring — it
|
|
56
|
+
// runs over ALL tasks even when the feature has no use cases at all
|
|
57
|
+
// (checkUseCases short-circuits above in that case), because a task's
|
|
58
|
+
// own doneCriteria can describe an artifact with no use case in sight.
|
|
59
|
+
const artifactCriteria = checkArtifactCriteria(data, join(planDir, 'usecases'));
|
|
60
|
+
|
|
54
61
|
const plan = planDispatch(data);
|
|
55
62
|
|
|
56
63
|
if (options.json) {
|
|
57
|
-
console.log(JSON.stringify({ ...plan, useCases: ucCheck }, null, 2));
|
|
64
|
+
console.log(JSON.stringify({ ...plan, useCases: ucCheck, artifactCriteria }, null, 2));
|
|
58
65
|
if (ucCheck.errors.length) process.exit(1);
|
|
59
66
|
return;
|
|
60
67
|
}
|
|
@@ -71,6 +78,11 @@ export async function dagCommand(featureName, options = {}) {
|
|
|
71
78
|
console.error(chalk.yellow(`\n⚠ Use cases — ${plural(ucCheck.warnings.length, 'aviso', 'avisos')}`));
|
|
72
79
|
for (const w of ucCheck.warnings) console.error(chalk.yellow(` ${w}`));
|
|
73
80
|
}
|
|
81
|
+
if (artifactCriteria.warnings.length) {
|
|
82
|
+
console.error(chalk.yellow(`\n⚠ Critérios de artefato — ${plural(artifactCriteria.warnings.length, 'aviso', 'avisos')}`));
|
|
83
|
+
for (const w of artifactCriteria.warnings) console.error(chalk.yellow(` ${w}`));
|
|
84
|
+
console.error(chalk.gray(' Teste que lê texto de arquivo não prova comportamento (morph-plan §2, "Por que artefato").'));
|
|
85
|
+
}
|
|
74
86
|
|
|
75
87
|
console.log(chalk.bold(`\n📊 DAG — ${featureName}`));
|
|
76
88
|
console.log('━'.repeat(60));
|