@polymorphism-tech/morph-spec 7.2.0 β†’ 7.4.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 (74) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +4 -2
  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 +2 -2
  8. package/framework/MORPH.md +29 -12
  9. package/framework/agents.json +132 -132
  10. package/framework/commands/morph-apply.md +1 -1
  11. package/framework/commands/morph-proposal.md +1 -1
  12. package/framework/evals/rubrics/README.md +9 -8
  13. package/framework/hooks/README.md +3 -1
  14. package/framework/hooks/claude-code/core/gate-guard.js +160 -0
  15. package/framework/hooks/claude-code/core/state-sync.js +15 -3
  16. package/framework/hooks/claude-code/core/validate-completion.js +241 -136
  17. package/framework/hooks/shared/state-reader.js +236 -221
  18. package/framework/phases.json +1 -1
  19. package/framework/skills/README.md +2 -2
  20. package/framework/skills/core/morph-apply/SKILL.md +63 -21
  21. package/framework/skills/core/morph-brainstorming/SKILL.md +5 -1
  22. package/framework/skills/core/morph-eval/SKILL.md +4 -5
  23. package/framework/skills/core/morph-implement/SKILL.md +47 -19
  24. package/framework/skills/core/morph-plan/SKILL.md +8 -2
  25. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  26. package/framework/skills/core/morph-review/SKILL.md +13 -6
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  28. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  29. package/framework/standards/ai-agents/setup.md +258 -244
  30. package/framework/templates/README.md +708 -709
  31. package/framework/templates/REGISTRY.json +1 -20
  32. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  33. package/framework/templates/docs/proposal.md +171 -171
  34. package/framework/templates/feature/recap.md +145 -145
  35. package/package.json +1 -1
  36. package/src/commands/project/doctor.js +941 -946
  37. package/src/commands/project/init.js +517 -515
  38. package/src/commands/project/status.js +373 -376
  39. package/src/commands/state/approve.js +251 -213
  40. package/src/commands/validation/validate-feature.js +356 -287
  41. package/src/core/state/state-manager.js +910 -934
  42. package/src/dashboard/app/App.jsx +29 -31
  43. package/src/dashboard/app/components/Sidebar.jsx +44 -45
  44. package/src/dashboard/scanner/index.js +20 -21
  45. package/src/lib/context/context-builder.js +3 -3
  46. package/src/lib/generators/context-generator.js +2 -3
  47. package/src/lib/generators/mandate-generator.js +48 -92
  48. package/src/lib/generators/metadata-extractor.js +385 -386
  49. package/src/lib/generators/recap-generator.js +285 -285
  50. package/src/lib/risk-detector.js +2 -2
  51. package/src/lib/validators/content/content-validator.js +89 -286
  52. package/src/lib/validators/content/index.js +0 -1
  53. package/src/utils/hooks-installer.js +527 -516
  54. package/framework/templates/feature/tasks.md +0 -199
  55. package/framework/workflows/README.md +0 -1041
  56. package/framework/workflows/configs/fast-track.json +0 -46
  57. package/framework/workflows/configs/full.json +0 -64
  58. package/framework/workflows/configs/quick.json +0 -32
  59. package/framework/workflows/configs/standard.json +0 -57
  60. package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +0 -392
  61. package/framework/workflows/docs/design-impl.md +0 -37
  62. package/framework/workflows/docs/enforcement-pipeline.md +0 -668
  63. package/framework/workflows/docs/fast-track.md +0 -29
  64. package/framework/workflows/docs/full-morph.md +0 -69
  65. package/framework/workflows/docs/standard.md +0 -49
  66. package/framework/workflows/docs/ui-refresh.md +0 -39
  67. package/src/commands/state/index.js +0 -7
  68. package/src/core/workflows/workflow-detector.js +0 -586
  69. package/src/core/workflows/workflow-router.js +0 -204
  70. package/src/dashboard/app/pages/Workflows.jsx +0 -346
  71. package/src/dashboard/scanner/workflows-scanner.js +0 -18
  72. package/src/lib/phase-chain/phase-validator.js +0 -359
  73. package/src/lib/validators/content/cross-phase-validator.js +0 -281
  74. package/src/lib/validators/spec-validator.js +0 -312
