@polymorphism-tech/morph-spec 4.7.1 → 4.8.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 (138) hide show
  1. package/.morph/analytics/threads-log.jsonl +54 -0
  2. package/.morph/state.json +198 -0
  3. package/LICENSE +1 -2
  4. package/README.md +379 -414
  5. package/bin/morph-spec.js +57 -403
  6. package/bin/validate.js +2 -26
  7. package/claude-plugin.json +2 -2
  8. package/docs/ARCHITECTURE.md +43 -46
  9. package/docs/CHEATSHEET.md +203 -221
  10. package/docs/COMMAND-FLOWS.md +319 -289
  11. package/docs/QUICKSTART.md +2 -8
  12. package/docs/plans/2026-02-22-claude-docs-morph-alignment-analysis.md +2 -0
  13. package/docs/plans/2026-02-22-claude-settings.md +2 -0
  14. package/docs/plans/2026-02-22-morph-cc-alignment-impl.md +2 -0
  15. package/docs/plans/2026-02-22-morph-spec-next.md +2 -0
  16. package/docs/plans/2026-02-22-native-alignment-design.md +2 -0
  17. package/docs/plans/2026-02-22-native-alignment-impl.md +2 -0
  18. package/docs/plans/2026-02-22-native-enrichment-design.md +2 -0
  19. package/docs/plans/2026-02-22-native-enrichment.md +2 -0
  20. package/docs/plans/2026-02-23-ddd-architecture-refactor.md +2 -0
  21. package/docs/plans/2026-02-23-ddd-nextsteps.md +2 -0
  22. package/docs/plans/2026-02-23-infra-architect-refactor.md +2 -0
  23. package/docs/plans/2026-02-23-nextjs-code-review-design.md +2 -1
  24. package/docs/plans/2026-02-23-nextjs-code-review-impl.md +2 -0
  25. package/docs/plans/2026-02-23-nextjs-standards-design.md +2 -1
  26. package/docs/plans/2026-02-23-nextjs-standards-impl.md +2 -0
  27. package/docs/plans/2026-02-24-cli-radical-simplification.md +592 -0
  28. package/docs/plans/2026-02-24-framework-failure-points.md +125 -0
  29. package/docs/plans/2026-02-24-morph-init-design.md +337 -0
  30. package/docs/plans/2026-02-24-morph-init-impl.md +1269 -0
  31. package/docs/plans/2026-02-24-tutorial-command-design.md +71 -0
  32. package/docs/plans/2026-02-24-tutorial-command.md +298 -0
  33. package/framework/CLAUDE.md +2 -2
  34. package/framework/commands/morph-proposal.md +3 -3
  35. package/framework/hooks/README.md +11 -10
  36. package/framework/hooks/claude-code/notification/approval-reminder.js +2 -0
  37. package/framework/hooks/claude-code/post-tool-use/dispatch.js +1 -1
  38. package/framework/hooks/claude-code/pre-tool-use/protect-readonly-files.js +4 -55
  39. package/framework/hooks/claude-code/session-start/inject-morph-context.js +20 -5
  40. package/framework/hooks/claude-code/statusline.py +6 -1
  41. package/framework/hooks/claude-code/stop/validate-completion.js +1 -1
  42. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +1 -1
  43. package/framework/hooks/dev/check-sync-health.js +117 -0
  44. package/framework/hooks/dev/guard-version-numbers.js +57 -0
  45. package/framework/hooks/dev/sync-standards-registry.js +60 -0
  46. package/framework/hooks/dev/sync-template-registry.js +60 -0
  47. package/framework/hooks/dev/validate-skill-format.js +70 -0
  48. package/framework/hooks/dev/validate-standard-format.js +73 -0
  49. package/framework/hooks/shared/payload-utils.js +39 -0
  50. package/framework/hooks/shared/state-reader.js +25 -1
  51. package/framework/rules/morph-workflow.md +1 -1
  52. package/framework/skills/level-0-meta/morph-init/SKILL.md +216 -0
  53. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
  54. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +4 -4
  55. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
  56. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +192 -191
  57. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +181 -180
  58. package/framework/skills/level-1-workflows/phase-design/SKILL.md +339 -338
  59. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +254 -253
  60. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +168 -170
  61. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +284 -283
  62. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +246 -245
  63. package/framework/templates/examples/design-system-examples.md +1 -1
  64. package/framework/templates/ui/FluentDesignTheme.cs +1 -1
  65. package/framework/templates/ui/MudTheme.cs +1 -1
  66. package/framework/templates/ui/design-system.css +1 -1
  67. package/package.json +4 -2
  68. package/scripts/bump-version.js +248 -0
  69. package/scripts/install-dev-hooks.js +138 -0
  70. package/src/commands/agents/index.js +1 -2
  71. package/src/commands/index.js +13 -16
  72. package/src/commands/project/doctor.js +100 -14
  73. package/src/commands/project/index.js +7 -10
  74. package/src/commands/project/init.js +398 -555
  75. package/src/commands/project/install-plugin-cmd.js +28 -0
  76. package/src/commands/project/setup-infra-cmd.js +12 -0
  77. package/src/commands/project/tutorial.js +115 -0
  78. package/src/commands/project/update.js +22 -37
  79. package/src/commands/state/approve.js +213 -221
  80. package/src/commands/state/index.js +0 -1
  81. package/src/commands/state/state.js +337 -365
  82. package/src/commands/templates/index.js +0 -4
  83. package/src/commands/trust/trust.js +1 -93
  84. package/src/commands/utils/index.js +1 -5
  85. package/src/commands/validation/index.js +1 -5
  86. package/src/core/registry/command-registry.js +11 -285
  87. package/src/core/state/state-manager.js +5 -2
  88. package/src/lib/detectors/index.js +81 -87
  89. package/src/lib/detectors/structure-detector.js +275 -273
  90. package/src/lib/generators/recap-generator.js +232 -225
  91. package/src/lib/installers/mcp-installer.js +18 -3
  92. package/src/scripts/global-install.js +34 -0
  93. package/src/scripts/install-plugin.js +126 -0
  94. package/src/scripts/setup-infra.js +203 -0
  95. package/src/utils/agents-installer.js +10 -1
  96. package/src/utils/hooks-installer.js +70 -17
  97. package/CLAUDE.md +0 -77
  98. package/docs/claude-alignment-report.md +0 -137
  99. package/docs/examples/order-management/contracts.cs +0 -84
  100. package/docs/examples/order-management/proposal.md +0 -24
  101. package/docs/examples/order-management/spec.md +0 -162
  102. package/src/commands/feature/create-story.js +0 -362
  103. package/src/commands/feature/index.js +0 -6
  104. package/src/commands/feature/shard-spec.js +0 -225
  105. package/src/commands/feature/sprint-status.js +0 -250
  106. package/src/commands/generation/generate-onboarding.js +0 -169
  107. package/src/commands/generation/generate.js +0 -276
  108. package/src/commands/generation/index.js +0 -5
  109. package/src/commands/learning/capture-pattern.js +0 -121
  110. package/src/commands/learning/index.js +0 -5
  111. package/src/commands/learning/search-patterns.js +0 -126
  112. package/src/commands/mcp/mcp.js +0 -102
  113. package/src/commands/project/changes.js +0 -66
  114. package/src/commands/project/cost.js +0 -179
  115. package/src/commands/project/detect.js +0 -114
  116. package/src/commands/project/diff.js +0 -278
  117. package/src/commands/project/revert.js +0 -173
  118. package/src/commands/project/standards.js +0 -80
  119. package/src/commands/project/sync.js +0 -167
  120. package/src/commands/project/update-agents.js +0 -23
  121. package/src/commands/state/rollback-phase.js +0 -185
  122. package/src/commands/templates/template-customize.js +0 -87
  123. package/src/commands/templates/template-list.js +0 -114
  124. package/src/commands/templates/template-show.js +0 -129
  125. package/src/commands/templates/template-validate.js +0 -91
  126. package/src/commands/utils/troubleshoot.js +0 -222
  127. package/src/commands/validation/analyze-blazor-concurrency.js +0 -193
  128. package/src/commands/validation/lint-fluent.js +0 -352
  129. package/src/commands/validation/validate-blazor-state.js +0 -210
  130. package/src/commands/validation/validate-blazor.js +0 -156
  131. package/src/commands/validation/validate-css.js +0 -84
  132. package/src/lib/detectors/conversation-analyzer.js +0 -163
  133. package/src/lib/learning/index.js +0 -7
  134. package/src/lib/learning/learning-system.js +0 -520
  135. package/src/lib/troubleshooting/index.js +0 -8
  136. package/src/lib/troubleshooting/troubleshoot-grep.js +0 -198
  137. package/src/lib/troubleshooting/troubleshoot-index.js +0 -144
  138. package/src/llm/environment-detector.js +0 -43
