@polymorphism-tech/morph-spec 8.1.2 → 8.2.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 (45) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +16 -1
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +6 -4
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +8 -5
  7. package/framework/MORPH.md +28 -4
  8. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -1
  9. package/framework/hooks/claude-code/core/worktree-context.js +43 -0
  10. package/framework/hooks/shared/build-check.js +8 -1
  11. package/framework/hooks/shared/payload-utils.js +16 -2
  12. package/framework/hooks/shared/phase-utils.js +2 -0
  13. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  14. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  15. package/framework/skills/core/morph-apply/SKILL.md +13 -8
  16. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  17. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  18. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  19. package/framework/skills/core/morph-implement/SKILL.md +22 -10
  20. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  21. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  22. package/framework/skills/core/morph-review/SKILL.md +19 -12
  23. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  24. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  25. package/package.json +1 -1
  26. package/src/commands/project/fleet.js +54 -0
  27. package/src/commands/project/worktree.js +206 -43
  28. package/src/commands/state/advance.js +8 -0
  29. package/src/commands/state/create.js +6 -0
  30. package/src/commands/state/handoff.js +49 -0
  31. package/src/commands/state/score.js +13 -2
  32. package/src/commands/tasks/dag.js +11 -3
  33. package/src/core/paths/output-schema.js +28 -0
  34. package/src/lib/generators/handoff-generator.js +267 -0
  35. package/src/lib/generators/index.js +1 -3
  36. package/src/lib/stack/stack-profile.js +40 -9
  37. package/src/lib/tasks/tasks-json-parser.js +21 -0
  38. package/src/lib/tasks/test-runner.js +26 -4
  39. package/src/lib/validators/validation-runner.js +4 -1
  40. package/src/lib/worktree/env-allocator.js +149 -0
  41. package/src/lib/worktree/fleet-scanner.js +156 -0
  42. package/src/lib/context/context-builder.js +0 -289
  43. package/src/lib/generators/context-generator.js +0 -519
  44. package/src/lib/generators/mandate-generator.js +0 -468
  45. package/src/lib/generators/recap-generator.js +0 -285
@@ -1,285 +0,0 @@
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 ?? 9.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
- }