@@ -1,285 +1,285 @@
1
- /**
2
- * Recap Generator
3
- *
4
- * Auto-generates recap.md from real project data: state.json, contract compliance,
5
- * validation results, and decisions. Replaces manual template filling.
6
- *
7
- * MORPH-SPEC 5.0 - Recap Generator
8
- */
9
-
10
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
11
- import { join, dirname } from 'path';
12
- import chalk from 'chalk';
13
- import { loadState, markOutput } from '../../core/state/state-manager.js';
14
- import { runValidation } from '../validators/validation-runner.js';
15
-
16
- /**
17
- * Generate recap.md for a feature
18
- *
19
- * @param {string} projectPath - Project root path
20
- * @param {string} featureName - Feature name
21
- * @param {Object} options - Options
22
- * @returns {string} Generated recap content
23
- */
24
- export async function generateRecap(projectPath, featureName, options = {}) {
25
- const state = loadState(false);
26
- if (!state || !state.features[featureName]) {
27
- throw new Error(`Feature '${featureName}' not found in state.json`);
28
- }
29
-
30
- const feature = state.features[featureName];
31
- const outputsPath = join(projectPath, '.morph/features', featureName);
32
-
33
- // Gather data
34
- const tasksSummary = getTasksSummary(feature);
35
- const agentsSummary = getAgentsSummary(feature);
36
- const checkpointsSummary = getCheckpointsSummary(feature);
37
- // Decision constraints support (stub - decision-constraint-loader removed as orphaned code)
38
- const decisions = { decisions: [] };
39
- const validationResult = await runValidation(projectPath, featureName, { verbose: false });
40
-
41
- // Contract coverage
42
- let contractCoverage = null;
43
- if (validationResult.issues) {
44
- const contractIssues = validationResult.issues.filter(i => i.message?.includes('Interface') || i.message?.includes('contracts'));
45
- if (contractIssues.length > 0 || existsSync(join(outputsPath, 'contracts.cs'))) {
46
- // Extract coverage from validation result
47
- contractCoverage = extractContractCoverage(validationResult);
48
- }
49
- }
50
-
51
- // Generate markdown
52
- const recap = buildRecapMarkdown({
53
- featureName,
54
- feature,
55
- tasksSummary,
56
- agentsSummary,
57
- checkpointsSummary,
58
- decisions: decisions.decisions,
59
- validationResult,
60
- contractCoverage
61
- });
62
-
63
- // Write to file
64
- const recapPath = join(outputsPath, 'recap.md');
65
- if (!existsSync(dirname(recapPath))) {
66
- mkdirSync(dirname(recapPath), { recursive: true });
67
- }
68
- writeFileSync(recapPath, recap, 'utf-8');
69
-
70
- // Auto-mark recap output as created in state
71
- try {
72
- await markOutput(featureName, 'recap');
73
- } catch {
74
- // Non-fatal: state may not exist in all contexts
75
- }
76
-
77
- if (!options.quiet) {
78
- console.log(chalk.green(`\nβœ… Recap generated: ${recapPath}`));
79
- }
80
-
81
- return recap;
82
- }
83
-
84
- function getTasksSummary(feature) {
85
- // v2: feature.tasks is an array of task objects
86
- if (Array.isArray(feature.tasks)) {
87
- const tasks = feature.tasks;
88
- const completed = tasks.filter(t => t.status === 'completed');
89
- const pending = tasks.filter(t => t.status === 'pending');
90
- const inProgress = tasks.filter(t => t.status === 'in_progress');
91
- return {
92
- total: tasks.length,
93
- completed: completed.length,
94
- pending: pending.length,
95
- inProgress: inProgress.length,
96
- percentage: tasks.length > 0 ? Math.round((completed.length / tasks.length) * 100) : 0,
97
- items: tasks
98
- };
99
- }
100
-
101
- // v3-v4 legacy: feature.tasks is a counter object {total, completed, inProgress, pending}
102
- const counters = feature.tasks || {};
103
- const items = [];
104
- const total = counters.total || items.length || 0;
105
- const completedCount = counters.completed ?? items.filter(t => t.status === 'completed').length;
106
- const pendingCount = counters.pending ?? items.filter(t => t.status === 'pending').length;
107
- const inProgressCount = counters.inProgress ?? items.filter(t => t.status === 'in_progress').length;
108
-
109
- return {
110
- total,
111
- completed: completedCount,
112
- pending: pendingCount,
113
- inProgress: inProgressCount,
114
- percentage: total > 0 ? Math.round((completedCount / total) * 100) : 0,
115
- items
116
- };
117
- }
118
-
119
- function getAgentsSummary(feature) {
120
- return feature.activeAgents || [];
121
- }
122
-
123
- function getCheckpointsSummary(feature) {
124
- return (feature.checkpoints || []).map(cp => ({
125
- timestamp: cp.timestamp,
126
- note: cp.note,
127
- tasks: cp.tasksCompleted || []
128
- }));
129
- }
130
-
131
- function extractContractCoverage(validationResult) {
132
- // Look for contract compliance data in validation issues
133
- const interfaceErrors = validationResult.issues.filter(i =>
134
- i.message?.includes('Interface') && i.level === 'error'
135
- );
136
- const methodErrors = validationResult.issues.filter(i =>
137
- i.message?.includes('Method') && i.message?.includes('missing') && i.level === 'error'
138
- );
139
-
140
- return {
141
- missingInterfaces: interfaceErrors.length,
142
- missingMethods: methodErrors.length,
143
- hasIssues: interfaceErrors.length > 0 || methodErrors.length > 0
144
- };
145
- }
146
-
147
- function buildRecapMarkdown(data) {
148
- const { featureName, feature, tasksSummary, agentsSummary, checkpointsSummary, decisions, validationResult, contractCoverage } = data;
149
- const now = new Date().toISOString().split('T')[0];
150
-
151
- let md = `# Recap: ${featureName}\n\n`;
152
- md += `> Generated: ${now} | Phase: ${feature.phase} | Status: ${feature.status}\n\n`;
153
- md += `---\n\n`;
154
-
155
- // Progress
156
- md += `## Progress\n\n`;
157
- md += `| Metric | Value |\n|--------|-------|\n`;
158
- md += `| Tasks completed | ${tasksSummary.completed}/${tasksSummary.total} (${tasksSummary.percentage}%) |\n`;
159
- md += `| Tasks pending | ${tasksSummary.pending} |\n`;
160
- md += `| Tasks in progress | ${tasksSummary.inProgress} |\n`;
161
- md += `| Active agents | ${agentsSummary.length} |\n`;
162
- md += `| Checkpoints | ${checkpointsSummary.length} |\n\n`;
163
-
164
- // Validation Results
165
- md += `## Validation Results\n\n`;
166
- if (validationResult.passed) {
167
- md += `**Status: PASSED** (${validationResult.warnings} warnings)\n\n`;
168
- } else {
169
- md += `**Status: FAILED** (${validationResult.errors} errors, ${validationResult.warnings} warnings)\n\n`;
170
- if (validationResult.issues.length > 0) {
171
- md += `| Level | Issue |\n|-------|-------|\n`;
172
- for (const issue of validationResult.issues.slice(0, 15)) {
173
- md += `| ${issue.level} | ${issue.message} |\n`;
174
- }
175
- md += `\n`;
176
- }
177
- md += `> **Note:** Errors above are from morph-spec static validators (may include false positives).\n`;
178
- md += `> Verify actual build status with the commands in the section below.\n\n`;
179
- }
180
-
181
- // Score Breakdown (when rubric scoring is available)
182
- if (validationResult.scores) {
183
- const scores = validationResult.scores;
184
- md += `### Quality Score: ${scores.score}/10\n\n`;
185
- md += `| Category | Score | Weight | Status |\n|----------|-------|--------|--------|\n`;
186
- const approveThreshold = scores.approveThreshold ?? 8.0;
187
- for (const [category, data] of Object.entries(scores.breakdown)) {
188
- if (category === '_unmapped') continue;
189
- const pct = Math.round(data.weight * 100);
190
- const status = data.score >= approveThreshold ? 'Pass' : data.score >= 6 ? 'Needs work' : 'Critical';
191
- md += `| ${category} | ${Math.round(data.score * 10) / 10}/10 | ${pct}% | ${status} |\n`;
192
- }
193
- md += `\n`;
194
- }
195
-
196
- // Suggested Quality Verification
197
- const agents = agentsSummary;
198
- const hasNextjs = agents.some(a => ['nextjs-expert', 'css-specialist', 'ui-designer'].includes(a));
199
- const hasDotnet = agents.some(a => ['dotnet-senior', 'ef-modeler'].includes(a));
200
- if (hasNextjs || hasDotnet) {
201
- md += `## Suggested Quality Verification\n\n`;
202
- if (hasNextjs) {
203
- md += `**Next.js / TypeScript:**\n\`\`\`bash\nnpx tsc --noEmit\nnpx eslint . --max-warnings 0\n\`\`\`\n\n`;
204
- }
205
- if (hasDotnet) {
206
- md += `**.NET:**\n\`\`\`bash\ndotnet build\ndotnet test\n\`\`\`\n\n`;
207
- }
208
- }
209
-
210
- // Contract Coverage
211
- if (contractCoverage) {
212
- md += `## Contract Compliance\n\n`;
213
- if (contractCoverage.hasIssues) {
214
- md += `- Missing interfaces: ${contractCoverage.missingInterfaces}\n`;
215
- md += `- Missing methods: ${contractCoverage.missingMethods}\n\n`;
216
- } else {
217
- md += `All contracts implemented.\n\n`;
218
- }
219
- }
220
-
221
- // Decisions
222
- if (decisions.length > 0) {
223
- md += `## Decisions Applied\n\n`;
224
- for (const d of decisions) {
225
- if (d.title) {
226
- md += `- **${d.title}**: ${d.decision.slice(0, 100)}${d.decision.length > 100 ? '...' : ''}\n`;
227
- }
228
- }
229
- md += `\n`;
230
- }
231
-
232
- // Agents
233
- if (agentsSummary.length > 0) {
234
- md += `## Active Agents\n\n`;
235
- md += agentsSummary.map(a => `- ${a}`).join('\n');
236
- md += `\n\n`;
237
- }
238
-
239
- // Checkpoints
240
- if (checkpointsSummary.length > 0) {
241
- md += `## Checkpoints\n\n`;
242
- for (const cp of checkpointsSummary) {
243
- md += `- ${cp.timestamp}: ${cp.note}\n`;
244
- }
245
- md += `\n`;
246
- }
247
-
248
- // Tasks Detail
249
- if (tasksSummary.items.length > 0) {
250
- const anyNotes = tasksSummary.items.some(t => t.notes?.length > 0);
251
- md += `## Tasks\n\n`;
252
- if (anyNotes) {
253
- md += `| ID | Title | Status | Notes |\n|----|-------|--------|-------|\n`;
254
- for (const task of tasksSummary.items) {
255
- const statusIcon = task.status === 'completed' ? 'βœ…' : task.status === 'in_progress' ? 'πŸ”„' : '⏳';
256
- const noteCount = task.notes?.length || 0;
257
- const notesCell = noteCount > 0 ? `${noteCount} note${noteCount > 1 ? 's' : ''}` : '-';
258
- md += `| ${task.id} | ${task.title || '-'} | ${statusIcon} ${task.status} | ${notesCell} |\n`;
259
- }
260
- } else {
261
- md += `| ID | Title | Status |\n|----|-------|--------|\n`;
262
- for (const task of tasksSummary.items) {
263
- const statusIcon = task.status === 'completed' ? 'βœ…' : task.status === 'in_progress' ? 'πŸ”„' : '⏳';
264
- md += `| ${task.id} | ${task.title || '-'} | ${statusIcon} ${task.status} |\n`;
265
- }
266
- }
267
- md += `\n`;
268
- }
269
-
270
- // Deviations from Plan
271
- const deviations = tasksSummary.items
272
- .filter(t => t.notes?.some(n => n.type === 'deviation'))
273
- .flatMap(t => t.notes.filter(n => n.type === 'deviation').map(n => ({ taskId: t.id, text: n.text })));
274
- if (deviations.length > 0) {
275
- md += `## Deviations from Plan\n\n`;
276
- for (const d of deviations) {
277
- md += `- **${d.taskId}**: ${d.text}\n`;
278
- }
279
- md += `\n`;
280
- }
281
-
282
- md += `---\n\n*Generated by MORPH-SPEC Recap Generator*\n`;
283
-
284
- return md;
285
- }
1
+ /**
2
+ * Recap Generator
3
+ *
4
+ * Auto-generates recap.md from real project data: state.json, contract compliance,
5
+ * validation results, and decisions. Replaces manual template filling.
6
+ *
7
+ * MORPH-SPEC 5.0 - Recap Generator
8
+ */
9
+
10
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
11
+ import { join, dirname } from 'path';
12
+ import chalk from 'chalk';
13
+ import { loadState, markOutput } from '../../core/state/state-manager.js';
14
+ import { runValidation } from '../validators/validation-runner.js';
15
+
16
+ /**
17
+ * Generate recap.md for a feature
18
+ *
19
+ * @param {string} projectPath - Project root path
20
+ * @param {string} featureName - Feature name
21
+ * @param {Object} options - Options
22
+ * @returns {string} Generated recap content
23
+ */
24
+ export async function generateRecap(projectPath, featureName, options = {}) {
25
+ const state = loadState(false);
26
+ if (!state || !state.features[featureName]) {
27
+ throw new Error(`Feature '${featureName}' not found in state.json`);
28
+ }
29
+
30
+ const feature = state.features[featureName];
31
+ const outputsPath = join(projectPath, '.morph/features', featureName);
32
+
33
+ // Gather data
34
+ const tasksSummary = getTasksSummary(feature);
35
+ const agentsSummary = getAgentsSummary(feature);
36
+ const checkpointsSummary = getCheckpointsSummary(feature);
37
+ // Decision constraints support (stub - decision-constraint-loader removed as orphaned code)
38
+ const decisions = { decisions: [] };
39
+ const validationResult = await runValidation(projectPath, featureName, { verbose: false });
40
+
41
+ // Contract coverage β€” derived from validation issues. v5 keeps contracts
42
+ // inside spec.md, so there is no separate contracts file to probe.
43
+ let contractCoverage = null;
44
+ if (validationResult.issues) {
45
+ const contractIssues = validationResult.issues.filter(i => i.message?.includes('Interface') || i.message?.includes('contracts'));
46
+ if (contractIssues.length > 0) {
47
+ contractCoverage = extractContractCoverage(validationResult);
48
+ }
49
+ }
50
+
51
+ // Generate markdown
52
+ const recap = buildRecapMarkdown({
53
+ featureName,
54
+ feature,
55
+ tasksSummary,
56
+ agentsSummary,
57
+ checkpointsSummary,
58
+ decisions: decisions.decisions,
59
+ validationResult,
60
+ contractCoverage
61
+ });
62
+
63
+ // Write to file
64
+ const recapPath = join(outputsPath, 'recap.md');
65
+ if (!existsSync(dirname(recapPath))) {
66
+ mkdirSync(dirname(recapPath), { recursive: true });
67
+ }
68
+ writeFileSync(recapPath, recap, 'utf-8');
69
+
70
+ // Auto-mark recap output as created in state
71
+ try {
72
+ await markOutput(featureName, 'recap');
73
+ } catch {
74
+ // Non-fatal: state may not exist in all contexts
75
+ }
76
+
77
+ if (!options.quiet) {
78
+ console.log(chalk.green(`\nβœ… Recap generated: ${recapPath}`));
79
+ }
80
+
81
+ return recap;
82
+ }
83
+
84
+ function getTasksSummary(feature) {
85
+ // v2: feature.tasks is an array of task objects
86
+ if (Array.isArray(feature.tasks)) {
87
+ const tasks = feature.tasks;
88
+ const completed = tasks.filter(t => t.status === 'completed');
89
+ const pending = tasks.filter(t => t.status === 'pending');
90
+ const inProgress = tasks.filter(t => t.status === 'in_progress');
91
+ return {
92
+ total: tasks.length,
93
+ completed: completed.length,
94
+ pending: pending.length,
95
+ inProgress: inProgress.length,
96
+ percentage: tasks.length > 0 ? Math.round((completed.length / tasks.length) * 100) : 0,
97
+ items: tasks
98
+ };
99
+ }
100
+
101
+ // v3-v4 legacy: feature.tasks is a counter object {total, completed, inProgress, pending}
102
+ const counters = feature.tasks || {};
103
+ const items = [];
104
+ const total = counters.total || items.length || 0;
105
+ const completedCount = counters.completed ?? items.filter(t => t.status === 'completed').length;
106
+ const pendingCount = counters.pending ?? items.filter(t => t.status === 'pending').length;
107
+ const inProgressCount = counters.inProgress ?? items.filter(t => t.status === 'in_progress').length;
108
+
109
+ return {
110
+ total,
111
+ completed: completedCount,
112
+ pending: pendingCount,
113
+ inProgress: inProgressCount,
114
+ percentage: total > 0 ? Math.round((completedCount / total) * 100) : 0,
115
+ items
116
+ };
117
+ }
118
+
119
+ function getAgentsSummary(feature) {
120
+ return feature.activeAgents || [];
121
+ }
122
+
123
+ function getCheckpointsSummary(feature) {
124
+ return (feature.checkpoints || []).map(cp => ({
125
+ timestamp: cp.timestamp,
126
+ note: cp.note,
127
+ tasks: cp.tasksCompleted || []
128
+ }));
129
+ }
130
+
131
+ function extractContractCoverage(validationResult) {
132
+ // Look for contract compliance data in validation issues
133
+ const interfaceErrors = validationResult.issues.filter(i =>
134
+ i.message?.includes('Interface') && i.level === 'error'
135
+ );
136
+ const methodErrors = validationResult.issues.filter(i =>
137
+ i.message?.includes('Method') && i.message?.includes('missing') && i.level === 'error'
138
+ );
139
+
140
+ return {
141
+ missingInterfaces: interfaceErrors.length,
142
+ missingMethods: methodErrors.length,
143
+ hasIssues: interfaceErrors.length > 0 || methodErrors.length > 0
144
+ };
145
+ }
146
+
147
+ function buildRecapMarkdown(data) {
148
+ const { featureName, feature, tasksSummary, agentsSummary, checkpointsSummary, decisions, validationResult, contractCoverage } = data;
149
+ const now = new Date().toISOString().split('T')[0];
150
+
151
+ let md = `# Recap: ${featureName}\n\n`;
152
+ md += `> Generated: ${now} | Phase: ${feature.phase} | Status: ${feature.status}\n\n`;
153
+ md += `---\n\n`;
154
+
155
+ // Progress
156
+ md += `## Progress\n\n`;
157
+ md += `| Metric | Value |\n|--------|-------|\n`;
158
+ md += `| Tasks completed | ${tasksSummary.completed}/${tasksSummary.total} (${tasksSummary.percentage}%) |\n`;
159
+ md += `| Tasks pending | ${tasksSummary.pending} |\n`;
160
+ md += `| Tasks in progress | ${tasksSummary.inProgress} |\n`;
161
+ md += `| Active agents | ${agentsSummary.length} |\n`;
162
+ md += `| Checkpoints | ${checkpointsSummary.length} |\n\n`;
163
+
164
+ // Validation Results
165
+ md += `## Validation Results\n\n`;
166
+ if (validationResult.passed) {
167
+ md += `**Status: PASSED** (${validationResult.warnings} warnings)\n\n`;
168
+ } else {
169
+ md += `**Status: FAILED** (${validationResult.errors} errors, ${validationResult.warnings} warnings)\n\n`;
170
+ if (validationResult.issues.length > 0) {
171
+ md += `| Level | Issue |\n|-------|-------|\n`;
172
+ for (const issue of validationResult.issues.slice(0, 15)) {
173
+ md += `| ${issue.level} | ${issue.message} |\n`;
174
+ }
175
+ md += `\n`;
176
+ }
177
+ md += `> **Note:** Errors above are from morph-spec static validators (may include false positives).\n`;
178
+ md += `> Verify actual build status with the commands in the section below.\n\n`;
179
+ }
180
+
181
+ // Score Breakdown (when rubric scoring is available)
182
+ if (validationResult.scores) {
183
+ const scores = validationResult.scores;
184
+ md += `### Quality Score: ${scores.score}/10\n\n`;
185
+ md += `| Category | Score | Weight | Status |\n|----------|-------|--------|--------|\n`;
186
+ const approveThreshold = scores.approveThreshold ?? 8.0;
187
+ for (const [category, data] of Object.entries(scores.breakdown)) {
188
+ if (category === '_unmapped') continue;
189
+ const pct = Math.round(data.weight * 100);
190
+ const status = data.score >= approveThreshold ? 'Pass' : data.score >= 6 ? 'Needs work' : 'Critical';
191
+ md += `| ${category} | ${Math.round(data.score * 10) / 10}/10 | ${pct}% | ${status} |\n`;
192
+ }
193
+ md += `\n`;
194
+ }
195
+
196
+ // Suggested Quality Verification
197
+ const agents = agentsSummary;
198
+ const hasNextjs = agents.some(a => ['nextjs-expert', 'css-specialist', 'ui-designer'].includes(a));
199
+ const hasDotnet = agents.some(a => ['dotnet-senior', 'ef-modeler'].includes(a));
200
+ if (hasNextjs || hasDotnet) {
201
+ md += `## Suggested Quality Verification\n\n`;
202
+ if (hasNextjs) {
203
+ md += `**Next.js / TypeScript:**\n\`\`\`bash\nnpx tsc --noEmit\nnpx eslint . --max-warnings 0\n\`\`\`\n\n`;
204
+ }
205
+ if (hasDotnet) {
206
+ md += `**.NET:**\n\`\`\`bash\ndotnet build\ndotnet test\n\`\`\`\n\n`;
207
+ }
208
+ }
209
+
210
+ // Contract Coverage
211
+ if (contractCoverage) {
212
+ md += `## Contract Compliance\n\n`;
213
+ if (contractCoverage.hasIssues) {
214
+ md += `- Missing interfaces: ${contractCoverage.missingInterfaces}\n`;
215
+ md += `- Missing methods: ${contractCoverage.missingMethods}\n\n`;
216
+ } else {
217
+ md += `All contracts implemented.\n\n`;
218
+ }
219
+ }
220
+
221
+ // Decisions
222
+ if (decisions.length > 0) {
223
+ md += `## Decisions Applied\n\n`;
224
+ for (const d of decisions) {
225
+ if (d.title) {
226
+ md += `- **${d.title}**: ${d.decision.slice(0, 100)}${d.decision.length > 100 ? '...' : ''}\n`;
227
+ }
228
+ }
229
+ md += `\n`;
230
+ }
231
+
232
+ // Agents
233
+ if (agentsSummary.length > 0) {
234
+ md += `## Active Agents\n\n`;
235
+ md += agentsSummary.map(a => `- ${a}`).join('\n');
236
+ md += `\n\n`;
237
+ }
238
+
239
+ // Checkpoints
240
+ if (checkpointsSummary.length > 0) {
241
+ md += `## Checkpoints\n\n`;
242
+ for (const cp of checkpointsSummary) {
243
+ md += `- ${cp.timestamp}: ${cp.note}\n`;
244
+ }
245
+ md += `\n`;
246
+ }
247
+
248
+ // Tasks Detail
249
+ if (tasksSummary.items.length > 0) {
250
+ const anyNotes = tasksSummary.items.some(t => t.notes?.length > 0);
251
+ md += `## Tasks\n\n`;
252
+ if (anyNotes) {
253
+ md += `| ID | Title | Status | Notes |\n|----|-------|--------|-------|\n`;
254
+ for (const task of tasksSummary.items) {
255
+ const statusIcon = task.status === 'completed' ? 'βœ…' : task.status === 'in_progress' ? 'πŸ”„' : '⏳';
256
+ const noteCount = task.notes?.length || 0;
257
+ const notesCell = noteCount > 0 ? `${noteCount} note${noteCount > 1 ? 's' : ''}` : '-';
258
+ md += `| ${task.id} | ${task.title || '-'} | ${statusIcon} ${task.status} | ${notesCell} |\n`;
259
+ }
260
+ } else {
261
+ md += `| ID | Title | Status |\n|----|-------|--------|\n`;
262
+ for (const task of tasksSummary.items) {
263
+ const statusIcon = task.status === 'completed' ? 'βœ…' : task.status === 'in_progress' ? 'πŸ”„' : '⏳';
264
+ md += `| ${task.id} | ${task.title || '-'} | ${statusIcon} ${task.status} |\n`;
265
+ }
266
+ }
267
+ md += `\n`;
268
+ }
269
+
270
+ // Deviations from Plan
271
+ const deviations = tasksSummary.items
272
+ .filter(t => t.notes?.some(n => n.type === 'deviation'))
273
+ .flatMap(t => t.notes.filter(n => n.type === 'deviation').map(n => ({ taskId: t.id, text: n.text })));
274
+ if (deviations.length > 0) {
275
+ md += `## Deviations from Plan\n\n`;
276
+ for (const d of deviations) {
277
+ md += `- **${d.taskId}**: ${d.text}\n`;
278
+ }
279
+ md += `\n`;
280
+ }
281
+
282
+ md += `---\n\n*Generated by MORPH-SPEC Recap Generator*\n`;
283
+
284
+ return md;
285
+ }
@@ -8,12 +8,12 @@
8
8
  * Signals checked (in order):
9
9
  * - designSystemDivergence: boolean β€” conflicting design sources detected
10
10
  * - standardsViolations: string[] β€” violations of mandate.md proibiΓ§Γ΅es
11
- * - evalScore: number β€” morph-eval score (pause if < 8)
11
+ * - evalScore: number β€” morph-eval score (pause if < 9)
12
12
  * - taskCount: number β€” total tasks (pause if > 20)
13
13
  */
14
14
 
15
15
  const SCOPE_TASK_THRESHOLD = 20;
16
- const MIN_EVAL_SCORE = 8;
16
+ const MIN_EVAL_SCORE = 9;
17
17
 
18
18
  /**
19
19
  * @param {object} config - .morph/config/config.json contents (uses `trust`)