@@ -1,225 +1,232 @@
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 3.0 - Phase B: Spec-Driven Pipeline
8
- */
9
-
10
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
11
- import { join, dirname } from 'path';
12
- import chalk from 'chalk';
13
- import { loadState } 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
- if (!options.quiet) {
71
- console.log(chalk.green(`\n✅ Recap generated: ${recapPath}`));
72
- }
73
-
74
- return recap;
75
- }
76
-
77
- function getTasksSummary(feature) {
78
- // v2: feature.tasks is an array of task objects
79
- if (Array.isArray(feature.tasks)) {
80
- const tasks = feature.tasks;
81
- const completed = tasks.filter(t => t.status === 'completed');
82
- const pending = tasks.filter(t => t.status === 'pending');
83
- const inProgress = tasks.filter(t => t.status === 'in_progress');
84
- return {
85
- total: tasks.length,
86
- completed: completed.length,
87
- pending: pending.length,
88
- inProgress: inProgress.length,
89
- percentage: tasks.length > 0 ? Math.round((completed.length / tasks.length) * 100) : 0,
90
- items: tasks
91
- };
92
- }
93
-
94
- // v3: feature.tasks is a counter object {total, completed, inProgress, pending}
95
- // Use feature.taskList for individual items if available
96
- const counters = feature.tasks || {};
97
- const items = feature.taskList || [];
98
- const total = counters.total || items.length || 0;
99
- const completedCount = counters.completed ?? items.filter(t => t.status === 'completed').length;
100
- const pendingCount = counters.pending ?? items.filter(t => t.status === 'pending').length;
101
- const inProgressCount = counters.inProgress ?? items.filter(t => t.status === 'in_progress').length;
102
-
103
- return {
104
- total,
105
- completed: completedCount,
106
- pending: pendingCount,
107
- inProgress: inProgressCount,
108
- percentage: total > 0 ? Math.round((completedCount / total) * 100) : 0,
109
- items
110
- };
111
- }
112
-
113
- function getAgentsSummary(feature) {
114
- return feature.activeAgents || [];
115
- }
116
-
117
- function getCheckpointsSummary(feature) {
118
- return (feature.checkpoints || []).map(cp => ({
119
- timestamp: cp.timestamp,
120
- note: cp.note,
121
- tasks: cp.tasksCompleted || []
122
- }));
123
- }
124
-
125
- function extractContractCoverage(validationResult) {
126
- // Look for contract compliance data in validation issues
127
- const interfaceErrors = validationResult.issues.filter(i =>
128
- i.message?.includes('Interface') && i.level === 'error'
129
- );
130
- const methodErrors = validationResult.issues.filter(i =>
131
- i.message?.includes('Method') && i.message?.includes('missing') && i.level === 'error'
132
- );
133
-
134
- return {
135
- missingInterfaces: interfaceErrors.length,
136
- missingMethods: methodErrors.length,
137
- hasIssues: interfaceErrors.length > 0 || methodErrors.length > 0
138
- };
139
- }
140
-
141
- function buildRecapMarkdown(data) {
142
- const { featureName, feature, tasksSummary, agentsSummary, checkpointsSummary, decisions, validationResult, contractCoverage } = data;
143
- const now = new Date().toISOString().split('T')[0];
144
-
145
- let md = `# Recap: ${featureName}\n\n`;
146
- md += `> Generated: ${now} | Phase: ${feature.phase} | Status: ${feature.status}\n\n`;
147
- md += `---\n\n`;
148
-
149
- // Progress
150
- md += `## Progress\n\n`;
151
- md += `| Metric | Value |\n|--------|-------|\n`;
152
- md += `| Tasks completed | ${tasksSummary.completed}/${tasksSummary.total} (${tasksSummary.percentage}%) |\n`;
153
- md += `| Tasks pending | ${tasksSummary.pending} |\n`;
154
- md += `| Tasks in progress | ${tasksSummary.inProgress} |\n`;
155
- md += `| Active agents | ${agentsSummary.length} |\n`;
156
- md += `| Checkpoints | ${checkpointsSummary.length} |\n\n`;
157
-
158
- // Validation Results
159
- md += `## Validation Results\n\n`;
160
- if (validationResult.passed) {
161
- md += `**Status: PASSED** (${validationResult.warnings} warnings)\n\n`;
162
- } else {
163
- md += `**Status: FAILED** (${validationResult.errors} errors, ${validationResult.warnings} warnings)\n\n`;
164
- if (validationResult.issues.length > 0) {
165
- md += `| Level | Issue |\n|-------|-------|\n`;
166
- for (const issue of validationResult.issues.slice(0, 15)) {
167
- md += `| ${issue.level} | ${issue.message} |\n`;
168
- }
169
- md += `\n`;
170
- }
171
- }
172
-
173
- // Contract Coverage
174
- if (contractCoverage) {
175
- md += `## Contract Compliance\n\n`;
176
- if (contractCoverage.hasIssues) {
177
- md += `- Missing interfaces: ${contractCoverage.missingInterfaces}\n`;
178
- md += `- Missing methods: ${contractCoverage.missingMethods}\n\n`;
179
- } else {
180
- md += `All contracts implemented.\n\n`;
181
- }
182
- }
183
-
184
- // Decisions
185
- if (decisions.length > 0) {
186
- md += `## Decisions Applied\n\n`;
187
- for (const d of decisions) {
188
- if (d.title) {
189
- md += `- **${d.title}**: ${d.decision.slice(0, 100)}${d.decision.length > 100 ? '...' : ''}\n`;
190
- }
191
- }
192
- md += `\n`;
193
- }
194
-
195
- // Agents
196
- if (agentsSummary.length > 0) {
197
- md += `## Active Agents\n\n`;
198
- md += agentsSummary.map(a => `- ${a}`).join('\n');
199
- md += `\n\n`;
200
- }
201
-
202
- // Checkpoints
203
- if (checkpointsSummary.length > 0) {
204
- md += `## Checkpoints\n\n`;
205
- for (const cp of checkpointsSummary) {
206
- md += `- ${cp.timestamp}: ${cp.note}\n`;
207
- }
208
- md += `\n`;
209
- }
210
-
211
- // Tasks Detail
212
- if (tasksSummary.items.length > 0) {
213
- md += `## Tasks\n\n`;
214
- md += `| ID | Title | Status |\n|----|-------|--------|\n`;
215
- for (const task of tasksSummary.items) {
216
- const statusIcon = task.status === 'completed' ? '✅' : task.status === 'in_progress' ? '🔄' : '⏳';
217
- md += `| ${task.id} | ${task.title || '-'} | ${statusIcon} ${task.status} |\n`;
218
- }
219
- md += `\n`;
220
- }
221
-
222
- md += `---\n\n*Generated by MORPH-SPEC Recap Generator*\n`;
223
-
224
- return md;
225
- }
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 3.0 - Phase B: Spec-Driven Pipeline
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: feature.tasks is a counter object {total, completed, inProgress, pending}
102
+ // Use feature.taskList for individual items if available
103
+ const counters = feature.tasks || {};
104
+ const items = feature.taskList || [];
105
+ const total = counters.total || items.length || 0;
106
+ const completedCount = counters.completed ?? items.filter(t => t.status === 'completed').length;
107
+ const pendingCount = counters.pending ?? items.filter(t => t.status === 'pending').length;
108
+ const inProgressCount = counters.inProgress ?? items.filter(t => t.status === 'in_progress').length;
109
+
110
+ return {
111
+ total,
112
+ completed: completedCount,
113
+ pending: pendingCount,
114
+ inProgress: inProgressCount,
115
+ percentage: total > 0 ? Math.round((completedCount / total) * 100) : 0,
116
+ items
117
+ };
118
+ }
119
+
120
+ function getAgentsSummary(feature) {
121
+ return feature.activeAgents || [];
122
+ }
123
+
124
+ function getCheckpointsSummary(feature) {
125
+ return (feature.checkpoints || []).map(cp => ({
126
+ timestamp: cp.timestamp,
127
+ note: cp.note,
128
+ tasks: cp.tasksCompleted || []
129
+ }));
130
+ }
131
+
132
+ function extractContractCoverage(validationResult) {
133
+ // Look for contract compliance data in validation issues
134
+ const interfaceErrors = validationResult.issues.filter(i =>
135
+ i.message?.includes('Interface') && i.level === 'error'
136
+ );
137
+ const methodErrors = validationResult.issues.filter(i =>
138
+ i.message?.includes('Method') && i.message?.includes('missing') && i.level === 'error'
139
+ );
140
+
141
+ return {
142
+ missingInterfaces: interfaceErrors.length,
143
+ missingMethods: methodErrors.length,
144
+ hasIssues: interfaceErrors.length > 0 || methodErrors.length > 0
145
+ };
146
+ }
147
+
148
+ function buildRecapMarkdown(data) {
149
+ const { featureName, feature, tasksSummary, agentsSummary, checkpointsSummary, decisions, validationResult, contractCoverage } = data;
150
+ const now = new Date().toISOString().split('T')[0];
151
+
152
+ let md = `# Recap: ${featureName}\n\n`;
153
+ md += `> Generated: ${now} | Phase: ${feature.phase} | Status: ${feature.status}\n\n`;
154
+ md += `---\n\n`;
155
+
156
+ // Progress
157
+ md += `## Progress\n\n`;
158
+ md += `| Metric | Value |\n|--------|-------|\n`;
159
+ md += `| Tasks completed | ${tasksSummary.completed}/${tasksSummary.total} (${tasksSummary.percentage}%) |\n`;
160
+ md += `| Tasks pending | ${tasksSummary.pending} |\n`;
161
+ md += `| Tasks in progress | ${tasksSummary.inProgress} |\n`;
162
+ md += `| Active agents | ${agentsSummary.length} |\n`;
163
+ md += `| Checkpoints | ${checkpointsSummary.length} |\n\n`;
164
+
165
+ // Validation Results
166
+ md += `## Validation Results\n\n`;
167
+ if (validationResult.passed) {
168
+ md += `**Status: PASSED** (${validationResult.warnings} warnings)\n\n`;
169
+ } else {
170
+ md += `**Status: FAILED** (${validationResult.errors} errors, ${validationResult.warnings} warnings)\n\n`;
171
+ if (validationResult.issues.length > 0) {
172
+ md += `| Level | Issue |\n|-------|-------|\n`;
173
+ for (const issue of validationResult.issues.slice(0, 15)) {
174
+ md += `| ${issue.level} | ${issue.message} |\n`;
175
+ }
176
+ md += `\n`;
177
+ }
178
+ }
179
+
180
+ // Contract Coverage
181
+ if (contractCoverage) {
182
+ md += `## Contract Compliance\n\n`;
183
+ if (contractCoverage.hasIssues) {
184
+ md += `- Missing interfaces: ${contractCoverage.missingInterfaces}\n`;
185
+ md += `- Missing methods: ${contractCoverage.missingMethods}\n\n`;
186
+ } else {
187
+ md += `All contracts implemented.\n\n`;
188
+ }
189
+ }
190
+
191
+ // Decisions
192
+ if (decisions.length > 0) {
193
+ md += `## Decisions Applied\n\n`;
194
+ for (const d of decisions) {
195
+ if (d.title) {
196
+ md += `- **${d.title}**: ${d.decision.slice(0, 100)}${d.decision.length > 100 ? '...' : ''}\n`;
197
+ }
198
+ }
199
+ md += `\n`;
200
+ }
201
+
202
+ // Agents
203
+ if (agentsSummary.length > 0) {
204
+ md += `## Active Agents\n\n`;
205
+ md += agentsSummary.map(a => `- ${a}`).join('\n');
206
+ md += `\n\n`;
207
+ }
208
+
209
+ // Checkpoints
210
+ if (checkpointsSummary.length > 0) {
211
+ md += `## Checkpoints\n\n`;
212
+ for (const cp of checkpointsSummary) {
213
+ md += `- ${cp.timestamp}: ${cp.note}\n`;
214
+ }
215
+ md += `\n`;
216
+ }
217
+
218
+ // Tasks Detail
219
+ if (tasksSummary.items.length > 0) {
220
+ md += `## Tasks\n\n`;
221
+ md += `| ID | Title | Status |\n|----|-------|--------|\n`;
222
+ for (const task of tasksSummary.items) {
223
+ const statusIcon = task.status === 'completed' ? '✅' : task.status === 'in_progress' ? '🔄' : '⏳';
224
+ md += `| ${task.id} | ${task.title || '-'} | ${statusIcon} ${task.status} |\n`;
225
+ }
226
+ md += `\n`;
227
+ }
228
+
229
+ md += `---\n\n*Generated by MORPH-SPEC Recap Generator*\n`;
230
+
231
+ return md;
232
+ }
@@ -96,6 +96,21 @@ export function checkPrerequisites(mcpEntry) {
96
96
  return results;
97
97
  }
