@polymorphism-tech/morph-spec 8.16.0 → 8.17.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/README.md +2 -2
- package/bin/morph-spec.js +11 -0
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +8 -3
- package/framework/MORPH.md +1 -1
- package/framework/evals/rubrics/condensed.md +49 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +9 -0
- package/framework/rules/morph-task-tracking.md +4 -2
- package/framework/schemas/tasks.schema.json +10 -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 +40 -28
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +16 -13
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +26 -25
- package/framework/skills/core/morph-plan/SKILL.md +14 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/package.json +2 -2
- package/src/commands/project/standards.js +220 -0
- package/src/lib/tasks/tasks-json-parser.js +26 -0
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.17.0
|
|
7
7
|
**Requires:** Node.js 22+, Claude Code
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -368,4 +368,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
|
|
|
368
368
|
|
|
369
369
|
---
|
|
370
370
|
|
|
371
|
-
*morph-spec v8.
|
|
371
|
+
*morph-spec v8.17.0 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/bin/morph-spec.js
CHANGED
|
@@ -26,6 +26,7 @@ import { statusCommand } from '../src/commands/project/status.js';
|
|
|
26
26
|
import { worktreeSetupCommand, worktreeLinkCommand, worktreeListCommand } from '../src/commands/project/worktree.js';
|
|
27
27
|
import { fleetCommand } from '../src/commands/project/fleet.js';
|
|
28
28
|
import { claimCommand } from '../src/commands/project/claim.js';
|
|
29
|
+
import { standardsCommand } from '../src/commands/project/standards.js';
|
|
29
30
|
import { reportCheckCommand } from '../src/commands/state/report-check.js';
|
|
30
31
|
|
|
31
32
|
// === Approval gates (human-only) ===
|
|
@@ -381,6 +382,16 @@ program
|
|
|
381
382
|
.option('--json', 'Output as JSON')
|
|
382
383
|
.action((feature, options) => claimCommand(feature, options));
|
|
383
384
|
|
|
385
|
+
// ─── Standards search (server-side, R2 token cut) ─────────────────────────
|
|
386
|
+
program
|
|
387
|
+
.command('standards')
|
|
388
|
+
.description('Search STANDARDS.json server-side (top 3, full digest) instead of reading the whole registry. No --find: list categories + counts')
|
|
389
|
+
.option('--find <query>', 'Search query — matches name/id, tags/aliases, category, digest')
|
|
390
|
+
.option('--category <category>', 'Restrict --find to a single category')
|
|
391
|
+
.option('--tags <tags>', 'Restrict --find to standards carrying any of these comma-separated tags')
|
|
392
|
+
.option('--json', 'Output as JSON')
|
|
393
|
+
.action((options) => standardsCommand(options));
|
|
394
|
+
|
|
384
395
|
// ─── Sub-agent report protocol ────────────────────────────────────────────
|
|
385
396
|
program
|
|
386
397
|
.command('report-check <feature> [task]')
|
package/claude-plugin.json
CHANGED
package/docs/CHEATSHEET.md
CHANGED
package/docs/QUICKSTART.md
CHANGED
package/framework/CLAUDE.md
CHANGED
|
@@ -15,10 +15,10 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
|
|
|
15
15
|
|
|
16
16
|
**NUNCA:**
|
|
17
17
|
- Pular para código sem proposal aprovado (Gate 1) e plano aprovado (Gate 2).
|
|
18
|
-
- Modificar o contrato do plano depois do Gate 2: `spec.md`, `mandate.md`, e os campos de plano de `tasks.json` (`title`, `description`, `dependencies`, `effort`, `doneCriteria`, `group`) estão congelados. **Os use cases (`2-plan/usecases/UC-*.md`) NÃO estão** — são editáveis por design, é assim que se muda o comportamento e se re-roda `implement`; a auditoria fica no git.
|
|
18
|
+
- Modificar o contrato do plano depois do Gate 2: `spec.md`, `mandate.md`, e os campos de plano de `tasks.json` (`title`, `description`, `dependencies`, `effort`, `doneCriteria`, `group`, `agent`, `standards`) estão congelados. **Os use cases (`2-plan/usecases/UC-*.md`) NÃO estão** — são editáveis por design, é assim que se muda o comportamento e se re-roda `implement`; a auditoria fica no git.
|
|
19
19
|
- Escrever critérios de aceite no `proposal.md`. O comportamento verificável mora nos use cases, cuja pós-condição precisa ser **observável em teste** e chega **literal** ao `doneCriteria` da task.
|
|
20
20
|
- Editar `.morph/specs/usecases/` à mão. O acervo só se escreve pela promoção do `finish`, que lê de `2-plan/usecases/` — é ela que carimba `status: implemented` e sobrescreve por ID. Para mudar um UC existente (o caso `bug`), copie-o para o `2-plan/usecases/` da feature e corrija lá.
|
|
21
|
-
- Tocar os campos de contrato do plano em `tasks.json` após o Gate 2 (`title`, `description`, `dependencies`, `effort`, `doneCriteria`, `group`) — o hook `protect-spec-files` faz diff semântico e bloqueia mudanças neles, seja via `Write` ou `Edit`. (Os campos mutáveis `status`/`outputs`/`notes` continuam graváveis por `Write` ou `Edit` — ver SEMPRE.)
|
|
21
|
+
- Tocar os campos de contrato do plano em `tasks.json` após o Gate 2 (`title`, `description`, `dependencies`, `effort`, `doneCriteria`, `group`, `agent`, `standards`) — o hook `protect-spec-files` faz diff semântico e bloqueia mudanças neles, seja via `Write` ou `Edit`. (Os campos mutáveis `status`/`outputs`/`notes` continuam graváveis por `Write` ou `Edit` — ver SEMPRE.)
|
|
22
22
|
- Listar perguntas em texto simples — sempre use a tool `AskUserQuestion` (1-4 perguntas por chamada).
|
|
23
23
|
- Usar `Guid.NewGuid()`, classes não-sealed sem justificativa, exceptions como fluxo de controle, camada de Service/Manager entre handler e dados, repository genérico (`IRepository<T>`/`IUnitOfWork`), ou SQL cru sem justificativa.
|
|
24
24
|
|
|
@@ -29,7 +29,12 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
|
|
|
29
29
|
- Apresentar gates com o template fixo definido em MORPH.md / morph-plan. **A pergunta PRECISA nomear a feature** ("Gate 2 — Plano de {feature} aprovado?"): é por esse nome que o hook `state-sync` atribui a aprovação ao `feature.json` certo, na árvore certa. Sem nome, e havendo mais de uma feature na árvore, ele **recusa a gravar** e avisa — adivinhar já carimbou um Gate 2 na feature errada. Nesse caso aprove com `morph-spec approve <feature> <gate>`.
|
|
30
30
|
- Registrar decisões não triviais em `decisions.md`.
|
|
31
31
|
- Aplicar `morph-eval` após cada tarefa; silêncio em score ≥ 9 é o sinal correto. Score < 9 dispara o loop de correção autônomo (re-implementa, sem pausa), não escala ao humano entre tarefas. No Gate 3, o score composto da feature vem do **avaliador independente** despachado pela `morph-review` (persona `evaluator`, contexto fresco) — nunca de auto-avaliação de quem implementou.
|
|
32
|
-
-
|
|
32
|
+
- **Contrato do relatório final** em todo dispatch de sub-agent:
|
|
33
|
+
1. Grava o relatório em arquivo canônico ANTES de encerrar — `.morph/features/{f}/3-implement/reports/{taskId}.md` (avaliador: `4-review/evaluator-report.md`) — com os 4 itens obrigatórios (o quê / arquivos / problemas / auto-score 0-10). Esse arquivo é o retorno autoritativo, não a mensagem final; nunca chamar `SendMessage`.
|
|
34
|
+
2. Não tocar `tasks.json`/`feature.json`/`state.json` nem o board de sessão — estado é do orquestrador.
|
|
35
|
+
3. Sem build/test da suíte compartilhada em batch paralelo sem isolamento (verificação é do orquestrador, pós-batch).
|
|
36
|
+
|
|
37
|
+
Racional completo: `MORPH.md` §6. Templates curtos: `morph-implement` §4, `morph-apply` §3b. **O orquestrador lê o arquivo ao retomar controle** — nunca confia só na mensagem de retorno da tool.
|
|
33
38
|
|
|
34
39
|
## Slash commands
|
|
35
40
|
|
package/framework/MORPH.md
CHANGED
|
@@ -199,7 +199,7 @@ O índice de standards fica em `framework/standards/STANDARDS.json`. Cada entrad
|
|
|
199
199
|
|
|
200
200
|
**Fluxo de consulta:**
|
|
201
201
|
|
|
202
|
-
1. Antes de iniciar uma tarefa, identifique de 1 a 3 standards relevantes
|
|
202
|
+
1. Antes de iniciar uma tarefa, identifique de 1 a 3 standards relevantes — rode `morph-spec standards --find "<domínio/tecnologia da task>"` (o comando devolve o top 3 como `id · path · digest`, sem você abrir o índice inteiro) em vez de filtrar `id`/`tags` à mão no JSON.
|
|
203
203
|
2. Leia apenas o arquivo `.md` daquele standard específico — o path está no campo `path` do JSON.
|
|
204
204
|
3. Aplique o standard durante a implementação. Se precisar de contexto adicional, leia mais um standard.
|
|
205
205
|
4. Registre no recap.md qual standard foi consultado e como foi aplicado.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Rubrica Condensada — Eval Inline por Task
|
|
2
|
+
|
|
3
|
+
Versão compacta das 4 rubricas completas de `framework/evals/rubrics/`, para o eval
|
|
4
|
+
**inline por task** (`morph-eval` §2, escopo tarefa única). Mesmas dimensões, mesmos
|
|
5
|
+
pesos, mesma régua — só menos texto por banda.
|
|
6
|
+
|
|
7
|
+
> **O Gate 3 NÃO usa esta condensada.** O avaliador independente da `morph-review`
|
|
8
|
+
> (persona `evaluator`, contexto fresco) sempre lê as 4 rubricas completas — a régua
|
|
9
|
+
> do juiz de fase não muda. Esta condensada só corta releitura no loop intra-task.
|
|
10
|
+
|
|
11
|
+
## Verificações mecânicas obrigatórias — auto-reprovação (teto 2)
|
|
12
|
+
|
|
13
|
+
Se o diff toca endpoint minimal API ou fetch cross-camada, independente do resto:
|
|
14
|
+
|
|
15
|
+
- Serviço injetado na lambda do endpoint sem `AddScoped`/`AddSingleton`/`AddTransient` em `Program.cs`.
|
|
16
|
+
- Chamada server-direct e client-side proxy pro mesmo recurso reusando a mesma constante de prefixo.
|
|
17
|
+
|
|
18
|
+
## Dimensões e pesos
|
|
19
|
+
|
|
20
|
+
| Dimensão | Peso |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Arquitetura | 30% |
|
|
23
|
+
| Contratos | 25% |
|
|
24
|
+
| Qualidade de código | 25% |
|
|
25
|
+
| Cobertura de testes | 20% |
|
|
26
|
+
|
|
27
|
+
## Bandas por dimensão
|
|
28
|
+
|
|
29
|
+
| Dimensão | 1-3 | 4-6 | 7-8 | 9-10 |
|
|
30
|
+
|---|---|---|---|---|
|
|
31
|
+
| **Arquitetura** | Ignora a arquitetura do spec.md; sem feature folder; deps circulares; ou violação mecânica acima | Pastas corretas, responsabilidades se misturam; deps cross-feature sem justificativa; domain acoplado a infra | Desvios menores justificados; deps cross-feature via abstração; impurezas de infra pontuais | Feature folder rigoroso; zero deps cross-feature diretas; domain isolado; DI onde exige |
|
|
32
|
+
| **Contratos** | Sem tipos ou `any` generalizado; zero validação; sem tratamento de erro; naming arbitrário | `any`/genérico visível; validação parcial; mix exceptions/Result; naming inconsistente | Tipos bem definidos, `any` raro e justificado; validação na maioria; erro consistente | Zero `any`; validação em todos endpoints; Result pattern/discriminated union; naming consistente |
|
|
33
|
+
| **Qualidade** | Nomes arbitrários; god objects; duplicação massiva; fora do padrão do stack | Nomes genéricos (`data`,`temp`); funções >30 linhas; duplicação perceptível | Nomes claros, raras exceções; bem dimensionado; duplicação mínima | Nomes comunicam intenção; funções ≤20-25 linhas, 1 responsabilidade; zero duplicação/dead code |
|
|
34
|
+
| **Testes** | Sem testes ou sem asserts reais; zero edge case; nomes genéricos | Happy path OK, edge cases parciais; erros só óbvios; testes interdependentes | Happy path completo; maioria dos edge cases; erros nos fluxos principais | Toda pós-condição do `usecase` tem teste; A1/A2 cobertos; edge+erro cobertos; isolados |
|
|
35
|
+
|
|
36
|
+
> Task com `usecase: UC-nn`: pós-condição sem teste **impede a faixa 9-10** em Testes, mesmo com o resto da suíte completo (herdado de `test-coverage.md`).
|
|
37
|
+
|
|
38
|
+
## Score composto
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
score = (arquitetura × 0.30) + (contratos × 0.25) + (qualidade × 0.25) + (testes × 0.20)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Arredonde para uma casa decimal. ≥9 aprova em silêncio; <9 alimenta o loop de correção do `morph-apply`.
|
|
45
|
+
|
|
46
|
+
## Quando abrir a completa em vez desta
|
|
47
|
+
|
|
48
|
+
Dúvida de banda, violação mecânica suspeita, ou escopo de fase (Gate 3) — abra
|
|
49
|
+
`framework/evals/rubrics/{dimension}.md`. Esta condensada é o atalho do caso comum.
|
|
@@ -41,6 +41,15 @@ import { validateTasksDoc, outputsDivergenceViolations } from '../../shared/task
|
|
|
41
41
|
* Compare two tasks.json structures and return the list of plan-contract
|
|
42
42
|
* violations — changes that touch anything other than the mutable fields.
|
|
43
43
|
*
|
|
44
|
+
* Frozen-by-default, not an enumerated frozen list: the loop below walks the
|
|
45
|
+
* UNION of old/new task keys and flags any key not in `mutableFields`. A new
|
|
46
|
+
* plan-contract field (e.g. `agent`/`standards`, stamped by morph-plan — R2
|
|
47
|
+
* token-cut) needs NO entry added here to become protected; it is frozen the
|
|
48
|
+
* moment it exists on a task, simply by never being added to
|
|
49
|
+
* MUTABLE_SPEC_FIELDS (output-schema.js → `npm run generate` →
|
|
50
|
+
* framework/hooks/shared/phase-utils.js). Only fields that must stay
|
|
51
|
+
* EDITABLE post-Gate-2 (today: status, outputs, notes) belong in that list.
|
|
52
|
+
*
|
|
44
53
|
* @param {object} oldDoc - parsed previous file content
|
|
45
54
|
* @param {object} newDoc - parsed proposed file content
|
|
46
55
|
* @param {string[]} mutableFields - field names allowed to change
|
|
@@ -49,8 +49,10 @@ touching ONLY the mutable fields:
|
|
|
49
49
|
- `notes` — free-text record (deviations, context, resolved blockers)
|
|
50
50
|
|
|
51
51
|
The plan contract — `title`, `description`, `dependencies`, `effort`, `doneCriteria`,
|
|
52
|
-
`group` — is frozen after Gate 2. Any attempt to change it is
|
|
53
|
-
`protect-spec-files` hook.
|
|
52
|
+
`group`, `agent`, `standards` — is frozen after Gate 2. Any attempt to change it is
|
|
53
|
+
blocked by the `protect-spec-files` hook. `agent` (persona id, no `morph-` prefix)
|
|
54
|
+
and `standards` (paths pinned by morph-plan) are optional — legacy plans without
|
|
55
|
+
them remain valid.
|
|
54
56
|
|
|
55
57
|
Use the `Write` tool (whole-file rewrite) OR the `Edit` tool — `Edit` is allowed
|
|
56
58
|
as long as it touches only `status`, `outputs`, and `notes`. State is then synced
|
|
@@ -30,6 +30,16 @@
|
|
|
30
30
|
"type": "string",
|
|
31
31
|
"pattern": "^UC-\\d+$",
|
|
32
32
|
"description": "ID of the use case this task realizes (2-plan/usecases/UC-{nn}-{slug}.md). Optional: technical tasks with no observable behaviour have none."
|
|
33
|
+
},
|
|
34
|
+
"agent": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"description": "Persona id assigned by morph-plan for this task — the bare framework/agents.json id, no 'morph-' prefix (e.g. \"dotnet-senior\"). Optional. Part of the plan contract: frozen after Gate 2."
|
|
38
|
+
},
|
|
39
|
+
"standards": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"items": { "type": "string", "minLength": 1 },
|
|
42
|
+
"description": "Standards pinned by morph-plan for this task, as paths relative to the framework root (e.g. \"standards/backend/dotnet/vsa-handler-patterns.md\"). Optional. Part of the plan contract: frozen after Gate 2."
|
|
33
43
|
}
|
|
34
44
|
},
|
|
35
45
|
"additionalProperties": false
|
|
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar um novo projeto de LP com o template Umbraco genérico
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# create-umbraco-lp
|
|
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber instrução "use /migrate-lp-to-umbraco {projeto}" do usuário, ou quando o usuário descrever necessidade de migrar uma LP para Umbraco
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# migrate-lp-to-umbraco
|
|
@@ -4,7 +4,7 @@ description: Orquestra a execução autônoma de tasks de uma feature morph-spec
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: após Gate 2 aprovado, ao receber instrução "use /morph-apply" ou "implementar feature"
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -78,10 +78,22 @@ while (true):
|
|
|
78
78
|
// Entre tasks NÃO há pausa humana: score < 9 dispara re-implementação, não AskUserQuestion.
|
|
79
79
|
attempt = 0
|
|
80
80
|
best = { score: 0, output: null }
|
|
81
|
+
skillLoaded = false // true assim que Skill(morph-implement) foi invocada nesta sessão
|
|
81
82
|
while attempt < 3:
|
|
82
83
|
attempt += 1
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
|
|
85
|
+
if attempt == 1 or not skillLoaded:
|
|
86
|
+
// attempt 1 sempre invoca — é o que carrega mandate/spec/standard/persona
|
|
87
|
+
// pela primeira vez. Reinvoca também se skillLoaded caiu para false (ver nota).
|
|
88
|
+
invoke Skill(morph-implement) with task
|
|
89
|
+
(no attempt > 1: passar o feedback priorizado da tentativa anterior como input)
|
|
90
|
+
skillLoaded = true
|
|
91
|
+
else:
|
|
92
|
+
// attempt > 1, skill já em contexto desde attempt 1: NÃO reinvoque a Skill —
|
|
93
|
+
// aplique o feedback priorizado direto ao código já implementado (mesma
|
|
94
|
+
// lógica do passo 1 do morph-implement, "isto NÃO é implementação do zero",
|
|
95
|
+
// só que sem reler o documento inteiro da skill de novo).
|
|
96
|
+
aplicar feedback priorizado da tentativa anterior ao código já implementado
|
|
85
97
|
|
|
86
98
|
// CÓDIGO ANTES DO JUIZ — barreira determinística e barata (build+testes+validadores).
|
|
87
99
|
run: morph-spec verify {feature} {task.id} --json
|
|
@@ -115,14 +127,18 @@ while (true):
|
|
|
115
127
|
|
|
116
128
|
> **Verify vermelho consome uma tentativa** do loop (≤3) e devolve `feedback[]` acionável (erros de build, falhas de teste, issues de validador). O `morph-eval` NUNCA é invocado sobre código que não passou no verify — o juiz julga arquitetura/contratos/qualidade, o que o código determinístico não pega sozinho. Código roda antes, grátis; o juiz gasta tokens só no que sobra.
|
|
117
129
|
|
|
130
|
+
> **Retry sem re-invoke — e a exceção que evita rodar sem a skill.** `skillLoaded` cai de volta para `false` (forçando reinvocação no próximo attempt) se houver `/compact` OU troca de sessão (nova sessão retomando via `handoff.md`) entre a última invocação e o attempt corrente — os dois eventos podem descartar o conteúdo da skill do seu contexto sem avisar. Sem essa exceção, um retry pós-compact aplicaria feedback sem o mandate/standard/persona carregados. Fora desses dois casos, reinvocar a Skill só para aplicar um feedback pontual é exatamente o custo repetido (~13KB por tentativa) que este corte existe para eliminar.
|
|
131
|
+
>
|
|
132
|
+
> **Isto vale para a LEITURA da skill pelo orquestrador — não para o conteúdo de um dispatch.** Se o attempt 1 foi despachado a um sub-agent (§3b) e o attempt 2 também dispara os gatilhos de dispatch do `morph-implement` §4, o prompt de dispatch completo é reenviado normalmente: o sub-agent de um novo dispatch não tem o contexto do attempt anterior, então o corte de reinvoke não se aplica a ele. O que muda aqui é só a leitura da skill pelo ORQUESTRADOR no loop sequencial inline.
|
|
133
|
+
|
|
118
134
|
> **Por que loop e não pausa:** MORPH.md §0 manda auto-avançar entre tasks. Score baixo numa task individual é um problema de qualidade que o modelo deve **corrigir sozinho**, não delegar ao humano no meio da implementação. A pausa só existe quando o loop de correção se esgota (3 tentativas) — o "escape" do modelo autônomo. O trust mode não muda esse loop: ele governa os **gates** (proposal/plan/review), não o ciclo de correção intra-task.
|
|
119
135
|
|
|
120
136
|
### 3b. Modo Paralelo (para o subset independente)
|
|
121
137
|
|
|
122
138
|
**Antes de despachar, escope cada agente ao seu domínio.** Um agente genérico sem persona nem standard não "conhece" as convenções daquele domínio — produz código fora do padrão. Para cada fluxo/task a despachar, resolva:
|
|
123
139
|
|
|
124
|
-
- **Subagent:** em `.morph/framework/agents.json`, encontre o agente cujo `domains` cobre o `group`/domínio da task (ex: `backend` → `dotnet-senior`, `frontend` → `nextjs-expert`/`ui-designer`, `database` → `ef-modeler`).
|
|
125
|
-
- **Standard(s):** do array `standards`
|
|
140
|
+
- **Subagent:** use `task.agent` (id da persona, sem prefixo `morph-`) já carimbado em `tasks.json` pelo `morph-plan` §5a — o `subagent_type` do dispatch é **`morph-{task.agent}`** (ex.: `morph-dotnet-senior`). **Fallback (plano legado, task sem `agent`):** em `.morph/framework/agents.json`, encontre o agente cujo `domains` cobre o `group`/domínio da task (ex: `backend` → `dotnet-senior`, `frontend` → `nextjs-expert`/`ui-designer`, `database` → `ef-modeler`).
|
|
141
|
+
- **Standard(s):** use `task.standards` quando presente; senão, do array `standards` do agente resolvido acima em `agents.json` (e/ou do `mandate.md`), selecione o(s) path(s) `.md` relevante(s) à task.
|
|
126
142
|
- **Fallback (tipo não registrado):** se `morph-{id}` não estiver na sua lista de subagent types (stack não instalado neste projeto), use `subagent_type: "general-purpose"` colando o campo `persona` no prompt — e **registre nas `notes` da task**: `dispatch-fallback: general-purpose ({id} não registrado)`. O recap deve refletir o mecanismo real.
|
|
127
143
|
|
|
128
144
|
```
|
|
@@ -137,34 +153,29 @@ dispatch Agent (subagent_type: "morph-{id}") with prompt:
|
|
|
137
153
|
Contrato do relatório final:
|
|
138
154
|
1. GRAVE um relatório por task concluída em
|
|
139
155
|
`.morph/features/{feature}/3-implement/reports/{taskId}.md` ANTES de
|
|
140
|
-
encerrar
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
é falha de protocolo.
|
|
148
|
-
2. NÃO toque em tasks.json, feature.json, state.json nem no board de sessão
|
|
149
|
-
(TaskCreate/TaskUpdate) — todo estado (status de task, scores, gates) é
|
|
150
|
-
gravado exclusivamente pelo orquestrador.
|
|
156
|
+
encerrar (um arquivo por task, mesmo cobrindo várias tasks do fluxo
|
|
157
|
+
numa sessão só) — é o retorno autoritativo, não a sua mensagem final
|
|
158
|
+
(que é cortesia best-effort e pode não chegar inteira). NÃO chame
|
|
159
|
+
SendMessage.
|
|
160
|
+
2. NÃO toque em tasks.json, feature.json, state.json nem no board de
|
|
161
|
+
sessão (TaskCreate/TaskUpdate) — todo estado (status de task, scores,
|
|
162
|
+
gates) é gravado exclusivamente pelo orquestrador.
|
|
151
163
|
3. Verificação condicional ao isolamento:
|
|
152
164
|
- Batch SEM isolamento (working tree compartilhada): NÃO rode build/test da
|
|
153
165
|
suíte compartilhada — edições concorrentes quebram o build umas das outras;
|
|
154
|
-
a verificação é do orquestrador,
|
|
166
|
+
a verificação é do orquestrador, pós-batch (`morph-spec verify`).
|
|
155
167
|
- Batch COM isolamento (você recebeu `isolation: "worktree"`): rode build/test
|
|
156
168
|
DENTRO do seu worktree; commite seu trabalho na branch do worktree como
|
|
157
|
-
último ato antes do relatório; reporte a branch
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
Seu relatório final deve conter:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
- auto-score 0-10 (rubric: completeness, correctness, mandate compliance)
|
|
169
|
+
último ato antes do relatório; reporte a branch — nunca faça merge nem
|
|
170
|
+
toque na árvore principal.
|
|
171
|
+
|
|
172
|
+
Seu relatório final deve conter APENAS: (1) o que foi feito em 2-3
|
|
173
|
+
frases, (2) arquivos modificados, (3) problemas encontrados e como foram
|
|
174
|
+
resolvidos, (4) auto-score 0-10 com justificativa em uma linha. Relatório
|
|
175
|
+
ausente, oco, ou sem os 4 itens, é falha de protocolo.
|
|
165
176
|
```
|
|
166
177
|
|
|
167
|
-
> Este template é deliberadamente paritário com o template sequencial em `morph-implement` §4: ambos despacham o **subagent registrado do domínio (`morph-{id}`)** com standard escopado e as mesmas cláusulas do contrato do relatório final. A única diferença é que aqui múltiplos agentes rodam ao mesmo tempo, um por fluxo independente.
|
|
178
|
+
> Este template é deliberadamente paritário com o template sequencial em `morph-implement` §4: ambos despacham o **subagent registrado do domínio (`morph-{id}`)** com standard escopado e as **mesmas 3 cláusulas curtas** do contrato do relatório final e os mesmos 4 itens de relatório — racional completo em `MORPH.md` §6. A única diferença é que aqui múltiplos agentes rodam ao mesmo tempo, um por fluxo independente.
|
|
168
179
|
|
|
169
180
|
> **Alternativa quando os fluxos tocam os mesmos projetos/arquivos** (ex.: o mesmo csproj de testes compartilhado): siga a §3b-w abaixo em vez desta seção — despache com `isolation: "worktree"`, cada agente trabalha numa working tree própria e o orquestrador integra ao final.
|
|
170
181
|
|
|
@@ -247,10 +258,11 @@ O comando grava `gateDecisions.review = { pause, reason, signals, trust, decided
|
|
|
247
258
|
|
|
248
259
|
## Referências
|
|
249
260
|
|
|
250
|
-
- `.morph/framework/MORPH.md` — charter NLH
|
|
251
|
-
- `framework/skills/core/morph-implement/SKILL.md`
|
|
261
|
+
- `.morph/framework/MORPH.md` — charter NLH; §6 tem o racional completo do contrato do relatório final (aqui e no `morph-implement` §4 estão só as versões curtas coladas no dispatch)
|
|
262
|
+
- `framework/skills/core/morph-implement/SKILL.md` — §4 tem o template sequencial paritário a este §3b
|
|
252
263
|
- `framework/skills/core/morph-eval/SKILL.md`
|
|
253
264
|
- `framework/skills/core/morph-review/SKILL.md`
|
|
265
|
+
- `framework/skills/core/morph-plan/SKILL.md` §5a — resolve e carimba `task.agent`/`task.standards` no plano; fonte primária deste §3b
|
|
254
266
|
- `morph-spec dag {feature}` — análise de DAG e decisão sequencial vs paralelo (ponderada por effort), incluindo recomendação de isolamento (`isolation.recommended: "worktree"`)
|
|
255
267
|
- `morph-spec verify {feature} [task] --json` — cadeia determinística build+testes+validadores (código antes do juiz)
|
|
256
268
|
- `morph-spec score {feature} {task} {score} [--dims a,c,q,t]` — grava taskScores no feature.json com timestamp server-side
|
|
@@ -4,7 +4,7 @@ description: Entende o negócio, os usuários e a arquitetura antes de qualquer
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber um novo request de feature, antes de morph-plan
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-brainstorming
|
|
@@ -4,7 +4,7 @@ description: Avalia a qualidade de uma tarefa ou fase com rubricas graduadas 0-1
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao completar uma tarefa ou ao finalizar uma fase de implementação
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-eval
|
|
@@ -19,21 +19,23 @@ Também invocado ao receber o retorno de um sub-agent com auto-score < 7.
|
|
|
19
19
|
|
|
20
20
|
**Somente após `morph-spec verify {feature} {task}` verde** (a `morph-apply` garante isso no loop §3a). O verify é a barreira determinística (build+testes+validadores); esta skill julga o que o código não pega: arquitetura, contratos, qualidade, cobertura qualitativa.
|
|
21
21
|
|
|
22
|
-
> **Escopo de fase (Gate 3):** a avaliação da feature inteira NÃO é feita por quem implementou — a `morph-review` despacha a persona `evaluator` de `agents.json` (contexto fresco, sem autoria; recebe mandate + diff + saída real dos testes) usando esta skill como guia. O eval por task pode ser inline; o score composto do Gate 3 vem sempre do avaliador independente.
|
|
22
|
+
> **Escopo de fase (Gate 3):** a avaliação da feature inteira NÃO é feita por quem implementou — a `morph-review` despacha a persona `evaluator` de `agents.json` (contexto fresco, sem autoria; recebe mandate + diff + saída real dos testes) usando esta skill como guia, **sempre com as 4 rubricas completas** — nunca a condensada, que é exclusiva do eval inline por task. O eval por task pode ser inline; o score composto do Gate 3 vem sempre do avaliador independente, com a régua completa.
|
|
23
23
|
|
|
24
24
|
## O que fazer
|
|
25
25
|
|
|
26
26
|
0. Confirme que o `morph-spec verify` da task está verde (a morph-apply passa o resultado; numa invocação manual, rode `morph-spec verify {feature} {task} --json`). Se estiver vermelho, NÃO avalie — devolva o `feedback[]` do verify ao loop de correção.
|
|
27
27
|
|
|
28
|
-
1. Identifique o escopo da avaliação: tarefa única ou fase completa.
|
|
28
|
+
1. Identifique o escopo da avaliação: tarefa única (eval inline) ou fase completa (Gate 3).
|
|
29
29
|
|
|
30
|
-
2.
|
|
31
|
-
-
|
|
32
|
-
- `
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
2. Leia a rubrica correspondente ao escopo:
|
|
31
|
+
- **Tarefa única (eval inline, o caso comum):** leia SÓ `.morph/framework/evals/rubrics/condensed.md` — tabela única com as 4 dimensões e seus pesos, condensada das completas. Não abra as 4 rubricas completas para esse escopo; a condensada é fiel a elas (dimensões e pesos idênticos, enforced por `scripts/check-rubric-parity.js`).
|
|
32
|
+
- **Fase completa (Gate 3):** o avaliador independente despachado pela `morph-review` (persona `evaluator`, contexto fresco) usa **sempre** as 4 rubricas completas — a régua do juiz de Gate 3 não muda:
|
|
33
|
+
- `architecture.md` — peso 30% — vertical slice, separação de responsabilidades, infra
|
|
34
|
+
- `contracts.md` — peso 25% — tipos, validação, tratamento de erros, naming
|
|
35
|
+
- `code-quality.md` — peso 25% — naming, ausência de code smells, padrões do stack
|
|
36
|
+
- `test-coverage.md` — peso 20% — happy path, edge cases, isolamento, naming de testes
|
|
35
37
|
|
|
36
|
-
3. Atribua um score de 1 a 10 para cada dimensão seguindo as bandas da rubrica.
|
|
38
|
+
3. Atribua um score de 1 a 10 para cada dimensão seguindo as bandas da rubrica (condensada ou completa, conforme o passo 2).
|
|
37
39
|
|
|
38
40
|
4. Calcule o score composto: (arch × 0.30) + (contracts × 0.25) + (quality × 0.25) + (tests × 0.20).
|
|
39
41
|
|
|
@@ -53,9 +55,10 @@ Também invocado ao receber o retorno de um sub-agent com auto-score < 7.
|
|
|
53
55
|
|
|
54
56
|
## Referências
|
|
55
57
|
- `morph-spec verify {feature} [task] --json` — barreira determinística (build+testes+validadores) que precede esta skill
|
|
56
|
-
- `.morph/framework/evals/rubrics/
|
|
57
|
-
- `.morph/framework/evals/rubrics/
|
|
58
|
-
- `.morph/framework/evals/rubrics/
|
|
59
|
-
- `.morph/framework/evals/rubrics/
|
|
58
|
+
- `.morph/framework/evals/rubrics/condensed.md` — rubrica condensada (4 dimensões + pesos), usada no eval INLINE por task
|
|
59
|
+
- `.morph/framework/evals/rubrics/architecture.md` — rubrica completa de arquitetura (30%) — Gate 3, avaliador independente
|
|
60
|
+
- `.morph/framework/evals/rubrics/contracts.md` — rubrica completa de contratos (25%) — Gate 3, avaliador independente
|
|
61
|
+
- `.morph/framework/evals/rubrics/code-quality.md` — rubrica completa de qualidade (25%) — Gate 3, avaliador independente
|
|
62
|
+
- `.morph/framework/evals/rubrics/test-coverage.md` — rubrica completa de cobertura (20%) — Gate 3, avaliador independente
|
|
60
63
|
- `.morph/framework/evals/trace-schema.md` — formato canônico do trace
|
|
61
64
|
- `.morph/framework/MORPH.md` — seção 1 (papel Evaluator)
|
|
@@ -4,7 +4,7 @@ description: ADW cirúrgico de hotfix — resolve uma queda/bug crítico de prod
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber "/morph-hotfix {feature}", quando a morph-proposal classifica o pedido como workType hotfix (Passo 0), ou o usuário diz "produção caiu / hotfix / urgente"
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-hotfix
|
|
@@ -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.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-implement
|
|
@@ -18,23 +18,28 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
18
18
|
|
|
19
19
|
## O que fazer
|
|
20
20
|
|
|
21
|
-
1. **
|
|
21
|
+
1. **Leia o plano integral na primeira task da sessão.** No cold start (primeira task que você implementa nesta sessão — início de `/morph-apply`, ou a primeira depois de um `/compact`), **use a tool Read** em `mandate.md`, `spec.md` e `decisions.md` de `.morph/features/{feature}/2-plan/`, nessa ordem, e mantenha-os em contexto pelo resto da sessão. Nunca implemente a primeira task da sessão sem o mandate em contexto.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**Nas tasks seguintes da MESMA sessão, não releia por padrão.** O conteúdo carregado na task anterior continua válido — o contrato do plano está congelado pós-Gate 2 (`protect-spec-files` bloqueia mudança nos campos de contrato), então reler os três arquivos a cada task é custo sem sinal novo. Releia (o arquivo relevante, não necessariamente os três) só quando:
|
|
24
|
+
- houve `/compact` desde a última leitura — o compact pode ter descartado o conteúdo do contexto; ou
|
|
25
|
+
- a task corrente referencia um FR ou uma decisão que você não tem em contexto — dúvida legítima, não default cego.
|
|
26
|
+
|
|
27
|
+
> "Na dúvida, Read" continua valendo — mas como **gatilho** de releitura pontual (compact, ou lacuna de conteúdo específico), nunca como default de reler tudo a cada task. Isso é o que muda: o contrato deixou de ser "por task" e passou a ser "por sessão".
|
|
24
28
|
|
|
25
29
|
Do `mandate.md`, identifique os standards obrigatórios e patterns proibidos. Do `spec.md`, a arquitetura e as seções de FRs referenciadas pela `description` da task corrente. Do `decisions.md`, as ADRs já registradas — para não contradizê-las nem re-decidir o que já foi decidido.
|
|
26
30
|
|
|
27
31
|
**Se a `morph-apply` passou `feedback` de uma tentativa anterior** (loop de correção — a task foi implementada mas o `morph-eval` deu score < 9): isto NÃO é uma implementação do zero. Leia o feedback priorizado, identifique os pontos bloqueantes listados, e **corrija especificamente esses pontos** no código já existente. Não reescreva o que já passou; foque no que o eval apontou. Registre no recap qual feedback foi endereçado em qual tentativa.
|
|
28
32
|
|
|
29
33
|
2. Identifique o standard mais relevante para a tarefa atual:
|
|
30
|
-
-
|
|
31
|
-
- **
|
|
34
|
+
- Rode `morph-spec standards --find "<domínio/tecnologia da task>"` (filtros opcionais `--category`, `--tags`, `--json`) — o comando busca em `framework/standards/STANDARDS.json` e devolve o top 3 candidatos como `id · path · digest`, sem você abrir o index inteiro.
|
|
35
|
+
- **O digest decide a escolha.** Escolha 1-3 standards pelo `digest` retornado. Depois, **use a tool Read APENAS no(s) arquivo(s) `.md` escolhido(s)** (campo `path` do resultado) — o digest te diz qual candidato é o certo; o arquivo escolhido se lê inteiro, porque é onde vivem as convenções aplicáveis.
|
|
32
36
|
- **Tarefas de agente de IA (MAF):** Read os `framework/standards/ai-agents/*.md` relevantes para a task. Mesmo que você consulte docs externas (context7) para a superfície da API do `Microsoft.Agents.AI`, as **convenções de projeto** (single-agent por padrão, provider/caching/observability, A2A, middleware) vêm desses standards. Uma fonte externa não as conhece.
|
|
33
37
|
- Se nenhum standard cobrir o caso, registre no decisions.md e adote o critério mais conservador.
|
|
34
38
|
|
|
35
39
|
3. Identifique a persona de agente adequada para a tarefa:
|
|
36
|
-
- **
|
|
37
|
-
-
|
|
40
|
+
- **Fonte primária: `task.agent` do tasks.json.** O `morph-plan` já resolveu `agents.json` uma vez no momento do plano e carimbou `agent` (id da persona, sem prefixo `morph-`) e `standards` por task (ver `morph-plan` §5a) — use esses campos diretamente, sem reabrir `agents.json`.
|
|
41
|
+
- **Fallback — plano legado (task sem `agent`):** **use a tool Read em `.morph/framework/agents.json`** — não recupere a persona de memória. Encontre o agente cujo `domains` cobre o domínio da tarefa e leia o campo `persona` para adotar o ponto de vista durante a implementação.
|
|
42
|
+
- O array de standards — `task.standards` quando presente, ou o `standards` do agente lido no fallback — é o ponto de partida do passo 2.
|
|
38
43
|
- O `**Persona:**` que você grava no recap (passo 6) **deve** refletir o mecanismo REAL usado: `morph-{id}` (dispatch pelo subagent registrado), `general-purpose (fallback: {id})` (tipo não registrado — registre também nas `notes` da task), ou `inline ({id})` (você executou inline adotando a persona). É assim que o reviewer audita contra o que o runtime realmente registrou — uma auto-declaração que não casa com o dispatch é drift.
|
|
39
44
|
|
|
40
45
|
4. Decida se a task deve ser despachada para um sub-agent. Dispatch é o **default** para o caso pesado/multi-domínio (ver `.morph/framework/MORPH.md` seção 6) — despache quando a task dispara qualquer gatilho:
|
|
@@ -49,7 +54,7 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
49
54
|
|
|
50
55
|
> **Relação com `morph-spec dag`:** a decisão do DAG (na `morph-apply` §2) governa o **paralelismo ENTRE tasks** — quantos sub-agents rodam ao mesmo tempo. Os gatilhos acima governam o **dispatch INDIVIDUAL desta task**. São ortogonais e coexistem: `DAG: sequencial` NÃO significa "tudo inline" — significa "uma task por vez, na ordem". Exemplo: cadeia única backend de 12 tasks (DAG: sequencial); T7 tem effort L → despache T7 sozinha para `morph-dotnet-senior` e aguarde o retorno antes de seguir; T8 (S, mesmo domínio) roda inline.
|
|
51
56
|
|
|
52
|
-
**Se for despachar — use `subagent_type: "morph-{id}"` do agente identificado no passo 3.** Os subagents são gerados de `agents.json` na instalação (`.claude/agents/morph-{id}.md`) e a persona é o **system prompt** deles — não a cole no prompt. **Fallback:** se `morph-{id}` não estiver na sua lista de subagent types (stack não instalado), use `general-purpose` colando o campo `persona` no prompt e registre nas `notes` da task: `dispatch-fallback: general-purpose ({id} não registrado)`. Template do prompt:
|
|
57
|
+
**Se for despachar — use `subagent_type: "morph-{id}"` do agente identificado no passo 3.** Os subagents são gerados de `agents.json` na instalação (`.claude/agents/morph-{id}.md`) e a persona é o **system prompt** deles — não a cole no prompt. **Fallback:** se `morph-{id}` não estiver na sua lista de subagent types (stack não instalado), use `general-purpose` colando o campo `persona` no prompt e registre nas `notes` da task: `dispatch-fallback: general-purpose ({id} não registrado)`. Template do prompt (cláusulas do relatório em forma curta — racional completo em `MORPH.md` §6):
|
|
53
58
|
```
|
|
54
59
|
Implementar [ID]: [title da task]
|
|
55
60
|
|
|
@@ -59,27 +64,22 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
59
64
|
Done quando: [doneCriteria do tasks.json]
|
|
60
65
|
|
|
61
66
|
Contrato do relatório final:
|
|
62
|
-
1. GRAVE
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
chegar inteira ao orquestrador (o evento recebido às vezes é só um
|
|
66
|
-
idle_notification, sem texto). Ainda assim NÃO chame SendMessage: ele
|
|
67
|
-
gera um evento a mais além da notificação de término, custando um turno
|
|
68
|
-
inútil ao orquestrador. Relatório ausente, oco, ou sem os 4 itens abaixo,
|
|
69
|
-
é falha de protocolo.
|
|
67
|
+
1. GRAVE `.morph/features/{feature}/3-implement/reports/[ID].md` ANTES de
|
|
68
|
+
encerrar — é o retorno autoritativo, não a sua mensagem final (que é
|
|
69
|
+
cortesia best-effort e pode não chegar inteira). NÃO chame SendMessage.
|
|
70
70
|
2. NÃO toque em tasks.json, feature.json, state.json nem no board de sessão
|
|
71
|
-
|
|
72
|
-
orquestrador.
|
|
71
|
+
— todo estado é gravado exclusivamente pelo orquestrador.
|
|
73
72
|
3. Verificação condicional ao isolamento: em batch paralelo SEM isolamento,
|
|
74
73
|
NÃO rode build/test da suíte compartilhada — a verificação
|
|
75
|
-
(`morph-spec verify`) é do orquestrador,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
e reporte a branch — nunca faça merge.
|
|
74
|
+
(`morph-spec verify`) é do orquestrador, pós-batch. Em batch COM
|
|
75
|
+
`isolation: "worktree"`, rode build/test no seu worktree, commite na
|
|
76
|
+
branch do worktree como último ato antes do relatório — nunca faça merge.
|
|
79
77
|
|
|
80
78
|
Seu relatório (no arquivo, e na mensagem final quando ela chega) deve conter
|
|
81
79
|
APENAS: (1) o que foi feito em 2-3 frases, (2) arquivos modificados,
|
|
82
|
-
(3) problemas encontrados e como foram resolvidos, (4) auto-score 0-10
|
|
80
|
+
(3) problemas encontrados e como foram resolvidos, (4) auto-score 0-10 com
|
|
81
|
+
justificativa em uma linha. Relatório ausente, oco, ou sem os 4 itens, é
|
|
82
|
+
falha de protocolo.
|
|
83
83
|
```
|
|
84
84
|
Após o retorno do dispatch: **leia primeiro** `.morph/features/{feature}/3-implement/reports/[ID].md`
|
|
85
85
|
— é o valor de retorno autoritativo. Trate a mensagem de texto do sub-agent, se chegou inteira,
|
|
@@ -154,8 +154,9 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
154
154
|
- Trace em `.morph/traces/` gerado automaticamente pelo hook `trace-autogen` (a skill não o escreve)
|
|
155
155
|
|
|
156
156
|
## Referências
|
|
157
|
-
-
|
|
158
|
-
- `.morph/framework/agents.json` — personas dos agentes por domínio
|
|
157
|
+
- `morph-spec standards --find "<query>" [--category] [--tags] [--json]` — busca de standard por digest (passo 2); não abre `STANDARDS.json` inteiro
|
|
158
|
+
- `.morph/framework/agents.json` — personas dos agentes por domínio; lido apenas no fallback do passo 3 (task sem `agent` — plano legado)
|
|
159
159
|
- `.morph/framework/MORPH.md` seção 6 — critérios de dispatch e contrato de retorno do sub-agent
|
|
160
160
|
- `.morph/features/{feature}/2-plan/mandate.md` — contrato da feature
|
|
161
|
+
- `.morph/features/{feature}/2-plan/tasks.json` — `task.agent`/`task.standards` carimbados pelo `morph-plan` §5a (fonte primária dos passos 2 e 3)
|
|
161
162
|
- `.morph/framework/schemas/tasks.schema.json` — schema de tasks.json (enum de status e campos)
|
|
@@ -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.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -118,7 +118,9 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
118
118
|
"status": "pending",
|
|
119
119
|
"outputs": [],
|
|
120
120
|
"group": "backend",
|
|
121
|
-
"usecase": "UC-07"
|
|
121
|
+
"usecase": "UC-07",
|
|
122
|
+
"agent": "dotnet-senior",
|
|
123
|
+
"standards": ["standards/backend/dotnet/vsa-handler-patterns.md"]
|
|
122
124
|
}
|
|
123
125
|
]
|
|
124
126
|
}
|
|
@@ -132,6 +134,14 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
132
134
|
- `group` (opcional): backend, frontend, tests, infra, docs. Default backend.
|
|
133
135
|
- `outputs` (opcional): array de paths que serão criados/modificados.
|
|
134
136
|
- `usecase` (opcional): o **ID** do use case que a task realiza — `UC-07`, nunca o nome do arquivo. Task puramente técnica (build, config, migração) não tem.
|
|
137
|
+
- `agent` (opcional): o `id` da persona de `agents.json` responsável pelo domínio da task — **sem** o prefixo `morph-` (ex.: `"dotnet-senior"`, não `"morph-dotnet-senior"`). Carimbado no passo 5a abaixo; ausente numa task sem domínio de agente claro (config/docs) ou num plano legado.
|
|
138
|
+
- `standards` (opcional): array de paths relativos de `framework/standards/` (ex.: `["standards/backend/dotnet/vsa-handler-patterns.md"]`) — os standards da persona resolvida em `agent`, mais qualquer standard específico que a task exija.
|
|
139
|
+
|
|
140
|
+
**5a. Carimbe `agent` e `standards` — uma leitura de `agents.json`, N tasks.** Antes de fechar o array `tasks`, **use a tool Read UMA VEZ** em `.morph/framework/agents.json` (não releia por task — é essa releitura repetida por task, hoje feita em `morph-implement`/`morph-apply` a cada dispatch, que este passo existe para eliminar). Para cada task do plano, resolva:
|
|
141
|
+
- `agent`: o `id` da persona (chave do objeto `agents`, sem prefixo `morph-`) cujo `domains` cobre o `group`/domínio da task — o mesmo casamento que `morph-implement` §3 e `morph-apply` §3b fariam manualmente a cada dispatch; aqui você faz uma vez, no plano.
|
|
142
|
+
- `standards`: o array `standards` dessa persona, mais qualquer standard adicional que a task especificamente exija (some, não substitua).
|
|
143
|
+
|
|
144
|
+
Uma task cujo domínio não bate com nenhuma persona registrada (ex.: task pura de config/docs) fica sem `agent` — `morph-implement` cai no fallback de Read de `agents.json` nesse caso.
|
|
135
145
|
|
|
136
146
|
**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.
|
|
137
147
|
|
|
@@ -193,7 +203,7 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
193
203
|
- `.morph/features/{feature}/2-plan/usecases/UC-{nn}-{slug}.md` — um arquivo por use case, com pós-condições testáveis
|
|
194
204
|
- `.morph/features/{feature}/2-plan/spec.md` — especificação técnica completa
|
|
195
205
|
- `.morph/features/{feature}/2-plan/mandate.md` — contrato de implementação
|
|
196
|
-
- `.morph/features/{feature}/2-plan/tasks.json` — task list estruturada válida contra schema, com o `doneCriteria` carregando a pós-condição literal
|
|
206
|
+
- `.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)
|
|
197
207
|
- Gate 2 registrado aprovado em `.morph/features/{feature}/feature.json` (autoritativo)
|
|
198
208
|
|
|
199
209
|
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.
|
|
@@ -202,6 +212,7 @@ Ao registrar a aprovação do Gate 2, atualize o frontmatter de cada use case de
|
|
|
202
212
|
|
|
203
213
|
- `.morph/framework/MORPH.md` — seções 2 (Fases), 3 (Contratos de Output), 5 (Gates), Trust Mode
|
|
204
214
|
- `.morph/framework/standards/STANDARDS.json` — para identificar standards relevantes ao mandate
|
|
215
|
+
- `.morph/framework/agents.json` — personas por domínio; lido UMA VEZ no passo 5a para carimbar `agent`/`standards` por task
|
|
205
216
|
- `.morph/framework/schemas/tasks.schema.json` — schema obrigatório de tasks.json
|
|
206
217
|
- `.morph/specs/usecases/` — acervo permanente de use cases do projeto (leia antes de numerar; em `bug`, é onde mora o UC a corrigir)
|
|
207
218
|
- `.morph/features/{feature}/0-proposal/proposal.md` — escopo da feature
|
|
@@ -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.17.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.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -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.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-stack-scan
|
|
@@ -4,7 +4,7 @@ description: Localiza e carrega um standard específico do STANDARDS.json sob de
|
|
|
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.17.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polymorphism-tech/morph-spec",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.17.0",
|
|
4
4
|
"description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"generate:check": "node scripts/generate-refs.js --check",
|
|
46
46
|
"generate-standards": "node scripts/generate-standards-registry.js",
|
|
47
47
|
"generate-standards:check": "node scripts/generate-standards-registry.js --check",
|
|
48
|
-
"validate": "node scripts/validate-framework.js",
|
|
48
|
+
"validate": "node scripts/validate-framework.js && node scripts/check-rubric-parity.js",
|
|
49
49
|
"freshness": "node scripts/check-freshness.js",
|
|
50
50
|
"verify": "npm run generate:check && npm run generate-standards:check && npm run validate && npm run freshness && npm test",
|
|
51
51
|
"test": "node scripts/run-tests.mjs",
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `morph-spec standards --find "<query>" [--category <c>] [--tags <a,b>] [--json]`
|
|
3
|
+
*
|
|
4
|
+
* Server-side replacement for "read the whole STANDARDS.json to pick 1-3
|
|
5
|
+
* standards" (R2 of the performance plan — token cut per task). Today
|
|
6
|
+
* morph-implement loads the full 65 KB / 60-entry registry (with a `digest`
|
|
7
|
+
* per entry) just to choose a couple of relevant standards: ~16k tokens per
|
|
8
|
+
* task. This command searches server-side and returns only the TOP 3 hits —
|
|
9
|
+
* `id · path · digest` — where the digest is the FULL text: it is what
|
|
10
|
+
* substitutes for actually reading the standard's .md file.
|
|
11
|
+
*
|
|
12
|
+
* Without `--find` (a bare call): lists categories + counts — a cheap
|
|
13
|
+
* orientation view, no scoring.
|
|
14
|
+
*
|
|
15
|
+
* A query with no hit exits 0 (this is advisory tooling, not a gate) with a
|
|
16
|
+
* human-readable warning + category suggestions on stderr, and — under
|
|
17
|
+
* `--json` — a stable `{ query, results: [], suggestion }` shape on stdout so
|
|
18
|
+
* a JSON consumer never has to special-case "no results" as a parse error.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readFileSync } from 'fs';
|
|
22
|
+
import { join } from 'path';
|
|
23
|
+
import chalk from 'chalk';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve STANDARDS.json the same way doctor.js resolves the standards root:
|
|
27
|
+
* an installed project first (`.morph/framework/standards/`), falling back
|
|
28
|
+
* to this package's own bundled copy (`framework/standards/`) — the framework
|
|
29
|
+
* repo in dev, or the npm package's own copy before `setup-infra` has run.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} targetPath - project root to look for `.morph/` under
|
|
32
|
+
* @returns {string|null} absolute path to STANDARDS.json, or null if neither exists
|
|
33
|
+
*/
|
|
34
|
+
export function resolveStandardsJsonPath(targetPath) {
|
|
35
|
+
const installed = join(targetPath, '.morph', 'framework', 'standards', 'STANDARDS.json');
|
|
36
|
+
if (existsSync(installed)) return installed;
|
|
37
|
+
|
|
38
|
+
const packageFallback = join(import.meta.dirname, '..', '..', '..', 'framework', 'standards', 'STANDARDS.json');
|
|
39
|
+
if (existsSync(packageFallback)) return packageFallback;
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Load + parse STANDARDS.json. Throws on missing/invalid file — caller decides how to report it. */
|
|
45
|
+
function loadRegistry(standardsJsonPath) {
|
|
46
|
+
const raw = readFileSync(standardsJsonPath, 'utf8');
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* category → count, sorted by count desc then name asc.
|
|
52
|
+
* @param {object[]} standards
|
|
53
|
+
* @returns {{ category: string, count: number }[]}
|
|
54
|
+
*/
|
|
55
|
+
export function categoryCounts(standards) {
|
|
56
|
+
const counts = {};
|
|
57
|
+
for (const std of standards) {
|
|
58
|
+
const cat = std.category || 'uncategorized';
|
|
59
|
+
counts[cat] = (counts[cat] || 0) + 1;
|
|
60
|
+
}
|
|
61
|
+
return Object.entries(counts)
|
|
62
|
+
.map(([category, count]) => ({ category, count }))
|
|
63
|
+
.sort((a, b) => b.count - a.count || a.category.localeCompare(b.category));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Simple weighted relevance score for one standard against a query.
|
|
68
|
+
* Tiers, as specified: name/id > tags(+aliases) > digest. category/subcategory
|
|
69
|
+
* ride alongside the tags tier (lighter). A token-level fallback keeps
|
|
70
|
+
* multi-word queries findable even without a verbatim phrase match anywhere.
|
|
71
|
+
*
|
|
72
|
+
* @param {object} std - one STANDARDS.json entry
|
|
73
|
+
* @param {string} query - raw user query (already trimmed, non-empty)
|
|
74
|
+
* @returns {number} score, 0 = no match
|
|
75
|
+
*/
|
|
76
|
+
export function scoreStandard(std, query) {
|
|
77
|
+
const q = query.toLowerCase();
|
|
78
|
+
const tokens = q.split(/\s+/).filter((t) => t.length >= 3);
|
|
79
|
+
|
|
80
|
+
const name = String(std.name || '').toLowerCase();
|
|
81
|
+
const id = String(std.id || '').toLowerCase();
|
|
82
|
+
const category = String(std.category || '').toLowerCase();
|
|
83
|
+
const subcategory = String(std.subcategory || '').toLowerCase();
|
|
84
|
+
const tags = (std.tags || []).map((t) => String(t).toLowerCase());
|
|
85
|
+
const aliases = (std.aliases || []).map((a) => String(a).toLowerCase());
|
|
86
|
+
const digest = String(std.digest || '').toLowerCase();
|
|
87
|
+
|
|
88
|
+
let score = 0;
|
|
89
|
+
|
|
90
|
+
// Tier 1: name / id — the strongest signal.
|
|
91
|
+
if (id === q || name === q) score += 20;
|
|
92
|
+
else if (id.includes(q) || name.includes(q)) score += 12;
|
|
93
|
+
|
|
94
|
+
// Tier 2: tags / aliases (curated search terms) + category/subcategory.
|
|
95
|
+
if (tags.includes(q) || aliases.includes(q)) score += 8;
|
|
96
|
+
else if (tags.some((t) => t.includes(q)) || aliases.some((a) => a.includes(q))) score += 5;
|
|
97
|
+
if (category === q || subcategory === q) score += 4;
|
|
98
|
+
|
|
99
|
+
// Tier 3: digest — weakest signal, full-phrase substring only.
|
|
100
|
+
if (digest.includes(q)) score += 2;
|
|
101
|
+
|
|
102
|
+
// Token-level fallback for multi-word queries with no verbatim match above.
|
|
103
|
+
for (const tok of tokens) {
|
|
104
|
+
if (name.includes(tok) || id.includes(tok)) score += 2;
|
|
105
|
+
if (tags.some((t) => t.includes(tok)) || aliases.some((a) => a.includes(tok))) score += 1;
|
|
106
|
+
if (digest.includes(tok)) score += 0.5;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return score;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Rank standards against a query, applying optional category/tags filters
|
|
114
|
+
* first. Returns the TOP 3 with score > 0, sorted desc (ties broken by id).
|
|
115
|
+
*
|
|
116
|
+
* @param {object[]} standards
|
|
117
|
+
* @param {string} query
|
|
118
|
+
* @param {{ category?: string, tags?: string[] }} [filters]
|
|
119
|
+
* @returns {Array<object & { score: number }>}
|
|
120
|
+
*/
|
|
121
|
+
export function findStandards(standards, query, filters = {}) {
|
|
122
|
+
const category = filters.category?.toLowerCase();
|
|
123
|
+
const tags = (filters.tags || []).map((t) => t.toLowerCase());
|
|
124
|
+
|
|
125
|
+
let candidates = standards;
|
|
126
|
+
if (category) {
|
|
127
|
+
candidates = candidates.filter((s) => String(s.category || '').toLowerCase() === category);
|
|
128
|
+
}
|
|
129
|
+
if (tags.length) {
|
|
130
|
+
candidates = candidates.filter((s) =>
|
|
131
|
+
(s.tags || []).some((t) => tags.includes(String(t).toLowerCase())));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return candidates
|
|
135
|
+
.map((s) => ({ ...s, score: scoreStandard(s, query) }))
|
|
136
|
+
.filter((s) => s.score > 0)
|
|
137
|
+
.sort((a, b) => b.score - a.score || String(a.id).localeCompare(String(b.id)))
|
|
138
|
+
.slice(0, 3);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Parse `--tags a,b,c` into a trimmed, non-empty array. */
|
|
142
|
+
function parseTagsOption(raw) {
|
|
143
|
+
if (!raw) return [];
|
|
144
|
+
return String(raw).split(',').map((t) => t.trim()).filter(Boolean);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {{ find?: string, category?: string, tags?: string, json?: boolean, cwd?: string }} [options]
|
|
149
|
+
*/
|
|
150
|
+
export function standardsCommand(options = {}) {
|
|
151
|
+
const cwd = options.cwd || process.cwd();
|
|
152
|
+
const standardsJsonPath = resolveStandardsJsonPath(cwd);
|
|
153
|
+
|
|
154
|
+
if (!standardsJsonPath) {
|
|
155
|
+
console.error(chalk.red('\n❌ STANDARDS.json não encontrado — nem em .morph/framework/standards/ nem no pacote.'));
|
|
156
|
+
console.error(chalk.gray(' Rode `morph-spec setup-infra` (projeto instalado) ou verifique framework/standards/ (repo do framework).\n'));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let registry;
|
|
161
|
+
try {
|
|
162
|
+
registry = loadRegistry(standardsJsonPath);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error(chalk.red(`\n❌ STANDARDS.json inválido em ${standardsJsonPath}: ${err.message}\n`));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const standards = Array.isArray(registry.standards) ? registry.standards : [];
|
|
169
|
+
const query = (options.find || '').trim();
|
|
170
|
+
|
|
171
|
+
// ── Bare call (no --find): categories + counts, cheap orientation ────────
|
|
172
|
+
if (!query) {
|
|
173
|
+
const counts = categoryCounts(standards);
|
|
174
|
+
if (options.json) {
|
|
175
|
+
console.log(JSON.stringify({ total: standards.length, categories: counts }));
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
console.log(chalk.bold(`\nSTANDARDS.json — ${standards.length} standards, ${counts.length} categorias\n`));
|
|
179
|
+
const col = (s, w) => String(s).padEnd(w);
|
|
180
|
+
for (const { category, count } of counts) {
|
|
181
|
+
console.log(` ${col(category, 20)}${count}`);
|
|
182
|
+
}
|
|
183
|
+
console.log(chalk.gray('\nUse --find "<query>" para buscar (top 3, com digest completo).\n'));
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── --find: score + filter ────────────────────────────────────────────
|
|
188
|
+
const filters = { category: options.category, tags: parseTagsOption(options.tags) };
|
|
189
|
+
const results = findStandards(standards, query, filters);
|
|
190
|
+
|
|
191
|
+
if (!results.length) {
|
|
192
|
+
const counts = categoryCounts(standards);
|
|
193
|
+
if (options.json) {
|
|
194
|
+
console.log(JSON.stringify({ query, results: [], suggestion: { categories: counts } }));
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}
|
|
197
|
+
console.error(chalk.yellow(`\n⚠️ Nenhum standard encontrado para "${query}".`));
|
|
198
|
+
console.error(chalk.gray(' Categorias disponíveis: ' +
|
|
199
|
+
counts.map((c) => `${c.category} (${c.count})`).join(', ') + '\n'));
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const shaped = results.map((r) => ({
|
|
204
|
+
id: r.id, name: r.name, path: r.path, category: r.category, digest: r.digest, score: r.score,
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
if (options.json) {
|
|
208
|
+
console.log(JSON.stringify({ query, results: shaped }));
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(chalk.bold(`\nTOP ${shaped.length} para "${query}"\n`));
|
|
213
|
+
shaped.forEach((r, i) => {
|
|
214
|
+
console.log(chalk.cyan(`${i + 1}. ${r.id}`) + chalk.gray(` (${r.category})`));
|
|
215
|
+
console.log(` ${r.path}`);
|
|
216
|
+
console.log(chalk.gray(` ${r.digest}`));
|
|
217
|
+
console.log('');
|
|
218
|
+
});
|
|
219
|
+
process.exit(0);
|
|
220
|
+
}
|
|
@@ -87,6 +87,32 @@ export function validateTasksJson(data) {
|
|
|
87
87
|
if (task.usecase !== undefined && (typeof task.usecase !== 'string' || !USECASE_RE.test(task.usecase))) {
|
|
88
88
|
errors.push(`${ctx}.usecase must match /^UC-\\d+$/ (the ID alone, e.g. "UC-07")`);
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
// Optional: morph-plan may stamp a persona + standards per task (R2 token
|
|
92
|
+
// cut — `morph-spec standards --find` replaces reading the whole
|
|
93
|
+
// STANDARDS.json). Both are part of the plan contract and frozen after
|
|
94
|
+
// Gate 2 by protect-spec-files (any key outside MUTABLE_SPEC_FIELDS is
|
|
95
|
+
// blocked by construction — see that hook's top comment). Type-check
|
|
96
|
+
// only here: no cross-check against agents.json, since stack-filtered
|
|
97
|
+
// installs carry fewer personas than the full set and would false-fail.
|
|
98
|
+
if (task.agent !== undefined) {
|
|
99
|
+
if (typeof task.agent !== 'string' || !task.agent.trim()) {
|
|
100
|
+
errors.push(`${ctx}.agent must be a non-empty string (persona id, e.g. "dotnet-senior")`);
|
|
101
|
+
} else if (task.agent.startsWith('morph-')) {
|
|
102
|
+
errors.push(`${ctx}.agent must be the bare persona id — no "morph-" prefix (got "${task.agent}")`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (task.standards !== undefined) {
|
|
106
|
+
if (!Array.isArray(task.standards)) {
|
|
107
|
+
errors.push(`${ctx}.standards must be an array of paths`);
|
|
108
|
+
} else {
|
|
109
|
+
task.standards.forEach((s, i) => {
|
|
110
|
+
if (typeof s !== 'string' || !s.trim()) {
|
|
111
|
+
errors.push(`${ctx}.standards[${i}] must be a non-empty string path`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
90
116
|
}
|
|
91
117
|
|
|
92
118
|
return { valid: errors.length === 0, errors };
|