@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.
Files changed (183) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +30 -1
  3. package/bin/validate.js +379 -379
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +3 -1
  8. package/framework/MORPH.md +4 -3
  9. package/framework/commands/commit.md +166 -166
  10. package/framework/commands/morph-infra.md +215 -215
  11. package/framework/commands/morph-preflight.md +205 -205
  12. package/framework/commands/morph-troubleshoot.md +124 -124
  13. package/framework/evals/trace-schema.md +22 -12
  14. package/framework/hooks/README.md +2 -0
  15. package/framework/hooks/claude-code/core/subagent-report-guard.js +95 -0
  16. package/framework/hooks/claude-code/core/trace-autogen.js +183 -0
  17. package/framework/hooks/claude-code/statusline.py +679 -679
  18. package/framework/hooks/claude-code/statusline.sh +11 -11
  19. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  20. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  21. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  22. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  23. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  24. package/framework/hooks/shared/activity-logger.js +160 -160
  25. package/framework/hooks/shared/hook-response.js +45 -45
  26. package/framework/hooks/shared/payload-utils.js +78 -78
  27. package/framework/hooks/shared/stdin-reader.js +26 -26
  28. package/framework/hooks/shared/tasks-validator.js +47 -4
  29. package/framework/rubrics/backend.json +41 -41
  30. package/framework/rules/ai-agents-standards.md +45 -45
  31. package/framework/rules/dotnet-templates.md +86 -86
  32. package/framework/rules/frontend-standards.md +13 -13
  33. package/framework/rules/infrastructure-standards.md +27 -27
  34. package/framework/rules/neon-standards.md +25 -25
  35. package/framework/rules/nextjs-standards.md +20 -20
  36. package/framework/rules/nextjs-templates.md +40 -40
  37. package/framework/schemas/feature.schema.json +128 -0
  38. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  39. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  40. package/framework/skills/core/morph-apply/SKILL.md +23 -10
  41. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  42. package/framework/skills/core/morph-eval/SKILL.md +3 -6
  43. package/framework/skills/core/morph-implement/SKILL.md +28 -15
  44. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  45. package/framework/skills/core/morph-proposal/SKILL.md +2 -2
  46. package/framework/skills/core/morph-review/SKILL.md +2 -6
  47. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  48. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  49. package/framework/standards/ai-agents/agent-session.md +160 -160
  50. package/framework/standards/ai-agents/batching.md +193 -193
  51. package/framework/standards/ai-agents/context-providers.md +188 -188
  52. package/framework/standards/ai-agents/mcp-server.md +250 -250
  53. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  54. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  55. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  56. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  57. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  58. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  59. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  60. package/framework/standards/ai-agents/production.md +275 -275
  61. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  62. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  63. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  64. package/framework/standards/ai-agents/structured-output.md +249 -249
  65. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  66. package/framework/templates/.idea/morph-templates.xml +92 -0
  67. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  68. package/framework/templates/IDE-SNIPPETS.md +262 -262
  69. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  70. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  71. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  72. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  73. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  74. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  75. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  76. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  77. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  78. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  79. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  80. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  81. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  82. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  83. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  84. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  85. package/framework/templates/code/typescript/contracts.ts +167 -167
  86. package/framework/templates/docs/clarifications.md +253 -253
  87. package/framework/templates/docs/schema-analysis.md +119 -119
  88. package/framework/templates/docs/ui-components.md +52 -52
  89. package/framework/templates/docs/ui-design-system.md +76 -76
  90. package/framework/templates/docs/ui-flows.md +167 -167
  91. package/framework/templates/docs/ui-mockups.md +98 -98
  92. package/framework/templates/feature/decisions.md +122 -122
  93. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  94. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  95. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  96. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  97. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  98. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  99. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  100. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  101. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  102. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  103. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  104. package/framework/templates/infrastructure/github/README.md +56 -56
  105. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  106. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  107. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  108. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  109. package/framework/templates/integrations/asaas-client.cs +387 -387
  110. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  111. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  112. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  113. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -113
  114. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  115. package/framework/templates/project/validate.js +120 -120
  116. package/framework/templates/state.template.json +17 -17
  117. package/framework/templates/ui/design-system.css +226 -226
  118. package/package.json +1 -1
  119. package/src/commands/dashboard/dashboard.js +53 -53
  120. package/src/commands/index.js +13 -13
  121. package/src/commands/mcp/mcp-setup.js +519 -519
  122. package/src/commands/project/checkpoint.js +209 -209
  123. package/src/commands/project/doctor.js +23 -18
  124. package/src/commands/project/update.js +16 -9
  125. package/src/commands/state/create.js +58 -0
  126. package/src/commands/state/gate-decision.js +99 -0
  127. package/src/core/state/phase-state-machine.js +197 -197
  128. package/src/core/state/state-manager.js +55 -0
  129. package/src/core/templates/template-renderer.js +477 -477
  130. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  131. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  132. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  133. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  134. package/src/dashboard/app/main.jsx +13 -13
  135. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  136. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  137. package/src/dashboard/app/pages/LiveState.jsx +239 -239
  138. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  139. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  140. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  141. package/src/dashboard/app/pages/Standards.jsx +332 -332
  142. package/src/dashboard/app/styles/index.css +47 -47
  143. package/src/dashboard/index.html +12 -12
  144. package/src/dashboard/package.json +24 -24
  145. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  146. package/src/dashboard/scanner/phases-scanner.js +15 -15
  147. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  148. package/src/dashboard/scanner/rules-scanner.js +37 -37
  149. package/src/dashboard/scanner/skills-scanner.js +43 -43
  150. package/src/dashboard/scanner/squads-scanner.js +13 -13
  151. package/src/dashboard/scanner/standards-scanner.js +26 -26
  152. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  153. package/src/dashboard/vite.config.js +24 -24
  154. package/src/lib/agents/metrics-aggregator.js +56 -56
  155. package/src/lib/agents/metrics-calculator.js +125 -125
  156. package/src/lib/agents/output-collector.js +73 -73
  157. package/src/lib/detectors/claude-config-detector.js +152 -152
  158. package/src/lib/detectors/design-system-detector.js +231 -231
  159. package/src/lib/detectors/index.js +217 -217
  160. package/src/lib/generators/architecture-scanner.js +639 -639
  161. package/src/lib/generators/design-system-generator.js +508 -508
  162. package/src/lib/generators/index.js +13 -13
  163. package/src/lib/generators/settings-generator.js +209 -209
  164. package/src/lib/hooks/hook-executor.js +167 -167
  165. package/src/lib/installers/mcp-installer.js +719 -719
  166. package/src/lib/tasks/task-classifier.js +30 -30
  167. package/src/lib/tasks/task-parser.js +209 -209
  168. package/src/lib/tasks/test-runner.js +81 -81
  169. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  170. package/src/lib/validators/checkpoint-persistence.js +39 -39
  171. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  172. package/src/lib/validators/css/css-validator.js +340 -340
  173. package/src/lib/validators/packages/package-validator.js +347 -347
  174. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  175. package/src/lib/validators/shared/issue-counter.js +14 -14
  176. package/src/lib/validators/validation-runner.js +349 -349
  177. package/src/scripts/setup-infra.js +385 -383
  178. package/src/utils/agent-id-mapper.js +11 -11
  179. package/src/utils/agents-installer.js +25 -10
  180. package/src/utils/banner.js +37 -4
  181. package/src/utils/claude-settings-manager.js +300 -300
  182. package/src/utils/hooks-installer.js +32 -4
  183. 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
- ## Quando escrever
48
+ ## Como o trace é gerado (automático — não escreva à mão)
49
49
 
50
- | Evento | O que logar |
51
- |--------|-------------|
52
- | Task completada | Adicionar entrada em `tasks[]` com todos os campos preenchidos |
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
- ## Regras de escrita
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
- - O modelo escreve o trace como arquivo JSON ao final de cada task e ao final da fase.
59
- - Se um campo não tiver valor disponível, usar `null` — nunca omitir o campo.
60
- - Manter a estrutura completa para compatibilidade com análise de meta-harness.
61
- - `session_id` usa o formato `YYYY-MM-DD-HH-MM-SS` do início da sessão atual.
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));