98
98
 
99
+ /**
100
+ * Patch MCP config for the current platform.
101
+ * On Windows, npx must be invoked via `cmd /C` to avoid ENOENT errors.
102
+ * @param {Object} config - MCP server config { command, args, env? }
103
+ * @returns {Object} Patched config (new object, original unmodified)
104
+ */
105
+ export function patchConfigForPlatform(config) {
106
+ if (process.platform !== 'win32' || config.command !== 'npx') return config;
107
+ return {
108
+ ...config,
109
+ command: 'cmd',
110
+ args: ['/C', 'npx', ...(config.args || [])]
111
+ };
112
+ }
113
+
99
114
  /**
100
115
  * Install auto-installable MCPs (no credentials, no prerequisites)
101
116
  * @param {string} targetPath - Project root directory
@@ -106,12 +121,12 @@ export async function installAutoMcps(targetPath, mcpsToInstall) {
106
121
  const servers = {};
107
122
 
108
123
  for (const [name, entry] of Object.entries(mcpsToInstall)) {
109
- const config = { ...entry.install.config };
124
+ let config = { ...entry.install.config };
110
125
  // Only include env if it has actual values
111
126
  if (config.env && Object.values(config.env).every(v => !v)) {
112
127
  delete config.env;
113
128
  }
114
- servers[name] = config;
129
+ servers[name] = patchConfigForPlatform(config);
115
130
  }
116
131
 
117
132
  return installMcpServers(targetPath, servers);
@@ -126,7 +141,7 @@ export async function installAutoMcps(targetPath, mcpsToInstall) {
126
141
  * @returns {Promise<Object>} Result from installMcpServers
127
142
  */
