@polymorphism-tech/morph-spec 7.8.1 → 7.9.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 +30 -1
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +3 -1
- package/framework/MORPH.md +4 -3
- package/framework/commands/commit.md +166 -166
- package/framework/commands/morph-infra.md +215 -215
- package/framework/commands/morph-preflight.md +205 -205
- package/framework/commands/morph-troubleshoot.md +124 -124
- package/framework/evals/trace-schema.md +22 -12
- package/framework/hooks/README.md +2 -0
- package/framework/hooks/claude-code/core/subagent-report-guard.js +95 -0
- package/framework/hooks/claude-code/core/trace-autogen.js +183 -0
- package/framework/hooks/claude-code/statusline.py +679 -679
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/activity-logger.js +160 -160
- package/framework/hooks/shared/hook-response.js +45 -45
- package/framework/hooks/shared/payload-utils.js +78 -78
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/hooks/shared/tasks-validator.js +47 -4
- package/framework/rubrics/backend.json +41 -41
- package/framework/rules/ai-agents-standards.md +45 -45
- package/framework/rules/dotnet-templates.md +86 -86
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +27 -27
- package/framework/rules/neon-standards.md +25 -25
- package/framework/rules/nextjs-standards.md +20 -20
- package/framework/rules/nextjs-templates.md +40 -40
- package/framework/schemas/feature.schema.json +128 -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 +23 -10
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +3 -6
- package/framework/skills/core/morph-implement/SKILL.md +28 -15
- package/framework/skills/core/morph-plan/SKILL.md +1 -1
- package/framework/skills/core/morph-proposal/SKILL.md +2 -2
- package/framework/skills/core/morph-review/SKILL.md +2 -6
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +275 -275
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -113
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/index.js +13 -13
- package/src/commands/mcp/mcp-setup.js +519 -519
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +23 -18
- package/src/commands/project/update.js +16 -9
- package/src/commands/state/create.js +58 -0
- package/src/commands/state/gate-decision.js +99 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +55 -0
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/LiveState.jsx +239 -239
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +41 -41
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/scanner/standards-scanner.js +26 -26
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/metrics-aggregator.js +56 -56
- package/src/lib/agents/metrics-calculator.js +125 -125
- package/src/lib/agents/output-collector.js +73 -73
- package/src/lib/detectors/claude-config-detector.js +152 -152
- package/src/lib/detectors/design-system-detector.js +231 -231
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +639 -639
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/index.js +13 -13
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +81 -81
- package/src/lib/validators/architecture/architecture-validator.js +259 -259
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/packages/package-validator.js +347 -347
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +349 -349
- package/src/scripts/setup-infra.js +385 -383
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +25 -10
- package/src/utils/banner.js +37 -4
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/hooks-installer.js +32 -4
- package/src/utils/skills-installer.js +174 -172
|
@@ -45,22 +45,32 @@ Um arquivo por fase. Se a fase for executada em múltiplas sessões, o mesmo arq
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
##
|
|
48
|
+
## Como o trace é gerado (automático — não escreva à mão)
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
| Eval executada (`morph-eval`) | Preencher `eval_score` e `eval_dimensions` da task correspondente |
|
|
54
|
-
| Fase concluída | Preencher `completed_at` e `phase_summary` |
|
|
50
|
+
O trace é **gerado pelo hook `trace-autogen`** (PostToolUse em Write|Edit) a cada
|
|
51
|
+
write de `2-plan/tasks.json` — o último write da cerimônia de cada task. As skills
|
|
52
|
+
(`morph-implement`, `morph-eval`, `morph-review`) **não escrevem o trace**.
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
| Fonte | O que alimenta |
|
|
55
|
+
|-------|----------------|
|
|
56
|
+
| `tasks.json` | conjunto de tasks, `status` (done→completed, skipped→skipped, blocked→failed; pending/in_progress ficam fora), `outputs` → `files_modified` |
|
|
57
|
+
| `recap.md` | `**Standard:**` → `standards_consulted`; `**Persona:**` → `agent_persona` + `sub_agent_dispatched` (`morph-{id}`/`general-purpose` = true) |
|
|
58
|
+
| `feature.json → taskScores` (gravado por `morph-spec score`) | `eval_score` (lastScore) e `eval_dimensions` (camelCase → snake_case) |
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
`completed_at` e `phase_summary` são preenchidos quando nenhuma task resta
|
|
61
|
+
`pending`/`in_progress`. O arquivo existente da mesma feature+fase é atualizado
|
|
62
|
+
in place (`session_id`/`started_at` preservados entre sessões).
|
|
63
|
+
|
|
64
|
+
**Se o trace parecer incompleto, corrija a FONTE** (um `morph-spec score`
|
|
65
|
+
faltante, um recap sem o bloco `## T{N}`), nunca o trace.
|
|
66
|
+
|
|
67
|
+
## Regras de geração
|
|
68
|
+
|
|
69
|
+
- Se um campo não tiver valor disponível, o hook usa `null` — nunca omite o campo.
|
|
70
|
+
- Estrutura completa mantida para compatibilidade com análise de meta-harness.
|
|
71
|
+
- `session_id` usa o formato `YYYY-MM-DD-HH-MM-SS` da primeira geração.
|
|
62
72
|
- `standards_consulted` contém os `id`s do STANDARDS.json, não os paths.
|
|
63
|
-
- `files_modified` usa paths relativos à raiz do repositório.
|
|
73
|
+
- `files_modified` usa paths relativos à raiz do repositório (vem de `outputs`).
|
|
64
74
|
|
|
65
75
|
## Motivação
|
|
66
76
|
|
|
@@ -47,10 +47,12 @@ framework/hooks/
|
|
|
47
47
|
| **PreToolUse** (Bash) | pre-bash-denylist.js | Block | Blocks `rm -rf .morph/` and direct state edits |
|
|
48
48
|
| **PostToolUse** (Write\|Edit) | post-edit-typecheck.js | Advisory | Debounced `tsc --noEmit` after TS/TSX edits; silent on success |
|
|
49
49
|
| **PostToolUse** (Write\|Edit) | tasks-json-guard.js | Advisory | Validates the written tasks.json schema (status/effort enums); warns so the LLM fixes it immediately |
|
|
50
|
+
| **PostToolUse** (Write\|Edit) | trace-autogen.js | State sync | Auto-generates `.morph/traces/YYYY-MM-DD-{feature}-implement.json` from tasks.json + recap.md + taskScores on every tasks.json write — the skills never write traces by hand |
|
|
50
51
|
| **PostToolUse** (Write\|Edit\|AskUserQuestion) | state-sync.js | State sync | Autonomous state updates (feature register, output tracking, gate approval) |
|
|
51
52
|
| **PostToolUse** (Write\|Edit\|AskUserQuestion) | gate-guard.js | Advisory | Warns (never blocks) when a gate action disagrees with the persisted gateDecisions; logs one `coherent`/`divergent` line per check in `.morph/logs/activity.json` (issue #63) |
|
|
52
53
|
| **PostToolUse** (all tools) | loop-detect.js | Advisory | Warns when the last 8 actions collapse to ≤2 unique signatures |
|
|
53
54
|
| **Stop** | validate-completion.js | Advisory | Warns about incomplete tasks/missing outputs/recap drift; build check; session snapshot |
|
|
55
|
+
| **Stop** / **SubagentStop** | subagent-report-guard.js | Block (once) | Blocks a dispatched sub-agent stopping without SendMessage (clause 1 of the dispatch protocol) and injects the exact instruction; `stop_hook_active` prevents loops; inert in the orchestrator's session |
|
|
54
56
|
|
|
55
57
|
## Design Principles
|
|
56
58
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* subagent-report-guard.js — Stop / SubagentStop hook
|
|
4
|
+
*
|
|
5
|
+
* Enforces clause 1 of the morph-spec dispatch protocol: a dispatched
|
|
6
|
+
* sub-agent's FINAL act is SendMessage to the orchestrator (main). Plain text
|
|
7
|
+
* in the last message never reaches the orchestrator, and an idle agent forces
|
|
8
|
+
* the costly nag-and-wait cycle (observed 2/2 in real apply sessions —
|
|
9
|
+
* MORPH_GHLBrain 2026-07-11).
|
|
10
|
+
*
|
|
11
|
+
* Detection: the session transcript (JSONL) contains the dispatch-protocol
|
|
12
|
+
* marker ("Protocolo obrigatório") inside a USER message — that is how the
|
|
13
|
+
* dispatch prompt arrives in the sub-agent's own transcript. In the
|
|
14
|
+
* ORCHESTRATOR's transcript the marker only appears inside assistant tool_use
|
|
15
|
+
* input (the Agent call that did the dispatch), so the orchestrator is never
|
|
16
|
+
* blocked by this guard. A SendMessage tool_use anywhere in an assistant
|
|
17
|
+
* message counts as the report.
|
|
18
|
+
*
|
|
19
|
+
* Action: block the stop ONCE ({"decision":"block"}) with the exact
|
|
20
|
+
* instruction to call SendMessage — the agent continues and reports.
|
|
21
|
+
* stop_hook_active guards against blocking loops. Fail-open on any error.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { readFileSync, existsSync } from 'fs';
|
|
25
|
+
import { stateExists } from '../../shared/state-reader.js';
|
|
26
|
+
import { block, pass } from '../../shared/hook-response.js';
|
|
27
|
+
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
28
|
+
|
|
29
|
+
const PROTOCOL_MARKER = 'Protocolo obrigatório';
|
|
30
|
+
|
|
31
|
+
function readStdin() {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
34
|
+
} catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function projectDir() {
|
|
40
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Scan a transcript for the dispatch-protocol marker (user messages only) and
|
|
45
|
+
* for a SendMessage tool call (assistant messages).
|
|
46
|
+
*
|
|
47
|
+
* @param {string} path - JSONL transcript path
|
|
48
|
+
* @returns {{ dispatched: boolean, reported: boolean }}
|
|
49
|
+
*/
|
|
50
|
+
function scanTranscript(path) {
|
|
51
|
+
const raw = readFileSync(path, 'utf8');
|
|
52
|
+
let dispatched = false;
|
|
53
|
+
let reported = false;
|
|
54
|
+
for (const line of raw.split('\n')) {
|
|
55
|
+
if (!line.trim()) continue;
|
|
56
|
+
let role = null;
|
|
57
|
+
try {
|
|
58
|
+
const entry = JSON.parse(line);
|
|
59
|
+
role = entry?.message?.role ?? entry?.type ?? null;
|
|
60
|
+
} catch {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!dispatched && role === 'user' && line.includes(PROTOCOL_MARKER)) dispatched = true;
|
|
64
|
+
if (!reported && role === 'assistant' && line.includes('"name":"SendMessage"')) reported = true;
|
|
65
|
+
}
|
|
66
|
+
return { dispatched, reported };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
if (!stateExists(projectDir())) pass();
|
|
71
|
+
|
|
72
|
+
const input = readStdin();
|
|
73
|
+
|
|
74
|
+
// Never loop: a previous block already forced one continuation — let this
|
|
75
|
+
// stop through even if the agent still failed to report.
|
|
76
|
+
if (input.stop_hook_active) pass();
|
|
77
|
+
|
|
78
|
+
const transcriptPath = input.transcript_path;
|
|
79
|
+
if (!transcriptPath || !existsSync(transcriptPath)) pass();
|
|
80
|
+
|
|
81
|
+
const { dispatched, reported } = scanTranscript(transcriptPath);
|
|
82
|
+
if (!dispatched || reported) pass();
|
|
83
|
+
|
|
84
|
+
logHookActivity('subagent-report-guard', input.hook_event_name || 'Stop', 'blocked', projectDir());
|
|
85
|
+
block(
|
|
86
|
+
'[morph-spec] PROTOCOLO DE RETORNO — você foi despachado com o protocolo obrigatório ' +
|
|
87
|
+
'mas ainda não chamou SendMessage. Sua última mensagem em texto NÃO chega ao orquestrador. ' +
|
|
88
|
+
'Antes de encerrar, chame a tool SendMessage com to:"main" e o relatório completo no content: ' +
|
|
89
|
+
'(1) o que foi feito em 2-3 frases, (2) arquivos modificados, (3) problemas encontrados e ' +
|
|
90
|
+
'como foram resolvidos, (4) auto-score 0-10. O SendMessage é o seu ato final.'
|
|
91
|
+
);
|
|
92
|
+
} catch {
|
|
93
|
+
// Fail-open
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* trace-autogen.js — PostToolUse hook (Write|Edit)
|
|
4
|
+
*
|
|
5
|
+
* Auto-generates the execution trace
|
|
6
|
+
* `.morph/traces/YYYY-MM-DD-{feature}-implement.json` from tasks.json +
|
|
7
|
+
* recap.md + feature.json taskScores whenever tasks.json is written during
|
|
8
|
+
* the implement phase.
|
|
9
|
+
*
|
|
10
|
+
* This removes the manual trace-writing step from the skills (friction item
|
|
11
|
+
* observed in MORPH_GHLBrain 2026-07-11: 5 writes per task). The tasks.json
|
|
12
|
+
* status flip — the last write of every task's ceremony — now produces the
|
|
13
|
+
* trace for free: deterministic, idempotent, always in sync with the real
|
|
14
|
+
* on-disk state. The skills must NOT write traces by hand anymore.
|
|
15
|
+
*
|
|
16
|
+
* Sources:
|
|
17
|
+
* - tasks.json → task set, status (done→completed, skipped→skipped,
|
|
18
|
+
* blocked→failed; pending/in_progress stay out), outputs
|
|
19
|
+
* - recap.md → per-task `**Standard:**` id and `**Persona:**` line
|
|
20
|
+
* - feature.json → taskScores (lastScore → eval_score, dimensions →
|
|
21
|
+
* eval_dimensions, camelCase → snake_case)
|
|
22
|
+
*
|
|
23
|
+
* The most recent existing `*-{feature}-implement.json` is updated in place
|
|
24
|
+
* (session_id/started_at preserved); a new dated file is created otherwise.
|
|
25
|
+
* `completed_at` + phase_summary are stamped when no task is left
|
|
26
|
+
* pending/in_progress. Fail-open: any error exits 0 silently.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
30
|
+
import { join } from 'path';
|
|
31
|
+
import { readStdin } from '../../shared/stdin-reader.js';
|
|
32
|
+
import { stateExists } from '../../shared/state-reader.js';
|
|
33
|
+
import { getFilePath } from '../../shared/payload-utils.js';
|
|
34
|
+
|
|
35
|
+
function projectDir() {
|
|
36
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractFeature(filePath) {
|
|
40
|
+
const normalized = String(filePath).replace(/\\/g, '/');
|
|
41
|
+
const match = normalized.match(/\.morph\/features\/([^/]+)\/2-plan\/tasks\.json$/);
|
|
42
|
+
return match ? match[1] : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse recap.md task blocks (`## T{N} — ...`) into
|
|
47
|
+
* { [id]: { standard, persona } }. Best-effort: missing fields stay null.
|
|
48
|
+
*/
|
|
49
|
+
function parseRecap(raw) {
|
|
50
|
+
const map = {};
|
|
51
|
+
const blocks = String(raw).split(/^## (?=T\d+)/m).slice(1);
|
|
52
|
+
for (const block of blocks) {
|
|
53
|
+
const id = (block.match(/^(T\d+)/) ?? [])[1];
|
|
54
|
+
if (!id) continue;
|
|
55
|
+
const standard = (block.match(/\*\*Standard:\*\*\s*([^\s(]+)/) ?? [])[1] ?? null;
|
|
56
|
+
const persona = (block.match(/\*\*Persona:\*\*\s*(.+)$/m) ?? [])[1]?.trim() ?? null;
|
|
57
|
+
map[id] = { standard, persona };
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** session_id in the trace-schema format YYYY-MM-DD-HH-MM-SS. */
|
|
63
|
+
function sessionId(d) {
|
|
64
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
65
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}-${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** tasks.json status → trace-schema status. Unsettled statuses are omitted. */
|
|
69
|
+
const STATUS_MAP = { done: 'completed', skipped: 'skipped', blocked: 'failed' };
|
|
70
|
+
|
|
71
|
+
async function main() {
|
|
72
|
+
const cwd = projectDir();
|
|
73
|
+
if (!stateExists(cwd)) return;
|
|
74
|
+
|
|
75
|
+
const payload = await readStdin();
|
|
76
|
+
if (!payload) return;
|
|
77
|
+
|
|
78
|
+
const filePath = getFilePath(payload);
|
|
79
|
+
const feature = filePath ? extractFeature(filePath) : null;
|
|
80
|
+
if (!feature) return;
|
|
81
|
+
|
|
82
|
+
const featureDir = join(cwd, '.morph', 'features', feature);
|
|
83
|
+
const tasksPath = join(featureDir, '2-plan', 'tasks.json');
|
|
84
|
+
if (!existsSync(tasksPath)) return;
|
|
85
|
+
const tasksDoc = JSON.parse(readFileSync(tasksPath, 'utf8'));
|
|
86
|
+
const tasks = Array.isArray(tasksDoc.tasks) ? tasksDoc.tasks : [];
|
|
87
|
+
|
|
88
|
+
let recap = {};
|
|
89
|
+
const recapPath = join(featureDir, '3-implement', 'recap.md');
|
|
90
|
+
if (existsSync(recapPath)) {
|
|
91
|
+
try { recap = parseRecap(readFileSync(recapPath, 'utf8')); } catch { recap = {}; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let taskScores = {};
|
|
95
|
+
const featureJsonPath = join(featureDir, 'feature.json');
|
|
96
|
+
if (existsSync(featureJsonPath)) {
|
|
97
|
+
try { taskScores = JSON.parse(readFileSync(featureJsonPath, 'utf8')).taskScores ?? {}; } catch { taskScores = {}; }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const tracesDir = join(cwd, '.morph', 'traces');
|
|
101
|
+
mkdirSync(tracesDir, { recursive: true });
|
|
102
|
+
|
|
103
|
+
// Multi-session phases update the same file incrementally (trace-schema):
|
|
104
|
+
// reuse the most recent trace for this feature+phase if one exists.
|
|
105
|
+
const suffix = `-${feature}-implement.json`;
|
|
106
|
+
const existing = readdirSync(tracesDir)
|
|
107
|
+
.filter(f => /^\d{4}-\d{2}-\d{2}-/.test(f) && f.endsWith(suffix))
|
|
108
|
+
.sort()
|
|
109
|
+
.pop();
|
|
110
|
+
|
|
111
|
+
let previous = null;
|
|
112
|
+
if (existing) {
|
|
113
|
+
try { previous = JSON.parse(readFileSync(join(tracesDir, existing), 'utf8')); } catch { previous = null; }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const now = new Date();
|
|
117
|
+
const fileName = existing ?? `${now.toISOString().slice(0, 10)}${suffix}`;
|
|
118
|
+
|
|
119
|
+
const entries = tasks
|
|
120
|
+
.filter(t => STATUS_MAP[t.status])
|
|
121
|
+
.map(t => {
|
|
122
|
+
const recapEntry = recap[t.id] ?? {};
|
|
123
|
+
const score = taskScores[t.id];
|
|
124
|
+
const dims = score?.dimensions;
|
|
125
|
+
return {
|
|
126
|
+
id: t.id,
|
|
127
|
+
title: t.title ?? null,
|
|
128
|
+
status: STATUS_MAP[t.status],
|
|
129
|
+
standards_consulted: recapEntry.standard ? [recapEntry.standard] : [],
|
|
130
|
+
agent_persona: recapEntry.persona ?? null,
|
|
131
|
+
sub_agent_dispatched: recapEntry.persona
|
|
132
|
+
? /^morph-|general-purpose/.test(recapEntry.persona)
|
|
133
|
+
: false,
|
|
134
|
+
eval_score: score?.lastScore ?? null,
|
|
135
|
+
eval_dimensions: dims ? {
|
|
136
|
+
architecture: dims.architecture ?? null,
|
|
137
|
+
contracts: dims.contracts ?? null,
|
|
138
|
+
code_quality: dims.codeQuality ?? null,
|
|
139
|
+
test_coverage: dims.testCoverage ?? null,
|
|
140
|
+
} : null,
|
|
141
|
+
files_modified: Array.isArray(t.outputs) ? t.outputs.filter(o => typeof o === 'string') : [],
|
|
142
|
+
duration_seconds: null,
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const allSettled = tasks.length > 0 &&
|
|
147
|
+
tasks.every(t => t.status !== 'pending' && t.status !== 'in_progress');
|
|
148
|
+
const scores = entries.map(e => e.eval_score).filter(s => typeof s === 'number');
|
|
149
|
+
|
|
150
|
+
const standardCounts = {};
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
for (const std of entry.standards_consulted) {
|
|
153
|
+
standardCounts[std] = (standardCounts[std] ?? 0) + 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const mostUsed = Object.entries(standardCounts)
|
|
157
|
+
.sort((a, b) => b[1] - a[1])
|
|
158
|
+
.slice(0, 3)
|
|
159
|
+
.map(([std]) => std);
|
|
160
|
+
|
|
161
|
+
const trace = {
|
|
162
|
+
schema_version: '1.0',
|
|
163
|
+
session_id: previous?.session_id ?? sessionId(now),
|
|
164
|
+
feature,
|
|
165
|
+
phase: 'implement',
|
|
166
|
+
started_at: previous?.started_at ?? now.toISOString(),
|
|
167
|
+
completed_at: allSettled ? (previous?.completed_at ?? now.toISOString()) : null,
|
|
168
|
+
tasks: entries,
|
|
169
|
+
phase_summary: {
|
|
170
|
+
tasks_completed: entries.filter(e => e.status === 'completed').length,
|
|
171
|
+
tasks_failed: entries.filter(e => e.status === 'failed').length,
|
|
172
|
+
avg_eval_score: scores.length
|
|
173
|
+
? Math.round((scores.reduce((a, b) => a + b, 0) / scores.length) * 10) / 10
|
|
174
|
+
: null,
|
|
175
|
+
standards_most_used: mostUsed,
|
|
176
|
+
sub_agents_dispatched: entries.filter(e => e.sub_agent_dispatched).length,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
writeFileSync(join(tracesDir, fileName), JSON.stringify(trace, null, 2), 'utf8');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
main().catch(() => { /* fail-open */ }).finally(() => process.exit(0));
|