@polymorphism-tech/morph-spec 8.5.0 → 8.7.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 +4 -4
- package/bin/morph-spec.js +23 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +12 -7
- package/framework/MORPH.md +49 -6
- package/framework/evals/rubrics/test-coverage.md +4 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -0
- package/framework/hooks/claude-code/core/worktree-context.js +41 -5
- package/framework/hooks/shared/claim-registry.js +175 -0
- package/framework/hooks/shared/phase-utils.js +1 -0
- package/framework/hooks/shared/state-reader.js +68 -25
- package/framework/rules/morph-task-tracking.md +14 -8
- package/framework/rules/morph-workflow.md +36 -3
- package/framework/schemas/feature.schema.json +2 -1
- package/framework/schemas/tasks.schema.json +6 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +1 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +4 -3
- 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 +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +83 -13
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +7 -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 +1 -1
- package/src/commands/project/claim.js +91 -0
- package/src/commands/project/doctor.js +65 -0
- package/src/commands/project/fleet.js +37 -2
- package/src/commands/project/worktree.js +70 -7
- package/src/commands/state/advance.js +60 -1
- package/src/commands/state/approve.js +125 -2
- package/src/commands/state/archive.js +44 -5
- package/src/commands/state/create.js +3 -3
- package/src/commands/state/finish.js +103 -7
- package/src/commands/state/list.js +9 -1
- package/src/commands/state/report-check.js +202 -0
- package/src/commands/tasks/dag.js +25 -2
- package/src/commands/validation/validate-feature.js +9 -2
- package/src/core/paths/output-schema.js +66 -6
- package/src/core/state/lifecycle-audit.js +57 -0
- package/src/dashboard/scanner/state-scanner.js +11 -3
- package/src/lib/agents/output-collector.js +5 -0
- package/src/lib/generators/architecture-scanner.js +1 -1
- package/src/lib/generators/handoff-generator.js +4 -2
- package/src/lib/specs/usecase-check.js +173 -0
- package/src/lib/specs/usecase-promoter.js +120 -0
- package/src/lib/tasks/tasks-json-parser.js +16 -0
- package/src/lib/worktree/fleet-scanner.js +224 -15
- package/src/lib/worktree/worktree-audit.js +181 -0
- package/src/utils/file-copier.js +5 -1
|
@@ -181,6 +181,63 @@ export function getFeature(featureName, projectPath) {
|
|
|
181
181
|
*/
|
|
182
182
|
export const PHASE_INDEX = { proposal: 0, uiux: 1, plan: 2, implement: 3, review: 4 };
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* The canonical artifact of each phase, descending. Exported so a caller that
|
|
186
|
+
* holds the file list from somewhere OTHER than the local filesystem (a git
|
|
187
|
+
* tree, say) can answer the same question without re-encoding the map.
|
|
188
|
+
*/
|
|
189
|
+
export const PHASE_ARTIFACTS = [
|
|
190
|
+
['4-review', 'review', 'review-report.md'],
|
|
191
|
+
['3-implement', 'implement', 'recap.md'],
|
|
192
|
+
['2-plan', 'plan', 'spec.md'],
|
|
193
|
+
['1-design', 'uiux', 'design-system.md'],
|
|
194
|
+
['0-proposal', 'proposal', 'proposal.md'],
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
const gatesOf = (feature) => feature?.approvals || feature?.approvalGates;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Gate-implied phase from an ALREADY-LOADED feature object — no disk access.
|
|
201
|
+
*
|
|
202
|
+
* Split out of `gateImpliedPhase` so callers holding a feature.json that did
|
|
203
|
+
* not come from the working tree (e.g. `git show morph/{f}:…`) can reuse the
|
|
204
|
+
* rule instead of copying it. The path-based wrapper below is unchanged.
|
|
205
|
+
*
|
|
206
|
+
* @param {Object|null} feature
|
|
207
|
+
* @returns {string} phase name
|
|
208
|
+
*/
|
|
209
|
+
export function gateImpliedPhaseFrom(feature) {
|
|
210
|
+
const gates = gatesOf(feature);
|
|
211
|
+
const ok = (g) => gates?.[g]?.approved === true;
|
|
212
|
+
if (ok('review')) return 'review';
|
|
213
|
+
if (ok('plan')) return 'implement';
|
|
214
|
+
if (ok('uiux')) return 'plan';
|
|
215
|
+
return 'proposal';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Phase from an already-loaded feature plus an artifact predicate — no disk
|
|
220
|
+
* access of its own, so the artifact universe can be a git tree.
|
|
221
|
+
*
|
|
222
|
+
* @param {Object|null} feature
|
|
223
|
+
* @param {(relPath: string) => boolean} hasArtifact - receives POSIX-style
|
|
224
|
+
* paths relative to the feature dir, e.g. `2-plan/spec.md`
|
|
225
|
+
* @returns {string} phase name
|
|
226
|
+
*/
|
|
227
|
+
export function derivePhaseFrom(feature, hasArtifact) {
|
|
228
|
+
let artifactPhase = 'proposal';
|
|
229
|
+
for (const [folder, phase, canonical] of PHASE_ARTIFACTS) {
|
|
230
|
+
if (hasArtifact(`${folder}/${canonical}`)) { artifactPhase = phase; break; }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Gate-aware: an approved gate advances the phase even before the next
|
|
234
|
+
// phase's first canonical artifact exists. This breaks the chicken-and-egg
|
|
235
|
+
// where that first artifact could never be written because the phase never
|
|
236
|
+
// advanced to permit it. Take whichever of (artifact, gate-implied) is later.
|
|
237
|
+
const implied = gateImpliedPhaseFrom(feature);
|
|
238
|
+
return PHASE_INDEX[implied] > PHASE_INDEX[artifactPhase] ? implied : artifactPhase;
|
|
239
|
+
}
|
|
240
|
+
|
|
184
241
|
/**
|
|
185
242
|
* The phase implied purely by approved gates, independent of which artifacts
|
|
186
243
|
* exist on disk. An approved gate authorizes the NEXT phase, so:
|
|
@@ -192,32 +249,15 @@ export const PHASE_INDEX = { proposal: 0, uiux: 1, plan: 2, implement: 3, review
|
|
|
192
249
|
* @returns {string} phase name
|
|
193
250
|
*/
|
|
194
251
|
export function gateImpliedPhase(featureName, projectPath) {
|
|
195
|
-
|
|
196
|
-
if (isGateApproved(featureName, 'plan', projectPath)) return 'implement';
|
|
197
|
-
if (isGateApproved(featureName, 'uiux', projectPath)) return 'plan';
|
|
198
|
-
return 'proposal';
|
|
252
|
+
return gateImpliedPhaseFrom(getFeature(featureName, projectPath));
|
|
199
253
|
}
|
|
200
254
|
|
|
201
255
|
export function derivePhaseForFeature(featureName, projectPath) {
|
|
202
256
|
const basePath = join(projectPath || process.cwd(), '.morph/features', featureName);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
['1-design', 'uiux', 'design-system.md'],
|
|
208
|
-
['0-proposal', 'proposal', 'proposal.md'],
|
|
209
|
-
];
|
|
210
|
-
let artifactPhase = 'proposal';
|
|
211
|
-
for (const [folder, phase, canonical] of phaseMap) {
|
|
212
|
-
if (existsSync(join(basePath, folder, canonical))) { artifactPhase = phase; break; }
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Gate-aware: an approved gate advances the phase even before the next
|
|
216
|
-
// phase's first canonical artifact exists. This breaks the chicken-and-egg
|
|
217
|
-
// where that first artifact could never be written because the phase never
|
|
218
|
-
// advanced to permit it. Take whichever of (artifact, gate-implied) is later.
|
|
219
|
-
const implied = gateImpliedPhase(featureName, projectPath);
|
|
220
|
-
return PHASE_INDEX[implied] > PHASE_INDEX[artifactPhase] ? implied : artifactPhase;
|
|
257
|
+
return derivePhaseFrom(
|
|
258
|
+
getFeature(featureName, projectPath),
|
|
259
|
+
(rel) => existsSync(join(basePath, rel)),
|
|
260
|
+
);
|
|
221
261
|
}
|
|
222
262
|
|
|
223
263
|
/**
|
|
@@ -253,10 +293,9 @@ export function isGateApproved(featureName, gate, projectPath) {
|
|
|
253
293
|
* @param {string} [projectPath]
|
|
254
294
|
* @returns {string[]} List of gate names that are not yet approved
|
|
255
295
|
*/
|
|
256
|
-
export function
|
|
257
|
-
const feature = getFeature(featureName, projectPath);
|
|
296
|
+
export function pendingGatesFrom(feature) {
|
|
258
297
|
// Support both field names: 'approvals' (v5 state) and 'approvalGates' (legacy)
|
|
259
|
-
const gates = feature
|
|
298
|
+
const gates = gatesOf(feature);
|
|
260
299
|
if (!gates) return [];
|
|
261
300
|
|
|
262
301
|
return Object.entries(gates)
|
|
@@ -264,6 +303,10 @@ export function getPendingGates(featureName, projectPath) {
|
|
|
264
303
|
.map(([name]) => name);
|
|
265
304
|
}
|
|
266
305
|
|
|
306
|
+
export function getPendingGates(featureName, projectPath) {
|
|
307
|
+
return pendingGatesFrom(getFeature(featureName, projectPath));
|
|
308
|
+
}
|
|
309
|
+
|
|
267
310
|
/**
|
|
268
311
|
* Get feature outputs status.
|
|
269
312
|
* @param {string} featureName
|
|
@@ -33,11 +33,18 @@ TaskUpdate(taskId, subject="Round 3 — score 82/100")
|
|
|
33
33
|
|
|
34
34
|
## Persisting Progress to `tasks.json`
|
|
35
35
|
|
|
36
|
+
> **Dois sistemas, dois vocabulários.** Tudo acima desta linha é sobre as *session
|
|
37
|
+
> tasks* nativas do Claude Code (`TaskCreate`/`TaskUpdate`), cujo status terminal é
|
|
38
|
+
> `completed`. Tudo abaixo é sobre o `tasks.json` do morph-spec, governado por
|
|
39
|
+
> `.morph/framework/schemas/tasks.schema.json`, cujo status terminal é `done` —
|
|
40
|
+
> **`completed` não existe nesse enum** e o hook `protect-spec-files` rejeita a
|
|
41
|
+
> gravação. Não misture os dois.
|
|
42
|
+
|
|
36
43
|
Native session tasks (`TaskCreate`/`TaskUpdate`) track your live work. To persist
|
|
37
44
|
task progress for the feature, write `.morph/features/{feature}/2-plan/tasks.json`,
|
|
38
45
|
touching ONLY the mutable fields:
|
|
39
46
|
|
|
40
|
-
- `status` — `pending` → `in_progress` → `
|
|
47
|
+
- `status` — `pending` → `in_progress` → `done` (também: `skipped`, `blocked`)
|
|
41
48
|
- `outputs` — the files the task produced
|
|
42
49
|
- `notes` — free-text record (deviations, context, resolved blockers)
|
|
43
50
|
|
|
@@ -53,17 +60,16 @@ output.
|
|
|
53
60
|
## Deviation Tracking
|
|
54
61
|
|
|
55
62
|
When completing a task differently than originally planned, record the reason in the
|
|
56
|
-
task's `notes` field.
|
|
57
|
-
|
|
63
|
+
task's `notes` field. `notes` is **uma única string** (o schema declara
|
|
64
|
+
`"notes": { "type": "string" }`) — para registrar mais de um desvio, separe-os com
|
|
65
|
+
`;` ou `\n` dentro da mesma string. Um array é **rejeitado** pelo validador, com a
|
|
66
|
+
mensagem `'notes' must be a string`.
|
|
58
67
|
|
|
59
68
|
```json
|
|
60
69
|
{
|
|
61
70
|
"id": "T3",
|
|
62
|
-
"status": "
|
|
71
|
+
"status": "done",
|
|
63
72
|
"outputs": ["Features/User/Update/Handler.cs"],
|
|
64
|
-
"notes":
|
|
65
|
-
"deviation: split validation into a separate validator for testability",
|
|
66
|
-
"blocker-resolved: EF migration conflict fixed by regenerating the snapshot"
|
|
67
|
-
]
|
|
73
|
+
"notes": "deviation: split validation into a separate validator for testability; blocker-resolved: EF migration conflict fixed by regenerating the snapshot"
|
|
68
74
|
}
|
|
69
75
|
```
|
|
@@ -13,9 +13,9 @@ stacks:
|
|
|
13
13
|
|
|
14
14
|
**NEVER skip to code without a specification.** Every feature must progress through phases:
|
|
15
15
|
|
|
16
|
-
1. **Proposal** — Problem statement +
|
|
16
|
+
1. **Proposal** — Problem statement + scope + risks (`0-proposal/proposal.md`). No acceptance criteria: verifiable behaviour lives in the use cases.
|
|
17
17
|
2. **Design** (optional, UI-heavy) — Design system + mockups + flows (`1-design/`)
|
|
18
|
-
3. **Plan** —
|
|
18
|
+
3. **Plan** — Use cases first, then technical spec + mandate + tasks (`2-plan/usecases/UC-{nn}-{slug}.md`, `spec.md`, `mandate.md`, `tasks.json`). Each use case post-condition must be observable in a test and reaches the task's `doneCriteria` verbatim.
|
|
19
19
|
4. **Implement** — Code + recap (`3-implement/recap.md`)
|
|
20
20
|
5. **Review** — Review report + Gate 3 (`4-review/review-report.md`)
|
|
21
21
|
|
|
@@ -72,6 +72,39 @@ All outputs go in `.morph/features/{feature}/`:
|
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
+
## Formato obrigatório do `3-implement/recap.md`
|
|
76
|
+
|
|
77
|
+
> Esta exigência é **cobrada por hook** (`validate-completion`, no Stop). Ela está aqui, na regra
|
|
78
|
+
> sempre-ativa, e não só na skill `morph-implement`: descobrir o formato quando o hook dispara
|
|
79
|
+
> significa reescrever o recap depois de a fase já estar dada por encerrada.
|
|
80
|
+
|
|
81
|
+
**Para CADA task com `status: "done"` em `tasks.json`**, o recap precisa de um bloco próprio:
|
|
82
|
+
|
|
83
|
+
1. um heading `## T{N}` (o hook casa `^## T\d+` — uma linha de tabela `| T1 | … | done |` conta
|
|
84
|
+
como menção, **não** como bloco);
|
|
85
|
+
2. uma linha `**Standard:** {standard-id}` **dentro de 12 linhas** do heading. Sem standard que
|
|
86
|
+
cubra o caso, registre a decisão em `decisions.md` e cite
|
|
87
|
+
`**Standard:** ad-hoc (see decisions.md ADR-{N})`.
|
|
88
|
+
|
|
89
|
+
```markdown
|
|
90
|
+
## T{N} — {title}
|
|
91
|
+
|
|
92
|
+
**Standard:** {standard-id} (`.morph/framework/standards/{path}.md`)
|
|
93
|
+
**Persona:** {mecanismo real usado}
|
|
94
|
+
**Files:**
|
|
95
|
+
- `{path}`
|
|
96
|
+
|
|
97
|
+
{2-3 frases sobre o que foi feito}
|
|
98
|
+
|
|
99
|
+
**Issues encontrados:** {breve descrição ou "nenhum"}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
O `validate-completion` também acusa **drift**: uma task que aparece como concluída no recap mas
|
|
103
|
+
ainda está `pending`/`in_progress` no `tasks.json`. Template completo e regras de exceção em
|
|
104
|
+
`morph-implement` §6.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
75
108
|
## Checkpoints
|
|
76
109
|
|
|
77
110
|
Run a checkpoint every 3 completed tasks:
|
|
@@ -158,7 +191,7 @@ During implementation, these skills MUST be invoked at the specified triggers:
|
|
|
158
191
|
| Trigger | Skill | When |
|
|
159
192
|
|---------|-------|------|
|
|
160
193
|
| Before each task | `superpowers:test-driven-development` | Invoke BEFORE writing any code for a task |
|
|
161
|
-
| Before marking a task complete | `morph-verification-before-completion` | Invoke BEFORE setting a task's `status` to `
|
|
194
|
+
| Before marking a task complete | `morph-verification-before-completion` | Invoke BEFORE setting a task's `status` to `done` in `tasks.json` |
|
|
162
195
|
| On bug/unexpected | `superpowers:systematic-debugging` | Invoke IF a bug or unexpected behavior is encountered |
|
|
163
196
|
| After all tasks | `morph-post-implementation` | Invoke AFTER all tasks completed |
|
|
164
197
|
| Before PR | `morph-checklist` | Invoke BEFORE creating pull request |
|
|
@@ -222,7 +222,8 @@
|
|
|
222
222
|
"properties": {
|
|
223
223
|
"approved": { "type": "boolean" },
|
|
224
224
|
"timestamp": { "type": ["string", "null"], "description": "ISO timestamp of the approval/revocation, null while never evaluated." },
|
|
225
|
-
"approvedBy": { "type": ["string", "null"], "description": "\"auto\" (approve/advance --mode auto), \"manual\" (human in the loop), \"policy:{workType}\" (gate stamped at birth by `morph-spec create` per the work type's skippedGates — see src/core/work-types.js; the gate-guard hook only special-cases \"auto\", so policy-stamped gates stay silent to it), a --approver name, or null while never evaluated." }
|
|
225
|
+
"approvedBy": { "type": ["string", "null"], "description": "\"auto\" (approve/advance --mode auto), \"manual\" (human in the loop), \"policy:{workType}\" (gate stamped at birth by `morph-spec create` per the work type's skippedGates — see src/core/work-types.js; the gate-guard hook only special-cases \"auto\", so policy-stamped gates stay silent to it), a --approver name, or null while never evaluated." },
|
|
226
|
+
"selfAssessed": { "type": "boolean", "description": "Gate `review` SOMENTE. `true` quando o Gate 3 foi aprovado sem `4-review/evaluator-report.md`, via `morph-spec approve <f> review --self-assessed`. O score composto deveria vir do avaliador independente; esta flag registra a degradação em vez de deixá-la silenciosa, e aparece no `morph-spec list`." }
|
|
226
227
|
},
|
|
227
228
|
"additionalProperties": true
|
|
228
229
|
}
|
|
@@ -25,7 +25,12 @@
|
|
|
25
25
|
"status": { "type": "string", "enum": ["pending", "in_progress", "done", "skipped", "blocked"] },
|
|
26
26
|
"outputs": { "type": "array", "items": { "type": "string" } },
|
|
27
27
|
"notes": { "type": "string" },
|
|
28
|
-
"group": { "type": "string", "enum": ["backend", "frontend", "tests", "infra", "docs"] }
|
|
28
|
+
"group": { "type": "string", "enum": ["backend", "frontend", "tests", "infra", "docs"] },
|
|
29
|
+
"usecase": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"pattern": "^UC-\\d+$",
|
|
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
|
+
}
|
|
29
34
|
},
|
|
30
35
|
"additionalProperties": false
|
|
31
36
|
}
|
|
@@ -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.7.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.7.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.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -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.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-brainstorming
|
|
@@ -72,12 +72,13 @@ Para `workType: feature` (ou features sem `workType` — compat, ver MORPH.md §
|
|
|
72
72
|
- **Problema:** o que está sendo resolvido e para quem (máx. 2 parágrafos)
|
|
73
73
|
- **Escopo:** o que está incluído e o que está explicitamente excluído
|
|
74
74
|
- **Usuários e fluxos críticos:** quem usa e qual é o caminho principal
|
|
75
|
-
- **Critérios de aceite:** condições verificáveis que indicam que a feature está pronta
|
|
76
75
|
- **Stack:** confirme o stack padrão ou documente desvios aprovados
|
|
77
76
|
- **Riscos e dependências:** o que pode dar errado, o que esta feature depende
|
|
78
77
|
- **Compliance de stack:** "Em compliance" ou lista de desvios detectados e plano de resolução
|
|
79
78
|
|
|
80
|
-
**
|
|
79
|
+
**Não escreva critérios de aceite aqui.** O comportamento verificável mora nos **use cases**, destilados pela `morph-plan` na fase seguinte (`2-plan/usecases/UC-{nn}-{slug}.md`), onde cada pós-condição precisa ser observável em teste e chega literal ao `doneCriteria` das tasks. O `proposal.md` responde **o quê e por quê**; os use cases respondem **como o sistema se comporta**. Duplicar aqui produz duas fontes que divergem no primeiro ajuste.
|
|
80
|
+
|
|
81
|
+
**Se `workType: bug`** (modo bug-lean, ver nota no início da skill), substitua a lista acima pela estrutura enxuta: **Repro** (passos para reproduzir), **Diagnóstico** (o que foi observado/investigado), **Root cause provável** e **Escopo do fix** (o que muda, o que fica de fora) — sem as seções de negócio completas do fluxo `feature`. A condição verificável de que o bug foi corrigido vira a **pós-condição** que faltava no use case do acervo (`.morph/specs/usecases/`), corrigida ou acrescentada na fase plan.
|
|
81
82
|
|
|
82
83
|
Um bom proposal.md tem no máximo 2 páginas. Se está crescendo além disso, o escopo precisa ser cortado.
|
|
83
84
|
|
|
@@ -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.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-eval
|
|
@@ -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.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-hotfix
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: morph-plan
|
|
3
|
-
description: Guia a produção dos
|
|
3
|
+
description: Guia a produção dos artefatos de planejamento de uma feature (use cases, spec.md, mandate.md, tasks.json) e apresenta o Gate 2 trust-aware.
|
|
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.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
11
11
|
|
|
12
12
|
## Objetivo
|
|
13
13
|
|
|
14
|
-
Produzir `spec.md`, `mandate.md` e `tasks.json` com qualidade suficiente para abrir o Gate 2.
|
|
15
|
-
Os três artefatos
|
|
14
|
+
Produzir os **use cases**, `spec.md`, `mandate.md` e `tasks.json` com qualidade suficiente para abrir o Gate 2.
|
|
15
|
+
Os use cases definem o **comportamento**; os três artefatos seguintes definem o como e o quanto da feature.
|
|
16
|
+
|
|
17
|
+
A ordem importa e não é negociável: **comportamento antes de solução técnica**. Um `spec.md` escrito antes dos use cases resolve um problema que ainda não foi enunciado.
|
|
16
18
|
|
|
17
19
|
## Quando invocar
|
|
18
20
|
|
|
@@ -20,14 +22,70 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
20
22
|
|
|
21
23
|
## O que fazer
|
|
22
24
|
|
|
23
|
-
1. **Garanta que `proposal.md` e `stack-scan.md` estão no seu contexto antes de planejar.** Se não tem o conteúdo atual em contexto (sessão fresca, ou conteúdo possivelmente desatualizado), **use a tool Read para carregá-los** — não confie em cache de turnos anteriores. Na dúvida, Read. `.morph/features/{feature}/0-proposal/proposal.md` (escopo,
|
|
25
|
+
1. **Garanta que `proposal.md` e `stack-scan.md` estão no seu contexto antes de planejar.** Se não tem o conteúdo atual em contexto (sessão fresca, ou conteúdo possivelmente desatualizado), **use a tool Read para carregá-los** — não confie em cache de turnos anteriores. Na dúvida, Read. `.morph/features/{feature}/0-proposal/proposal.md` (problema, escopo, riscos — o comportamento é destilado aqui no passo 2, não vem pronto do proposal) e `.morph/features/{feature}/0-proposal/stack-scan.md` (contexto técnico detectado: greenfield vs brownfield, stack atual, drift). Em brownfield, o stack-scan informa decisões de spec/mandate — não invente padrões que conflitam com o detectado.
|
|
26
|
+
|
|
27
|
+
2. **Destile os use cases — ANTES de escrever `spec.md` e `tasks.json`.**
|
|
28
|
+
|
|
29
|
+
Um arquivo por use case em `.morph/features/{feature}/2-plan/usecases/UC-{nn}-{slug}.md`:
|
|
30
|
+
|
|
31
|
+
```markdown
|
|
32
|
+
---
|
|
33
|
+
id: UC-07
|
|
34
|
+
title: Listar veterinários com suas especialidades
|
|
35
|
+
actor: Visitante
|
|
36
|
+
status: draft
|
|
37
|
+
entities: [Vet, Specialty]
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Pré-condições
|
|
41
|
+
- Existe ao menos um veterinário cadastrado
|
|
42
|
+
|
|
43
|
+
## Cenário principal
|
|
44
|
+
1. Visitante acessa a listagem de veterinários
|
|
45
|
+
2. Sistema retorna os veterinários ordenados por sobrenome
|
|
46
|
+
3. Sistema exibe, para cada um, nome e especialidades
|
|
47
|
+
|
|
48
|
+
## Fluxos alternativos
|
|
49
|
+
- **A1** — Nenhum veterinário cadastrado → sistema exibe estado vazio
|
|
50
|
+
- **A2** — Veterinário sem especialidade → sistema exibe "sem especialidade"
|
|
51
|
+
|
|
52
|
+
## Pós-condições
|
|
53
|
+
- **P1** — A resposta contém todos os veterinários não arquivados
|
|
54
|
+
- **P2** — Cada veterinário traz suas especialidades em lista, nunca string concatenada
|
|
55
|
+
- **P3** — A ordenação por sobrenome é estável para nomes repetidos
|
|
56
|
+
|
|
57
|
+
## Delta de domínio
|
|
58
|
+
- `Vet.archivedAt` (novo, nullable) — habilita P1
|
|
59
|
+
- Nenhuma relação nova
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**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`.
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
**Identificador sequencial no projeto, não na feature.** Antes de numerar, leia o acervo `.morph/specs/usecases/` (se existir) mais os use cases das features ativas em `.morph/features/*/2-plan/usecases/`; tome o maior `UC-{nn}` e some um. Numerar por feature garante colisão no momento da promoção.
|
|
65
|
+
|
|
66
|
+
**Seções:** `Pré-condições` (o que já é verdade antes), `Cenário principal` (o caminho feliz, numerado), `Fluxos alternativos` (**A1**, **A2**… — cada alternativa aproxima uma user story; edge case de *comportamento* mora aqui, edge case *técnico* fica no `spec.md`), `Pós-condições` (**P1**, **P2**… — o que passa a ser verdade, cada uma testável) e `Delta de domínio` (**só o que muda**: campo novo, relação nova, invariante nova — o modelo completo continua sendo o código EF Core mais as migrations; se a feature não altera o domínio, escreva "nenhum").
|
|
67
|
+
|
|
68
|
+
**Quando é obrigatório** (ver MORPH.md §2.1 para o roteamento por tipo):
|
|
69
|
+
|
|
70
|
+
| Tipo | Use case |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `feature` | Obrigatório quando há comportamento observável. Feature puramente técnica (refactor, infra, migração) dispensa — **registre a dispensa em `decisions.md`**. |
|
|
73
|
+
| `bug` | Não cria novo: **copie** o `UC-{nn}-*.md` do acervo para `2-plan/usecases/` e **corrija ou adicione a pós-condição** lá — o bug é a pós-condição que faltava. |
|
|
74
|
+
| `chore` | Nenhum (já pula a fase plan por política). |
|
|
75
|
+
| `hotfix` | Não bloqueia. Referencia se existir; o pós-mortem pode virar pós-condição numa feature posterior. |
|
|
76
|
+
|
|
77
|
+
**Nunca edite `.morph/specs/usecases/` à mão.** A única via de escrita no acervo é a promoção do `finish`, que lê **só** de `2-plan/usecases/`: é ela que carimba `status: implemented`, sobrescreve por ID (mesmo com slug novo) e leva o arquivo no commit de arquivamento. Editar o acervo direto pula os três e cria uma segunda verdade fora do mecanismo. Mudar um UC existente = copiar para o `2-plan/usecases/` da feature, corrigir lá, e deixar o `finish` sobrescrever.
|
|
78
|
+
|
|
79
|
+
O use case fica **editável depois do Gate 2**, por design — é o que permite mudar o comportamento e re-rodar `implement`. A trilha de auditoria fica no git.
|
|
80
|
+
|
|
81
|
+
3. Produza `spec.md` em `.morph/features/{feature}/2-plan/`:
|
|
26
82
|
- Arquitetura da solução: quais componentes, como se conectam, fluxos de dados principais.
|
|
27
|
-
- Edge cases
|
|
83
|
+
- Edge cases **técnicos** e como serão tratados (os de comportamento estão nos fluxos alternativos dos use cases).
|
|
28
84
|
- Integrações externas e contratos esperados.
|
|
29
85
|
|
|
30
|
-
|
|
86
|
+
O `spec.md` responde **como** os use cases do passo 2 serão realizados. Não repita o comportamento aqui.
|
|
87
|
+
|
|
88
|
+
4. Produza `mandate.md` em `.morph/features/{feature}/2-plan/`:
|
|
31
89
|
- Stack exata do projeto (leia de `.morph/config/config.json` → `project.stack` ou do CLAUDE.md).
|
|
32
90
|
- Standards obrigatórios: liste os IDs relevantes de `.morph/framework/standards/STANDARDS.json`.
|
|
33
91
|
- Patterns proibidos — inclua sempre os defaults abaixo mais qualquer específico da feature:
|
|
@@ -41,7 +99,7 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
41
99
|
- **Features com agentes de IA (MAF):** se a feature envolve um agente (Microsoft Agent Framework), **use a tool Read** nos standards relevantes de `framework/standards/ai-agents/*.md` (ex.: `setup`, `agent-session`, `middleware-patterns`, `mcp-server`, `mcp-tools`, `multi-agent-patterns`, `production`) — filtre pelo que a feature exige via STANDARDS.json. Esses standards carregam as **convenções de projeto** (single-agent por padrão — Workflows só para fan-in/out real; provider, caching, observability, A2A) que uma fonte externa de docs (ex.: context7) não conhece. Padrão de divisão: **superfície da API** pode vir de docs ao vivo/context7 (mais frescas para um SDK em evolução), mas **as convenções vêm dos standards** — registre ambas no mandate. Não pule os `ai-agents/*.md` só porque o STANDARDS.json index lista que eles existem.
|
|
42
100
|
- Checklist de done com critérios verificáveis por tarefa.
|
|
43
101
|
|
|
44
|
-
|
|
102
|
+
5. Produza `tasks.json` em `.morph/features/{feature}/2-plan/` seguindo o schema em `.morph/framework/schemas/tasks.schema.json`.
|
|
45
103
|
|
|
46
104
|
**Formato obrigatório** (schema em `.morph/framework/schemas/tasks.schema.json` — o hook `tasks-json-guard` valida cada write):
|
|
47
105
|
|
|
@@ -56,10 +114,11 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
56
114
|
"description": "Detalhes — o que esta task realiza e por quê",
|
|
57
115
|
"dependencies": [],
|
|
58
116
|
"effort": "S",
|
|
59
|
-
"doneCriteria": "
|
|
117
|
+
"doneCriteria": "P2 — Cada veterinário traz suas especialidades em lista, nunca string concatenada",
|
|
60
118
|
"status": "pending",
|
|
61
119
|
"outputs": [],
|
|
62
|
-
"group": "backend"
|
|
120
|
+
"group": "backend",
|
|
121
|
+
"usecase": "UC-07"
|
|
63
122
|
}
|
|
64
123
|
]
|
|
65
124
|
}
|
|
@@ -72,12 +131,19 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
72
131
|
- `status` inicia sempre como `pending`.
|
|
73
132
|
- `group` (opcional): backend, frontend, tests, infra, docs. Default backend.
|
|
74
133
|
- `outputs` (opcional): array de paths que serão criados/modificados.
|
|
134
|
+
- `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.
|
|
135
|
+
|
|
136
|
+
**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
|
+
|
|
138
|
+
**`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. O que o `dag` **não** julga é se a pós-condição é observável em teste: isso é semântico e continua sendo trabalho seu no Gate 2 e do evaluator.
|
|
139
|
+
|
|
140
|
+
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.
|
|
75
141
|
|
|
76
142
|
**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).
|
|
77
143
|
|
|
78
144
|
Antes de retornar, valide rodando `morph-spec dag {feature}` — o comando parseia e valida o schema do tasks.json (ids únicos `T\d+`, enums de `status`/`effort`, `dependencies` válidas) e já imprime a análise de DAG que a `/morph-apply` vai usar. Erros de schema abortam com a lista do que corrigir.
|
|
79
145
|
|
|
80
|
-
|
|
146
|
+
6. Apresente o Gate 2 segundo trust mode.
|
|
81
147
|
|
|
82
148
|
Você pode embutir o diagrama do DAG do plano via `morph-spec dag {feature} --mermaid` (flowchart Mermaid) para visualização no gate.
|
|
83
149
|
|
|
@@ -124,16 +190,20 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
124
190
|
|
|
125
191
|
## Output esperado
|
|
126
192
|
|
|
193
|
+
- `.morph/features/{feature}/2-plan/usecases/UC-{nn}-{slug}.md` — um arquivo por use case, com pós-condições testáveis
|
|
127
194
|
- `.morph/features/{feature}/2-plan/spec.md` — especificação técnica completa
|
|
128
195
|
- `.morph/features/{feature}/2-plan/mandate.md` — contrato de implementação
|
|
129
|
-
- `.morph/features/{feature}/2-plan/tasks.json` — task list estruturada válida contra schema
|
|
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
|
|
130
197
|
- Gate 2 registrado aprovado em `.morph/features/{feature}/feature.json` (autoritativo)
|
|
131
198
|
|
|
199
|
+
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.
|
|
200
|
+
|
|
132
201
|
## Referências
|
|
133
202
|
|
|
134
203
|
- `.morph/framework/MORPH.md` — seções 2 (Fases), 3 (Contratos de Output), 5 (Gates), Trust Mode
|
|
135
204
|
- `.morph/framework/standards/STANDARDS.json` — para identificar standards relevantes ao mandate
|
|
136
205
|
- `.morph/framework/schemas/tasks.schema.json` — schema obrigatório de tasks.json
|
|
206
|
+
- `.morph/specs/usecases/` — acervo permanente de use cases do projeto (leia antes de numerar; em `bug`, é onde mora o UC a corrigir)
|
|
137
207
|
- `.morph/features/{feature}/0-proposal/proposal.md` — escopo da feature
|
|
138
208
|
- `.morph/features/{feature}/0-proposal/stack-scan.md` — contexto técnico detectado
|
|
139
209
|
- `.morph/framework/schemas/tasks.schema.json` — schema e enums de tasks.json
|
|
@@ -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.7.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.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -74,6 +74,12 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
74
74
|
- **Leia primeiro** `.morph/features/{feature}/4-review/evaluator-report.md` — é o valor de retorno autoritativo, não a mensagem de texto do dispatch (que é cortesia, útil só quando chega inteira). Os scores por dimensão e o composto do review-report vêm **desse arquivo**, não da sua auto-avaliação. Se o arquivo vier ausente, oco, ou sem os scores por dimensão, registre a falha de protocolo na seção de método do review-report e re-despache.
|
|
75
75
|
|
|
76
76
|
5. Produza `review-report.md` em `.morph/features/{feature}/4-review/` com:
|
|
77
|
+
- **`**Reviewed-SHA:** {git rev-parse HEAD}`** — o commit que você revisou, **obrigatório**.
|
|
78
|
+
Um review revisa uma ÁRVORE, e a árvore anda: sem esse carimbo, um relatório escrito antes
|
|
79
|
+
da última task fica no repo afirmando números e cobertura que já não valem. O
|
|
80
|
+
`morph-spec approve {feature} review` compara o carimbo com o HEAD e **recusa** se divergirem
|
|
81
|
+
(aconteceu de verdade: relatório de `b30ac6e` sobrevivendo à T3, que inverteu o default de
|
|
82
|
+
uma regra de negócio, e ainda dizendo `976/976` quando eram 982).
|
|
77
83
|
- Scores por dimensão (arquitetura, contratos, qualidade, cobertura)
|
|
78
84
|
- Score composto final
|
|
79
85
|
- Lista de itens bloqueantes (se houver) — com descrição e critério de resolução
|
|
@@ -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.7.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.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|