@polymorphism-tech/morph-spec 4.22.0 → 6.0.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 (211) hide show
  1. package/CLAUDE.md +57 -71
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +224 -491
  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 +55 -38
  8. package/framework/MORPH.md +244 -0
  9. package/framework/agents.json +127 -1198
  10. package/framework/commands/morph-apply.md +56 -240
  11. package/framework/commands/morph-archive.md +48 -85
  12. package/framework/commands/morph-preflight.md +7 -7
  13. package/framework/commands/morph-proposal.md +35 -191
  14. package/framework/commands/morph-status.md +19 -50
  15. package/framework/commands/morph-troubleshoot.md +2 -2
  16. package/framework/evals/rubrics/README.md +44 -0
  17. package/framework/evals/rubrics/architecture.md +46 -0
  18. package/framework/evals/rubrics/code-quality.md +50 -0
  19. package/framework/evals/rubrics/contracts.md +47 -0
  20. package/framework/evals/rubrics/test-coverage.md +50 -0
  21. package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
  22. package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
  23. package/framework/evals/suites/standard.yaml +5 -0
  24. package/framework/evals/trace-schema.md +67 -0
  25. package/framework/hooks/claude-code/core/README.md +3 -0
  26. package/framework/hooks/claude-code/core/denylist.json +14 -0
  27. package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
  28. package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
  30. package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
  31. package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
  32. package/framework/hooks/shared/phase-utils.js +20 -31
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
  35. package/framework/phases.json +41 -115
  36. package/framework/rubrics/eval-rubric.json +66 -0
  37. package/framework/skills/README.md +39 -64
  38. package/framework/skills/core/README.md +3 -0
  39. package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
  40. package/framework/skills/core/morph-eval/SKILL.md +57 -0
  41. package/framework/skills/core/morph-implement/SKILL.md +83 -0
  42. package/framework/skills/core/morph-plan/SKILL.md +64 -0
  43. package/framework/skills/core/morph-review/SKILL.md +62 -0
  44. package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
  45. package/framework/skills/core/morph-standards/SKILL.md +49 -0
  46. package/framework/templates/CLAUDE.md.core.guide.md +29 -0
  47. package/framework/templates/CLAUDE.md.core.template +22 -0
  48. package/framework/templates/REGISTRY.json +4 -4
  49. package/framework/workflows/configs/fast-track.json +5 -10
  50. package/framework/workflows/configs/full.json +4 -8
  51. package/framework/workflows/configs/quick.json +32 -0
  52. package/framework/workflows/configs/standard.json +57 -64
  53. package/package.json +7 -6
  54. package/src/commands/mcp/mcp-setup.js +58 -31
  55. package/src/commands/project/doctor.js +69 -70
  56. package/src/commands/project/init.js +26 -5
  57. package/src/commands/project/status.js +18 -19
  58. package/src/commands/state/approve.js +1 -1
  59. package/src/commands/validation/validate-feature.js +18 -112
  60. package/src/core/paths/output-schema.js +133 -152
  61. package/src/core/state/phase-state-machine.js +10 -24
  62. package/src/core/state/state-manager.js +17 -17
  63. package/src/core/workflows/workflow-detector.js +214 -31
  64. package/src/core/workflows/workflow-router.js +204 -0
  65. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  66. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  67. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  68. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  69. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  70. package/src/dashboard/scanner/agents-scanner.js +30 -35
  71. package/src/lib/agents/metrics-aggregator.js +1 -1
  72. package/src/lib/agents/output-collector.js +7 -9
  73. package/src/lib/context/context-builder.js +289 -0
  74. package/src/lib/detectors/claude-config-detector.js +19 -3
  75. package/src/lib/generators/context-generator.js +20 -42
  76. package/src/lib/generators/index.js +1 -0
  77. package/src/lib/generators/mandate-generator.js +512 -0
  78. package/src/lib/generators/metadata-extractor.js +1 -1
  79. package/src/lib/generators/recap-generator.js +3 -4
  80. package/src/lib/installers/mcp-installer.js +300 -92
  81. package/src/lib/phase-chain/phase-validator.js +36 -40
  82. package/src/lib/stack/stack-profile.js +74 -91
  83. package/src/lib/tasks/task-parser.js +1 -1
  84. package/src/lib/tasks/wave-resolver.js +353 -0
  85. package/src/lib/validators/checkpoint-persistence.js +2 -2
  86. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  87. package/src/lib/validators/content/index.js +1 -0
  88. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  89. package/src/scripts/global-install.js +2 -27
  90. package/src/utils/agents-installer.js +224 -447
  91. package/src/utils/claude-settings-manager.js +57 -15
  92. package/src/utils/file-copier.js +1 -0
  93. package/src/utils/hooks-installer.js +15 -117
  94. package/src/utils/skills-installer.js +12 -18
  95. package/bin/detect-agents.js +0 -226
  96. package/bin/task-manager.js +0 -1036
  97. package/framework/agents/README.md +0 -13
  98. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  99. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  100. package/framework/agents/architecture/architecture-expert.md +0 -98
  101. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  102. package/framework/agents/architecture/packages-validator.md +0 -89
  103. package/framework/agents/architecture/security-expert.md +0 -79
  104. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  105. package/framework/agents/architecture/standards-architect.md +0 -153
  106. package/framework/agents/backend/dotnet-senior.md +0 -163
  107. package/framework/agents/backend/ef-modeler.md +0 -119
  108. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  109. package/framework/agents/backend/ms-agent-expert.md +0 -55
  110. package/framework/agents/backend/neon-expert.md +0 -379
  111. package/framework/agents/frontend/css-specialist.md +0 -89
  112. package/framework/agents/frontend/design-system-validator.md +0 -100
  113. package/framework/agents/frontend/gsap-animator.md +0 -131
  114. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  115. package/framework/agents/frontend/nextjs-expert.md +0 -201
  116. package/framework/agents/frontend/ui-designer.md +0 -259
  117. package/framework/agents/infrastructure/infra-architect.md +0 -215
  118. package/framework/agents/integrations/asaas-financial.md +0 -138
  119. package/framework/agents/integrations/neon-auth.md +0 -151
  120. package/framework/agents/integrations/resend-email.md +0 -127
  121. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  122. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  123. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  124. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  125. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  126. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  127. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  128. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  129. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  130. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  131. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  132. package/framework/hooks/shared/compact-restore.js +0 -123
  133. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  134. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  135. package/framework/hooks/shared/stale-task-reset.js +0 -57
  136. package/framework/hooks/shared/worktree-helpers.js +0 -75
  137. package/framework/skills/level-0-meta/README.md +0 -7
  138. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  139. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  140. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  141. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  142. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  143. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  144. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  145. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  146. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  147. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  148. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  149. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  150. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  151. package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
  152. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  153. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  154. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
  155. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  156. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  157. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  158. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  159. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  160. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  161. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  162. package/framework/skills/level-1-workflows/README.md +0 -7
  163. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  164. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  165. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  166. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  168. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  169. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
  170. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  171. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  173. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  174. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  175. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  176. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  177. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  181. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
  182. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  183. package/framework/squad-templates/backend-only.json +0 -47
  184. package/framework/squad-templates/frontend-only.json +0 -56
  185. package/framework/squad-templates/full-stack.json +0 -74
  186. package/src/commands/agents/agent-metrics.js +0 -174
  187. package/src/commands/agents/audit-dispatch.js +0 -82
  188. package/src/commands/agents/detect-agents.js +0 -275
  189. package/src/commands/agents/dispatch-agents.js +0 -461
  190. package/src/commands/agents/index.js +0 -13
  191. package/src/commands/agents/manage-agents.js +0 -352
  192. package/src/commands/agents/project-metrics.js +0 -74
  193. package/src/commands/phase/phase-reset.js +0 -74
  194. package/src/commands/scope/escalate.js +0 -217
  195. package/src/commands/state/advance-phase.js +0 -600
  196. package/src/commands/state/phase-runner.js +0 -215
  197. package/src/commands/state/state.js +0 -444
  198. package/src/commands/task/expand.js +0 -100
  199. package/src/commands/tasks/index.js +0 -4
  200. package/src/commands/tasks/task.js +0 -105
  201. package/src/commands/templates/index.js +0 -5
  202. package/src/commands/templates/template-render.js +0 -93
  203. package/src/commands/trust/trust.js +0 -118
  204. package/src/lib/agents/agents-manager.js +0 -349
  205. package/src/lib/build/build-runner.js +0 -99
  206. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  207. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  208. package/src/lib/scope/impact-analyzer.js +0 -106
  209. package/src/lib/trust/trust-manager.js +0 -157
  210. package/src/utils/tier-helpers.js +0 -24
  211. /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
