@polymorphism-tech/morph-spec 4.21.0 → 5.0.1

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 (213) 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 -1183
  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 -28
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -210
  35. package/framework/phases.json +40 -114
  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/standards/STANDARDS.json +32 -0
  47. package/framework/standards/frontend/design-system/ai-image-generation.md +244 -0
  48. package/framework/standards/integration/mcp/mcp-tools.md +48 -0
  49. package/framework/templates/CLAUDE.md.core.guide.md +29 -0
  50. package/framework/templates/CLAUDE.md.core.template +22 -0
  51. package/framework/templates/REGISTRY.json +4 -4
  52. package/framework/workflows/configs/fast-track.json +5 -10
  53. package/framework/workflows/configs/full.json +4 -8
  54. package/framework/workflows/configs/quick.json +32 -0
  55. package/framework/workflows/configs/standard.json +57 -64
  56. package/package.json +7 -6
  57. package/src/commands/mcp/mcp-setup.js +58 -31
  58. package/src/commands/project/doctor.js +69 -70
  59. package/src/commands/project/init.js +26 -5
  60. package/src/commands/project/status.js +18 -19
  61. package/src/commands/state/approve.js +1 -1
  62. package/src/commands/validation/validate-feature.js +18 -112
  63. package/src/core/paths/output-schema.js +133 -151
  64. package/src/core/state/phase-state-machine.js +10 -24
  65. package/src/core/state/state-manager.js +17 -17
  66. package/src/core/workflows/workflow-detector.js +214 -31
  67. package/src/core/workflows/workflow-router.js +204 -0
  68. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  69. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  70. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  71. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  72. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  73. package/src/dashboard/scanner/agents-scanner.js +30 -35
  74. package/src/lib/agents/metrics-aggregator.js +1 -1
  75. package/src/lib/agents/output-collector.js +7 -8
  76. package/src/lib/context/context-builder.js +289 -0
  77. package/src/lib/detectors/claude-config-detector.js +19 -3
  78. package/src/lib/generators/context-generator.js +20 -42
  79. package/src/lib/generators/index.js +1 -0
  80. package/src/lib/generators/mandate-generator.js +512 -0
  81. package/src/lib/generators/metadata-extractor.js +1 -1
  82. package/src/lib/generators/recap-generator.js +3 -4
  83. package/src/lib/installers/mcp-installer.js +300 -92
  84. package/src/lib/phase-chain/phase-validator.js +36 -40
  85. package/src/lib/stack/stack-profile.js +74 -91
  86. package/src/lib/tasks/task-parser.js +1 -1
  87. package/src/lib/tasks/wave-resolver.js +353 -0
  88. package/src/lib/validators/checkpoint-persistence.js +2 -2
  89. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  90. package/src/lib/validators/content/index.js +1 -0
  91. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  92. package/src/scripts/global-install.js +2 -27
  93. package/src/utils/agents-installer.js +224 -447
  94. package/src/utils/claude-settings-manager.js +57 -15
  95. package/src/utils/file-copier.js +1 -0
  96. package/src/utils/hooks-installer.js +15 -117
  97. package/src/utils/skills-installer.js +12 -18
  98. package/bin/detect-agents.js +0 -226
  99. package/bin/task-manager.js +0 -1036
  100. package/framework/agents/README.md +0 -13
  101. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  102. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  103. package/framework/agents/architecture/architecture-expert.md +0 -98
  104. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  105. package/framework/agents/architecture/packages-validator.md +0 -89
  106. package/framework/agents/architecture/security-expert.md +0 -79
  107. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  108. package/framework/agents/architecture/standards-architect.md +0 -153
  109. package/framework/agents/backend/dotnet-senior.md +0 -163
  110. package/framework/agents/backend/ef-modeler.md +0 -119
  111. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  112. package/framework/agents/backend/ms-agent-expert.md +0 -55
  113. package/framework/agents/backend/neon-expert.md +0 -379
  114. package/framework/agents/frontend/css-specialist.md +0 -89
  115. package/framework/agents/frontend/design-system-validator.md +0 -100
  116. package/framework/agents/frontend/gsap-animator.md +0 -131
  117. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  118. package/framework/agents/frontend/nextjs-expert.md +0 -201
  119. package/framework/agents/frontend/ui-designer.md +0 -259
  120. package/framework/agents/infrastructure/infra-architect.md +0 -215
  121. package/framework/agents/integrations/asaas-financial.md +0 -138
  122. package/framework/agents/integrations/neon-auth.md +0 -151
  123. package/framework/agents/integrations/resend-email.md +0 -127
  124. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  125. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  126. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  127. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  128. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  129. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  130. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  131. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  132. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  133. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  134. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  135. package/framework/hooks/shared/compact-restore.js +0 -123
  136. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  137. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  138. package/framework/hooks/shared/stale-task-reset.js +0 -57
  139. package/framework/hooks/shared/worktree-helpers.js +0 -75
  140. package/framework/skills/level-0-meta/README.md +0 -7
  141. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  142. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  143. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  144. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  145. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  146. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  147. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  148. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  149. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  150. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  151. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  152. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  153. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  154. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  155. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  156. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -382
  157. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  158. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  159. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  160. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  161. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  162. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  163. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  164. package/framework/skills/level-1-workflows/README.md +0 -7
  165. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  166. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  168. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  169. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  170. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  171. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -493
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  173. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -46
  174. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  175. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  176. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  177. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  181. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  182. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  183. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -788
  184. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  185. package/framework/squad-templates/backend-only.json +0 -47
  186. package/framework/squad-templates/frontend-only.json +0 -56
  187. package/framework/squad-templates/full-stack.json +0 -74
  188. package/src/commands/agents/agent-metrics.js +0 -174
  189. package/src/commands/agents/audit-dispatch.js +0 -82
  190. package/src/commands/agents/detect-agents.js +0 -275
  191. package/src/commands/agents/dispatch-agents.js +0 -461
  192. package/src/commands/agents/index.js +0 -13
  193. package/src/commands/agents/manage-agents.js +0 -352
  194. package/src/commands/agents/project-metrics.js +0 -74
  195. package/src/commands/phase/phase-reset.js +0 -74
  196. package/src/commands/scope/escalate.js +0 -217
  197. package/src/commands/state/advance-phase.js +0 -600
  198. package/src/commands/state/phase-runner.js +0 -215
  199. package/src/commands/state/state.js +0 -444
  200. package/src/commands/task/expand.js +0 -100
  201. package/src/commands/tasks/index.js +0 -4
  202. package/src/commands/tasks/task.js +0 -105
  203. package/src/commands/templates/index.js +0 -5
  204. package/src/commands/templates/template-render.js +0 -93
  205. package/src/commands/trust/trust.js +0 -118
  206. package/src/lib/agents/agents-manager.js +0 -349
  207. package/src/lib/build/build-runner.js +0 -99
  208. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  209. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  210. package/src/lib/scope/impact-analyzer.js +0 -106
  211. package/src/lib/trust/trust-manager.js +0 -157
  212. package/src/utils/tier-helpers.js +0 -24
  213. /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;