128
143
  export async function installMcpWithCredentials(targetPath, name, mcpEntry, credentialValues) {
129
- const config = { ...mcpEntry.install.config };
144
+ const config = patchConfigForPlatform({ ...mcpEntry.install.config });
130
145
  config.env = { ...config.env, ...credentialValues };
131
146
  return installMcpServers(targetPath, { [name]: config });
132
147
  }
@@ -0,0 +1,34 @@
1
+ // src/scripts/global-install.js
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { homedir } from 'os';
5
+ import { mkdir, copyFile } from 'fs/promises';
6
+ import { existsSync } from 'fs';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const FRAMEWORK_DIR = join(__dirname, '..', '..', 'framework');
10
+
11
+ /**
12
+ * Install morph-init skill to a Claude global skills directory.
13
+ * @param {string} claudeDir - target ~/.claude dir (defaults to real ~/.claude)
14
+ */
15
+ export async function installGlobalSkill(claudeDir = join(homedir(), '.claude')) {
16
+ const src = join(FRAMEWORK_DIR, 'skills', 'level-0-meta', 'morph-init', 'SKILL.md');
17
+ const destDir = join(claudeDir, 'skills', 'morph-init');
18
+ const dest = join(destDir, 'SKILL.md');
19
+
20
+ if (!existsSync(src)) {
21
+ console.warn(`morph-init skill not found at ${src} — skipping global install`);
22
+ return;
23
+ }
24
+
25
+ await mkdir(destDir, { recursive: true });
26
+ await copyFile(src, dest);
27
+ }
28
+
29
+ // Run as postinstall script
30
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
31
+ installGlobalSkill()
32
+ .then(() => console.log('✓ morph-init skill installed to ~/.claude/skills/'))
33
+ .catch(e => console.warn('Could not install morph-init skill globally:', e.message));
34
+ }