@@ -0,0 +1,512 @@
1
+ /**
2
+ * Implementation Mandate Generator
3
+ *
4
+ * Synthesizes all spec phase outputs (spec.md, contracts, plan.md, tasks.md,
5
+ * decisions.md) into a single task-structured implementation mandate.
6
+ *
7
+ * The mandate is the primary context artifact for the implement phase —
8
+ * each task section is self-contained with acceptance criteria, contracts,
9
+ * file paths, and architectural constraints.
10
+ */
11
+
12
+ import { readFileSync, existsSync } from 'fs';
13
+ import { join } from 'path';
14
+
15
+ /**
16
+ * v5: parseTaskFromMarkdown lived in sprint-contract-generator. Inlined here.
17
+ */
18
+ function parseTaskFromMarkdown(tasksContent, taskId) {
19
+ const headingRe = new RegExp(`^### ${taskId}\\b[^\\n]*\\n([\\s\\S]*?)(?=^### |\\Z)`, 'm');
20
+ const match = tasksContent.match(headingRe);
21
+ if (!match) return null;
22
+ const block = match[1];
23
+ const frRefs = [...block.matchAll(/\bFR-?\d+\b/g)].map(m => m[0]);
24
+ return { id: taskId, frs: [...new Set(frRefs)], block };
25
+ }
26
+
27
+ /**
28
+ * Extract all FR sections from spec.md.
29
+ *
30
+ * @param {string} specContent - Full spec.md content
31
+ * @returns {Map<string, { title: string, content: string, criteria: string[] }>}
32
+ */
33
+ export function parseFRSections(specContent) {
34
+ const sections = new Map();
35
+ const frPattern = /^##\s+(FR\d+)[:\s—–-]+(.+?)$/gm;
36
+ let match;
37
+ const matches = [];
38
+
39
+ while ((match = frPattern.exec(specContent)) !== null) {
40
+ matches.push({
41
+ id: match[1],
42
+ title: match[2].trim(),
43
+ start: match.index,
44
+ headerEnd: match.index + match[0].length,
45
+ });
46
+ }
47
+
48
+ for (let i = 0; i < matches.length; i++) {
49
+ const current = matches[i];
50
+ const nextStart = i + 1 < matches.length ? matches[i + 1].start : specContent.length;
51
+ const body = specContent.slice(current.headerEnd, nextStart).trim();
52
+
53
+ // Extract bullet points as acceptance criteria
54
+ const criteria = [];
55
+ const bulletPattern = /^[\s]*[-*]\s+(.+)/gm;
56
+ let bulletMatch;
57
+ while ((bulletMatch = bulletPattern.exec(body)) !== null) {
58
+ criteria.push(bulletMatch[1].trim());
59
+ }
60
+
61
+ sections.set(current.id, {
62
+ title: current.title,
63
+ content: body,
64
+ criteria,
65
+ });
66
+ }
67
+
68
+ return sections;
69
+ }
70
+
71
+ /**
72
+ * Extract contract types/interfaces from contracts file content.
73
+ * Matches C# records, classes, interfaces and TypeScript types/interfaces.
74
+ *
75
+ * @param {string} contractsContent - Full contracts.cs or contracts.ts content
76
+ * @returns {Map<string, string>} Map of type name → definition
77
+ */
78
+ export function parseContracts(contractsContent) {
79
+ const types = new Map();
80
+
81
+ // C# sealed records: sealed record Foo(...)
82
+ const csRecordPattern = /^\s*((?:public\s+)?sealed\s+record\s+\w+\([^)]*\)\s*;)/gm;
83
+ let match;
84
+ while ((match = csRecordPattern.exec(contractsContent)) !== null) {
85
+ const nameMatch = match[1].match(/record\s+(\w+)/);
86
+ if (nameMatch) {
87
+ types.set(nameMatch[1], match[1].trim());
88
+ }
89
+ }
90
+
91
+ // C# interfaces: public interface IFoo { ... }
92
+ const csInterfacePattern = /^\s*((?:public\s+)?interface\s+\w+\s*\{[^}]*\})/gms;
93
+ while ((match = csInterfacePattern.exec(contractsContent)) !== null) {
94
+ const nameMatch = match[1].match(/interface\s+(\w+)/);
95
+ if (nameMatch) {
96
+ types.set(nameMatch[1], match[1].trim());
97
+ }
98
+ }
99
+
100
+ // TypeScript types/interfaces
101
+ const tsTypePattern = /^\s*(export\s+(?:type|interface)\s+\w+[\s\S]*?(?:\};?|=[^;]+;))/gm;
102
+ while ((match = tsTypePattern.exec(contractsContent)) !== null) {
103
+ const nameMatch = match[1].match(/(?:type|interface)\s+(\w+)/);
104
+ if (nameMatch) {
105
+ types.set(nameMatch[1], match[1].trim());
106
+ }
107
+ }
108
+
109
+ return types;
110
+ }
111
+
112
+ /**
113
+ * Extract file paths per task from plan.md.
114
+ * Looks for task references followed by file paths.
115
+ *
116
+ * @param {string} planContent - Full plan.md content
117
+ * @param {string} taskId - Task ID (e.g., 'T001')
118
+ * @returns {string[]} File paths
119
+ */
120
+ export function extractTaskFilePaths(planContent, taskId) {
121
+ const paths = [];
122
+
123
+ // Look for task section in plan
124
+ const taskSectionPattern = new RegExp(
125
+ `(?:^|\\n).*${taskId}[^\\n]*\\n([\\s\\S]*?)(?=\\n(?:#+\\s|$)|$)`,
126
+ 'i'
127
+ );
128
+ const sectionMatch = planContent.match(taskSectionPattern);
129
+ if (!sectionMatch) return paths;
130
+
131
+ const section = sectionMatch[1];
132
+ // Extract file paths (common patterns)
133
+ const filePatterns = [
134
+ /`([^`]+\.\w{1,6})`/g, // backtick-wrapped paths
135
+ /(?:^|\s)([\w/.]+\.(?:cs|ts|tsx|js|jsx|json|md|yaml|yml|csproj))/gm, // bare paths
136
+ ];
137
+
138
+ for (const pattern of filePatterns) {
139
+ let fileMatch;
140
+ while ((fileMatch = pattern.exec(section)) !== null) {
141
+ const filePath = fileMatch[1].trim();
142
+ if (filePath.length > 3 && !paths.includes(filePath)) {
143
+ paths.push(filePath);
144
+ }
145
+ }
146
+ }
147
+
148
+ return paths;
149
+ }
150
+
151
+ /**
152
+ * Extract architectural constraints from decisions.md.
153
+ *
154
+ * @param {string} decisionsContent - Full decisions.md content
155
+ * @returns {string[]} One-line constraint summaries
156
+ */
157
+ export function parseDecisions(decisionsContent) {
158
+ const constraints = [];
159
+ const adrPattern = /^###?\s+(ADR-\d+)[:\s—–-]+(.+?)$/gm;
160
+ let match;
161
+
162
+ while ((match = adrPattern.exec(decisionsContent)) !== null) {
163
+ constraints.push(`${match[1]}: ${match[2].trim()}`);
164
+ }
165
+
166
+ return constraints;
167
+ }
168
+
169
+ /**
170
+ * Extract all task IDs from tasks.md in order.
171
+ *
172
+ * @param {string} tasksContent - Full tasks.md content
173
+ * @returns {string[]} Task IDs in order (e.g., ['T001', 'T002', ...])
174
+ */
175
+ export function extractTaskIds(tasksContent) {
176
+ const ids = [];
177
+ const pattern = /^##\s+(T\d+)[:\s]/gm;
178
+ let match;
179
+ while ((match = pattern.exec(tasksContent)) !== null) {
180
+ if (!ids.includes(match[1])) {
181
+ ids.push(match[1]);
182
+ }
183
+ }
184
+ return ids;
185
+ }
186
+
187
+ /**
188
+ * Find contract types relevant to an FR or task.
189
+ * Matches by: type name in content, or FR title keywords in type name.
190
+ *
191
+ * @param {string} searchText - FR content + task title/description
192
+ * @param {Map<string, string>} contracts - Parsed contracts
193
+ * @returns {string[]} Matching contract definitions
194
+ */
195
+ function findRelevantContracts(searchText, contracts) {
196
+ const relevant = [];
197
+ const searchLower = searchText.toLowerCase();
198
+
199
+ for (const [typeName, definition] of contracts) {
200
+ // Direct match: type name appears in the search text
201
+ if (searchText.includes(typeName)) {
202
+ relevant.push(definition);
203
+ continue;
204
+ }
205
+ // Keyword match: split type name into words (e.g., UserRegisterRequest → user, register, request)
206
+ // and check if the significant words appear in the search text
207
+ const typeWords = typeName
208
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
209
+ .toLowerCase()
210
+ .split(/\s+/)
211
+ .filter(w => w.length > 3 && !['request', 'response', 'result'].includes(w));
212
+ if (typeWords.length > 0 && typeWords.every(w => searchLower.includes(w))) {
213
+ relevant.push(definition);
214
+ }
215
+ }
216
+ return relevant;
217
+ }
218
+
219
+ /**
220
+ * Generate an implementation mandate from spec phase outputs.
221
+ *
222
+ * @param {string} projectPath - Project root
223
+ * @param {string} featureName - Feature name
224
+ * @returns {{ mandate: string, stats: { frCount: number, taskCount: number, totalChars: number } }|null}
225
+ */
226
+ export function generateMandate(projectPath, featureName) {
227
+ const featureDir = join(projectPath, '.morph/features', featureName);
228
+
229
+ // Read required files
230
+ const tasksPath = join(featureDir, '2-plan/tasks.md');
231
+ const specPath = join(featureDir, '2-plan/spec.md');
232
+
233
+ if (!existsSync(tasksPath) || !existsSync(specPath)) return null;
234
+
235
+ const tasksContent = readFileSync(tasksPath, 'utf-8');
236
+ const specContent = readFileSync(specPath, 'utf-8');
237
+
238
+ // Read optional files
239
+ const contractsPaths = [
240
+ join(featureDir, '2-plan/contracts.cs'),
241
+ join(featureDir, '2-plan/contracts.ts'),
242
+ ];
243
+ let contractsContent = '';
244
+ for (const cp of contractsPaths) {
245
+ if (existsSync(cp)) {
246
+ contractsContent += readFileSync(cp, 'utf-8') + '\n';
247
+ }
248
+ }
249
+
250
+ const planPath = join(featureDir, '2-plan/plan.md');
251
+ const planContent = existsSync(planPath) ? readFileSync(planPath, 'utf-8') : '';
252
+
253
+ const decisionsPath = join(featureDir, '2-plan/decisions.md');
254
+ const decisionsContent = existsSync(decisionsPath) ? readFileSync(decisionsPath, 'utf-8') : '';
255
+
256
+ const proposalPath = join(featureDir, '0-proposal/proposal.md');
257
+ const proposalContent = existsSync(proposalPath) ? readFileSync(proposalPath, 'utf-8') : '';
258
+
259
+ // Read project config
260
+ let stack = '';
261
+ let architecture = '';
262
+ let workflow = '';
263
+ try {
264
+ const configPath = join(projectPath, '.morph/config/config.json');
265
+ if (existsSync(configPath)) {
266
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
267
+ stack = config?.project?.stack || '';
268
+ architecture = config?.architecture?.style || '';
269
+ workflow = config?.workflow || '';
270
+ }
271
+ } catch { /* ignore */ }
272
+
273
+ // Parse all inputs
274
+ const frSections = parseFRSections(specContent);
275
+ const contracts = parseContracts(contractsContent);
276
+ const decisions = parseDecisions(decisionsContent);
277
+ const taskIds = extractTaskIds(tasksContent);
278
+
279
+ // Build mandate
280
+ const lines = [];
281
+
282
+ // Header
283
+ lines.push(`# Implementation Mandate: ${featureName}`);
284
+ lines.push('');
285
+ lines.push(`**Generated:** ${new Date().toISOString().split('T')[0]} | **FRs:** ${frSections.size} | **Tasks:** ${taskIds.length}`);
286
+ if (stack || architecture) {
287
+ lines.push(`**Stack:** ${stack || 'unspecified'} | **Architecture:** ${architecture || 'unspecified'}`);
288
+ }
289
+
290
+ // Overview from proposal
291
+ if (proposalContent) {
292
+ lines.push('');
293
+ lines.push('## Overview');
294
+ // Extract first paragraph after the title
295
+ const paragraphs = proposalContent
296
+ .split(/\n\n+/)
297
+ .filter(p => p.trim() && !p.trim().startsWith('#'))
298
+ .slice(0, 1);
299
+ if (paragraphs.length > 0) {
300
+ lines.push(paragraphs[0].trim());
301
+ }
302
+ }
303
+
304
+ // Architectural constraints
305
+ if (decisions.length > 0) {
306
+ lines.push('');
307
+ lines.push('## Architectural Constraints');
308
+ for (const d of decisions) {
309
+ lines.push(`- ${d}`);
310
+ }
311
+ }
312
+
313
+ // Task sections
314
+ for (const taskId of taskIds) {
315
+ const task = parseTaskFromMarkdown(tasksContent, taskId);
316
+ if (!task) continue;
317
+
318
+ lines.push('');
319
+ lines.push('---');
320
+ lines.push('');
321
+ lines.push(`## ${taskId}: ${task.title}`);
322
+
323
+ // Metadata line
324
+ const metaParts = [];
325
+ if (task.frs.length > 0) metaParts.push(`**FRs:** ${task.frs.join(', ')}`);
326
+ const filePaths = planContent ? extractTaskFilePaths(planContent, taskId) : [];
327
+ if (filePaths.length > 0) metaParts.push(`**Files:** ${filePaths.join(', ')}`);
328
+ if (metaParts.length > 0) lines.push(metaParts.join(' | '));
329
+
330
+ // Acceptance criteria from FR sections
331
+ const allCriteria = [];
332
+ const allFRContracts = [];
333
+ for (const frId of task.frs) {
334
+ const fr = frSections.get(frId);
335
+ if (fr) {
336
+ allCriteria.push(...fr.criteria);
337
+ // Search for contracts in FR content + task title for broader matching
338
+ const searchText = `${fr.title} ${fr.content} ${task.title} ${task.description}`;
339
+ allFRContracts.push(...findRelevantContracts(searchText, contracts));
340
+ }
341
+ }
342
+
343
+ if (allCriteria.length > 0) {
344
+ lines.push('');
345
+ lines.push('### Acceptance Criteria');
346
+ for (const c of allCriteria) {
347
+ lines.push(`- [ ] ${c}`);
348
+ }
349
+ }
350
+
351
+ // Contracts
352
+ const uniqueContracts = [...new Set(allFRContracts)];
353
+ if (uniqueContracts.length > 0) {
354
+ lines.push('');
355
+ lines.push('### Contracts');
356
+ lines.push('```');
357
+ lines.push(uniqueContracts.join('\n'));
358
+ lines.push('```');
359
+ }
360
+
361
+ // Task description as notes
362
+ if (task.description) {
363
+ lines.push('');
364
+ lines.push('### Notes');
365
+ lines.push(task.description);
366
+ }
367
+ }
368
+
369
+ // Verification summary
370
+ lines.push('');
371
+ lines.push('---');
372
+ lines.push('');
373
+ lines.push('## Verification');
374
+ lines.push('```bash');
375
+ if (stack.includes('dotnet')) {
376
+ lines.push('dotnet build');
377
+ lines.push('dotnet test');
378
+ } else {
379
+ lines.push('npm run build');
380
+ lines.push('npm test');
381
+ }
382
+ lines.push('```');
383
+
384
+ const mandate = lines.join('\n');
385
+
386
+ return {
387
+ mandate,
388
+ stats: {
389
+ frCount: frSections.size,
390
+ taskCount: taskIds.length,
391
+ totalChars: mandate.length,
392
+ },
393
+ };
394
+ }
395
+
396
+ /**
397
+ * Extract a single task's section from a generated mandate.
398
+ *
399
+ * @param {string} mandateContent - Full mandate.md content
400
+ * @param {string} taskId - Task ID (e.g., 'T003')
401
+ * @returns {string|null} Task section text or null if not found
402
+ */
403
+ export function extractTaskSection(mandateContent, taskId) {
404
+ // Find the start of this task's section
405
+ const headerPattern = new RegExp(`^## ${taskId}:`, 'm');
406
+ const headerMatch = headerPattern.exec(mandateContent);
407
+ if (!headerMatch) return null;
408
+
409
+ const start = headerMatch.index;
410
+
411
+ // Find the end: next task header, Verification section, or end of content
412
+ const rest = mandateContent.slice(start + 1);
413
+ const endPatterns = [
414
+ /\n## T\d+:/,
415
+ /\n## Verification/,
416
+ ];
417
+
418
+ let end = mandateContent.length;
419
+ for (const pattern of endPatterns) {
420
+ const endMatch = pattern.exec(rest);
421
+ if (endMatch) {
422
+ const candidate = start + 1 + endMatch.index;
423
+ if (candidate < end) end = candidate;
424
+ }
425
+ }
426
+
427
+ // Extract and trim, removing trailing --- separators
428
+ let section = mandateContent.slice(start, end).trim();
429
+ section = section.replace(/\n---\s*$/, '').trim();
430
+
431
+ return section;
432
+ }
433
+
434
+ /**
435
+ * Extract multiple task sections (for agent dispatch).
436
+ *
437
+ * @param {string} mandateContent - Full mandate.md content
438
+ * @param {string[]} taskIds - Task IDs (e.g., ['T001', 'T002', 'T003'])
439
+ * @returns {string} Combined task sections
440
+ */
441
+ export function extractTaskSections(mandateContent, taskIds) {
442
+ const sections = [];
443
+ for (const taskId of taskIds) {
444
+ const section = extractTaskSection(mandateContent, taskId);
445
+ if (section) sections.push(section);
446
+ }
447
+ return sections.join('\n\n---\n\n');
448
+ }
449
+
450
+ /**
451
+ * Generate a mandate overview (for session start injection).
452
+ * Shows FR list with task mapping and completion status.
453
+ *
454
+ * @param {string} mandateContent - Full mandate.md content
455
+ * @param {Object} taskStatus - Map of taskId → status ('completed'|'in_progress'|'pending')
456
+ * @returns {string} Overview text
457
+ */
458
+ export function generateMandateOverview(mandateContent, taskStatus = {}) {
459
+ const lines = [];
460
+
461
+ // Extract task-to-FR mapping from mandate
462
+ const taskFRMap = new Map();
463
+ const frTaskMap = new Map();
464
+
465
+ const taskPattern = /^## (T\d+): .+$/gm;
466
+ const frLinePattern = /^\*\*FRs:\*\*\s+(.+?)(?:\s*\||$)/gm;
467
+
468
+ let match;
469
+ let lastTaskId = null;
470
+
471
+ while ((match = taskPattern.exec(mandateContent)) !== null) {
472
+ lastTaskId = match[1];
473
+ // Look for FRs line right after task header
474
+ const afterHeader = mandateContent.slice(match.index + match[0].length, match.index + match[0].length + 200);
475
+ const frMatch = afterHeader.match(/\*\*FRs:\*\*\s+([^\n|]+)/);
476
+ if (frMatch) {
477
+ const frs = frMatch[1].split(',').map(f => f.trim()).filter(Boolean);
478
+ taskFRMap.set(lastTaskId, frs);
479
+ for (const fr of frs) {
480
+ if (!frTaskMap.has(fr)) frTaskMap.set(fr, []);
481
+ frTaskMap.get(fr).push(lastTaskId);
482
+ }
483
+ }
484
+ }
485
+
486
+ // Build overview
487
+ lines.push('Implementation Mandate Overview:');
488
+
489
+ for (const [frId, taskIds] of frTaskMap) {
490
+ const taskStatuses = taskIds.map(tid => {
491
+ const status = taskStatus[tid] || 'pending';
492
+ if (status === 'completed') return `${tid} ✓`;
493
+ if (status === 'in_progress') return `${tid} →`;
494
+ return tid;
495
+ });
496
+ lines.push(` ${frId} → ${taskStatuses.join(', ')}`);
497
+ }
498
+
499
+ // Next task
500
+ const allTaskIds = [...taskFRMap.keys()];
501
+ const nextTask = allTaskIds.find(tid => {
502
+ const status = taskStatus[tid];
503
+ return status === 'in_progress' || !status || status === 'pending';
504
+ });
505
+ if (nextTask) {
506
+ const status = taskStatus[nextTask];
507
+ const verb = status === 'in_progress' ? 'In progress' : 'Next';
508
+ lines.push(` ${verb}: ${nextTask}`);
509
+ }
510
+
511
+ return lines.join('\n');
512
+ }
@@ -308,7 +308,7 @@ export function extractTasksMetadata(tasksPath) {
308
308
  byStatus: {
309
309
  pending: regularTasks.filter(t => t.status === 'pending').length,
310
310
  inProgress: regularTasks.filter(t => t.status === 'in_progress').length,
311
- done: regularTasks.filter(t => t.status === 'done').length,
311
+ completed: regularTasks.filter(t => t.status === 'completed').length,
312
312
  blocked: regularTasks.filter(t => t.status === 'blocked').length
313
313
  },
314
314
 
@@ -4,7 +4,7 @@
4
4
  * Auto-generates recap.md from real project data: state.json, contract compliance,
5
5
  * validation results, and decisions. Replaces manual template filling.
6
6
  *
7
- * MORPH-SPEC 3.0 - Phase B: Spec-Driven Pipeline
7
+ * MORPH-SPEC 5.0 - Recap Generator
8
8
  */
9
9
 
10
10
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
@@ -98,10 +98,9 @@ function getTasksSummary(feature) {
98
98
  };
99
99
  }
100
100
 
101
- // v3: feature.tasks is a counter object {total, completed, inProgress, pending}
102
- // Use feature.taskList for individual items if available
101
+ // v3-v4 legacy: feature.tasks is a counter object {total, completed, inProgress, pending}
103
102
  const counters = feature.tasks || {};
104
- const items = feature.taskList || [];
103
+ const items = [];
105
104
  const total = counters.total || items.length || 0;
106
105
  const completedCount = counters.completed ?? items.filter(t => t.status === 'completed').length;
107
106
  const pendingCount = counters.pending ?? items.filter(t => t.status === 'pending').length;