@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,114 +0,0 @@
1
- import { join } from 'path';
2
- import ora from 'ora';
3
- import chalk from 'chalk';
4
- import { logger } from '../../utils/logger.js';
5
- import { detectProject, getDetectionSummary } from '../../lib/detectors/index.js';
6
- import { ensureDir, writeFile, readJson, writeJson, pathExists } from '../../utils/file-copier.js';
7
-
8
- export async function detectCommand(options) {
9
- const targetPath = options.path || process.cwd();
10
-
11
- logger.header('MORPH-SPEC Project Detection');
12
- logger.dim(`Analyzing: ${targetPath}`);
13
- logger.blank();
14
-
15
- const spinner = ora('Detecting project structure...').start();
16
-
17
- try {
18
- // Run detection
19
- const results = await detectProject(targetPath, {
20
- structure: true,
21
- config: true,
22
- conversation: true,
23
- generateStandards: true
24
- });
25
-
26
- spinner.succeed('Detection complete!');
27
- logger.blank();
28
-
29
- // Display summary
30
- logger.header('Detection Results');
31
- logger.blank();
32
-
33
- // Stack
34
- logger.info(`Stack: ${chalk.cyan(results.structure.stack)}`);
35
- logger.info(`Architecture: ${chalk.cyan(results.structure.architecture)}`);
36
- if (results.structure.uiLibrary) {
37
- logger.info(`UI Library: ${chalk.cyan(results.structure.uiLibrary)}`);
38
- }
39
- logger.blank();
40
-
41
- // Technologies
42
- if (results.config.technologies.length > 0) {
43
- logger.header('Technologies:');
44
- results.config.technologies.forEach(tech => {
45
- logger.dim(` - ${tech}`);
46
- });
47
- logger.blank();
48
- }
49
-
50
- // Patterns
51
- if (results.structure.patterns.length > 0) {
52
- logger.header('Patterns:');
53
- results.structure.patterns.forEach(pattern => {
54
- logger.dim(` - ${pattern}`);
55
- });
56
- logger.blank();
57
- }
58
-
59
- // Recommendations
60
- if (results.inferred.recommendations.length > 0) {
61
- logger.header('Recommendations:');
62
- results.inferred.recommendations.forEach(rec => {
63
- logger.warn(` ⚠ ${rec}`);
64
- });
65
- logger.blank();
66
- }
67
-
68
- // Save results if requested
69
- if (options.save !== false) {
70
- spinner.start('Saving detection results...');
71
-
72
- const outputDir = join(targetPath, '.morph', 'project', 'context');
73
- await ensureDir(outputDir);
74
-
75
- // Save detection log
76
- const logPath = join(outputDir, 'detection-log.md');
77
- const summary = getDetectionSummary(results);
78
- await writeFile(logPath, summary);
79
-
80
- // Save inferred standards
81
- const standardsDir = join(targetPath, '.morph', 'project', 'standards');
82
- await ensureDir(standardsDir);
83
-
84
- const standardsPath = join(standardsDir, 'inferred.md');
85
- await writeFile(standardsPath, results.inferred.markdown);
86
-
87
- // Update config.json with detected stack and architecture
88
- const configPath = join(targetPath, '.morph', 'config', 'config.json');
89
- if (await pathExists(configPath)) {
90
- const projectConfig = await readJson(configPath);
91
- projectConfig.project = projectConfig.project || {};
92
- projectConfig.project.stack = results.structure.stack;
93
- projectConfig.project.architecture = results.structure.architecture;
94
- await writeJson(configPath, projectConfig);
95
- }
96
-
97
- spinner.succeed('Results saved!');
98
- logger.dim(` - ${logPath}`);
99
- logger.dim(` - ${standardsPath}`);
100
- }
101
-
102
- // Verbose output
103
- if (options.verbose) {
104
- logger.blank();
105
- logger.header('Detailed Results (JSON):');
106
- console.log(JSON.stringify(results, null, 2));
107
- }
108
-
109
- } catch (error) {
110
- spinner.fail('Detection failed');
111
- logger.error(error.message);
112
- process.exit(1);
113
- }
114
- }
@@ -1,278 +0,0 @@
1
- /**
2
- * MORPH-SPEC Diff Command
3
- *
4
- * Track changes to specifications since last approval.
5
- * Shows added/removed functional requirements, entity changes, and new decisions.
6
- *
7
- * Usage:
8
- * morph-spec diff <feature-name> [output-file]
9
- * morph-spec diff <feature-name> spec.md
10
- * morph-spec diff <feature-name> --json
11
- */
12
-
13
- import fs from 'fs';
14
- import path from 'path';
15
- import chalk from 'chalk';
16
- import { getFeature } from '../../core/state/state-manager.js';
17
-
18
- /**
19
- * Load snapshot from checkpoints directory
20
- */
21
- function loadSnapshot(featureName, outputFile) {
22
- const snapshotDir = path.join(process.cwd(), '.morph/checkpoints', featureName);
23
- const snapshotPath = path.join(snapshotDir, outputFile);
24
-
25
- if (!fs.existsSync(snapshotPath)) {
26
- return null;
27
- }
28
-
29
- return fs.readFileSync(snapshotPath, 'utf8');
30
- }
31
-
32
- /**
33
- * Load current version of output file
34
- */
35
- function loadCurrent(featureName, outputFile) {
36
- const currentPath = path.join(process.cwd(), '.morph/features', featureName, outputFile);
37
-
38
- if (!fs.existsSync(currentPath)) {
39
- return null;
40
- }
41
-
42
- return fs.readFileSync(currentPath, 'utf8');
43
- }
44
-
45
- /**
46
- * Extract functional requirements from spec content
47
- */
48
- function extractRequirements(content) {
49
- const requirements = [];
50
- const regex = /###\s+(FR[-_]?\d+):?\s*(.+)/gi;
51
- let match;
52
- while ((match = regex.exec(content)) !== null) {
53
- requirements.push({ id: match[1], title: match[2].trim() });
54
- }
55
- return requirements;
56
- }
57
-
58
- /**
59
- * Extract entities from spec or contracts
60
- */
61
- function extractEntities(content) {
62
- const entities = [];
63
- // Markdown entities
64
- const mdRegex = /###\s+(?:Entity|Table):?\s*(.+)/gi;
65
- let match;
66
- while ((match = mdRegex.exec(content)) !== null) {
67
- entities.push(match[1].trim());
68
- }
69
- // C# records
70
- const csRegex = /public\s+record\s+(\w+)/g;
71
- while ((match = csRegex.exec(content)) !== null) {
72
- entities.push(match[1]);
73
- }
74
- return entities;
75
- }
76
-
77
- /**
78
- * Extract ADRs from decisions
79
- */
80
- function extractDecisions(content) {
81
- const decisions = [];
82
- const regex = /###\s+ADR[-_]?\d*:?\s*(.+)/gi;
83
- let match;
84
- while ((match = regex.exec(content)) !== null) {
85
- decisions.push(match[1].trim());
86
- }
87
- return decisions;
88
- }
89
-
90
- /**
91
- * Compute diff between two content versions
92
- */
93
- function computeDiff(oldContent, newContent, outputFile) {
94
- const diff = {
95
- file: outputFile,
96
- added: [],
97
- removed: [],
98
- modified: []
99
- };
100
-
101
- if (!oldContent) {
102
- diff.added.push('(entire file is new)');
103
- return diff;
104
- }
105
-
106
- if (!newContent) {
107
- diff.removed.push('(file was deleted)');
108
- return diff;
109
- }
110
-
111
- // Compare requirements
112
- if (outputFile.endsWith('spec.md')) {
113
- const oldReqs = extractRequirements(oldContent);
114
- const newReqs = extractRequirements(newContent);
115
-
116
- const oldIds = new Set(oldReqs.map(r => r.id));
117
- const newIds = new Set(newReqs.map(r => r.id));
118
-
119
- for (const req of newReqs) {
120
- if (!oldIds.has(req.id)) {
121
- diff.added.push(`${req.id}: ${req.title}`);
122
- }
123
- }
124
- for (const req of oldReqs) {
125
- if (!newIds.has(req.id)) {
126
- diff.removed.push(`${req.id}: ${req.title}`);
127
- }
128
- }
129
-
130
- // Compare entities
131
- const oldEntities = new Set(extractEntities(oldContent));
132
- const newEntities = new Set(extractEntities(newContent));
133
-
134
- for (const entity of newEntities) {
135
- if (!oldEntities.has(entity)) {
136
- diff.added.push(`Entity: ${entity}`);
137
- }
138
- }
139
- for (const entity of oldEntities) {
140
- if (!newEntities.has(entity)) {
141
- diff.removed.push(`Entity: ${entity}`);
142
- }
143
- }
144
- }
145
-
146
- // Compare decisions
147
- if (outputFile.endsWith('decisions.md')) {
148
- const oldDecisions = new Set(extractDecisions(oldContent));
149
- const newDecisions = new Set(extractDecisions(newContent));
150
-
151
- for (const decision of newDecisions) {
152
- if (!oldDecisions.has(decision)) {
153
- diff.added.push(`ADR: ${decision}`);
154
- }
155
- }
156
- for (const decision of oldDecisions) {
157
- if (!newDecisions.has(decision)) {
158
- diff.removed.push(`ADR: ${decision}`);
159
- }
160
- }
161
- }
162
-
163
- // Line count change
164
- const oldLines = oldContent.split('\n').length;
165
- const newLines = newContent.split('\n').length;
166
- if (oldLines !== newLines) {
167
- const delta = newLines - oldLines;
168
- diff.modified.push(`${delta > 0 ? '+' : ''}${delta} lines (${oldLines} → ${newLines})`);
169
- }
170
-
171
- return diff;
172
- }
173
-
174
- /**
175
- * Save current version as snapshot for future diffs
176
- */
177
- export function saveSnapshot(featureName) {
178
- const outputDir = path.join(process.cwd(), '.morph/features', featureName);
179
- const snapshotDir = path.join(process.cwd(), '.morph/checkpoints', featureName);
180
-
181
- if (!fs.existsSync(outputDir)) return;
182
-
183
- fs.mkdirSync(snapshotDir, { recursive: true });
184
-
185
- const files = fs.readdirSync(outputDir).filter(f => f.endsWith('.md') || f.endsWith('.cs') || f.endsWith('.json'));
186
- for (const file of files) {
187
- const src = path.join(outputDir, file);
188
- const dest = path.join(snapshotDir, file);
189
- fs.copyFileSync(src, dest);
190
- }
191
- }
192
-
193
- /**
194
- * Main diff command
195
- */
196
- export async function diffCommand(featureName, outputFile, options = {}) {
197
- if (!featureName) {
198
- console.error(chalk.red('Error: Feature name required'));
199
- console.error(chalk.gray('Usage: morph-spec diff <feature-name> [output-file]'));
200
- process.exit(1);
201
- }
202
-
203
- const outputDir = path.join(process.cwd(), '.morph/features', featureName);
204
- if (!fs.existsSync(outputDir)) {
205
- console.error(chalk.red(`Error: Feature directory not found: ${outputDir}`));
206
- process.exit(1);
207
- }
208
-
209
- // Determine which files to diff
210
- let files;
211
- if (outputFile) {
212
- files = [outputFile];
213
- } else {
214
- files = fs.readdirSync(outputDir).filter(f => f.endsWith('.md') || f.endsWith('.cs'));
215
- }
216
-
217
- const diffs = files.map(file => {
218
- const oldContent = loadSnapshot(featureName, file);
219
- const newContent = loadCurrent(featureName, file);
220
- return computeDiff(oldContent, newContent, file);
221
- }).filter(d => d.added.length > 0 || d.removed.length > 0 || d.modified.length > 0);
222
-
223
- // JSON output
224
- if (options.json) {
225
- console.log(JSON.stringify({ feature: featureName, diffs }, null, 2));
226
- return;
227
- }
228
-
229
- // Visual output
230
- console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
231
- console.log(chalk.cyan('│ MORPH-SPEC DIFF │'));
232
- console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
233
-
234
- console.log(chalk.white.bold(`Feature: ${featureName}`));
235
- console.log(chalk.gray('Changes since last snapshot:\n'));
236
-
237
- if (diffs.length === 0) {
238
- console.log(chalk.green(' No changes detected since last snapshot.'));
239
- console.log(chalk.gray(' Run "morph-spec diff-save <feature>" to create a snapshot.\n'));
240
- return;
241
- }
242
-
243
- for (const diff of diffs) {
244
- console.log(chalk.white.bold(` ${diff.file}:`));
245
-
246
- for (const item of diff.added) {
247
- console.log(chalk.green(` + ${item}`));
248
- }
249
- for (const item of diff.removed) {
250
- console.log(chalk.red(` - ${item}`));
251
- }
252
- for (const item of diff.modified) {
253
- console.log(chalk.yellow(` ~ ${item}`));
254
- }
255
- console.log('');
256
- }
257
-
258
- const totalAdded = diffs.reduce((sum, d) => sum + d.added.length, 0);
259
- const totalRemoved = diffs.reduce((sum, d) => sum + d.removed.length, 0);
260
- console.log(chalk.gray('─'.repeat(60)));
261
- console.log(chalk.gray(`${totalAdded} addition(s), ${totalRemoved} removal(s) across ${diffs.length} file(s)\n`));
262
- }
263
-
264
- /**
265
- * Save snapshot command
266
- */
267
- export async function diffSaveCommand(featureName) {
268
- if (!featureName) {
269
- console.error(chalk.red('Error: Feature name required'));
270
- process.exit(1);
271
- }
272
-
273
- saveSnapshot(featureName);
274
- console.log(chalk.green(`✓ Snapshot saved for '${featureName}'`));
275
- console.log(chalk.gray(' Future "morph-spec diff" will compare against this snapshot.\n'));
276
- }
277
-
278
- export default diffCommand;
@@ -1,173 +0,0 @@
1
- /**
2
- * MORPH-SPEC Revert Command
3
- *
4
- * Revert a feature to a previous phase, optionally deleting later-phase outputs.
5
- *
6
- * Usage:
7
- * morph-spec revert <feature-name> --to <phase>
8
- * morph-spec revert <feature-name> --to design
9
- * morph-spec revert <feature-name> --to design --delete-outputs
10
- */
11
-
12
- import fs from 'fs';
13
- import path from 'path';
14
- import chalk from 'chalk';
15
- import { loadState, saveState, getFeature } from '../../core/state/state-manager.js';
16
-
17
- /**
18
- * Phase order for revert logic
19
- */
20
- const PHASE_ORDER = [
21
- { key: 'proposal', order: 0, outputs: ['proposal.md'] },
22
- { key: 'setup', order: 1, outputs: [] },
23
- { key: 'uiux', order: 2, outputs: ['ui-design-system.md', 'ui-mockups.md', 'ui-components.md', 'ui-flows.md'] },
24
- { key: 'design', order: 3, outputs: ['spec.md', 'schema-analysis.md', 'contracts.cs', 'decisions.md'] },
25
- { key: 'clarify', order: 4, outputs: ['clarifications.md'] },
26
- { key: 'tasks', order: 5, outputs: ['tasks.md', 'tasks.json'] },
27
- { key: 'implement', order: 6, outputs: ['recap.md'] },
28
- { key: 'sync', order: 7, outputs: [] }
29
- ];
30
-
31
- /**
32
- * Get outputs that would be affected by reverting to a phase
33
- */
34
- function getAffectedOutputs(targetPhase) {
35
- const targetOrder = PHASE_ORDER.find(p => p.key === targetPhase)?.order ?? -1;
36
- const affected = [];
37
-
38
- for (const phase of PHASE_ORDER) {
39
- if (phase.order > targetOrder) {
40
- affected.push(...phase.outputs.map(o => ({ phase: phase.key, file: o })));
41
- }
42
- }
43
-
44
- return affected;
45
- }
46
-
47
- /**
48
- * Main revert command
49
- */
50
- export async function revertCommand(featureName, options = {}) {
51
- if (!featureName) {
52
- console.error(chalk.red('Error: Feature name required'));
53
- console.error(chalk.gray('Usage: morph-spec revert <feature-name> --to <phase>'));
54
- process.exit(1);
55
- }
56
-
57
- const targetPhase = options.to;
58
- if (!targetPhase) {
59
- console.error(chalk.red('Error: Target phase required (--to <phase>)'));
60
- console.error(chalk.gray(`Valid phases: ${PHASE_ORDER.map(p => p.key).join(', ')}`));
61
- process.exit(1);
62
- }
63
-
64
- const targetDef = PHASE_ORDER.find(p => p.key === targetPhase);
65
- if (!targetDef) {
66
- console.error(chalk.red(`Error: Unknown phase: ${targetPhase}`));
67
- console.error(chalk.gray(`Valid phases: ${PHASE_ORDER.map(p => p.key).join(', ')}`));
68
- process.exit(1);
69
- }
70
-
71
- const feature = getFeature(featureName);
72
- if (!feature) {
73
- console.error(chalk.red(`Error: Feature '${featureName}' not found`));
74
- process.exit(1);
75
- }
76
-
77
- const currentDef = PHASE_ORDER.find(p => p.key === feature.phase);
78
- if (currentDef && targetDef.order >= currentDef.order) {
79
- console.error(chalk.red(`Error: Cannot revert forward. Current phase: ${feature.phase}, target: ${targetPhase}`));
80
- process.exit(1);
81
- }
82
-
83
- // Get affected outputs
84
- const affected = getAffectedOutputs(targetPhase);
85
- const outputDir = path.join(process.cwd(), '.morph/features', featureName);
86
- const existingAffected = affected.filter(a =>
87
- fs.existsSync(path.join(outputDir, a.file))
88
- );
89
-
90
- // Show what will happen
91
- console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
92
- console.log(chalk.cyan('│ MORPH-SPEC REVERT │'));
93
- console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
94
-
95
- console.log(chalk.white.bold(`Feature: ${featureName}`));
96
- console.log(chalk.gray(`Current phase: ${feature.phase} → Reverting to: ${targetPhase}\n`));
97
-
98
- if (existingAffected.length > 0) {
99
- if (options.deleteOutputs) {
100
- console.log(chalk.yellow('The following files will be DELETED:'));
101
- } else {
102
- console.log(chalk.yellow('The following outputs will be marked as draft (files preserved):'));
103
- }
104
- for (const item of existingAffected) {
105
- console.log(chalk.gray(` - ${item.file} (from ${item.phase} phase)`));
106
- }
107
- console.log('');
108
- }
109
-
110
- // Update state
111
- const state = loadState();
112
- const stateFeature = state.features[featureName];
113
-
114
- // Revert phase
115
- stateFeature.phase = targetPhase;
116
- stateFeature.updatedAt = new Date().toISOString();
117
-
118
- // Mark affected outputs as not created in state
119
- const outputKeyMap = {
120
- 'proposal.md': 'proposal',
121
- 'spec.md': 'spec',
122
- 'schema-analysis.md': 'schemaAnalysis',
123
- 'contracts.cs': 'contracts',
124
- 'decisions.md': 'decisions',
125
- 'clarifications.md': 'clarifications',
126
- 'tasks.md': 'tasks',
127
- 'tasks.json': 'tasks',
128
- 'ui-design-system.md': 'uiDesignSystem',
129
- 'ui-mockups.md': 'uiMockups',
130
- 'ui-components.md': 'uiComponents',
131
- 'ui-flows.md': 'uiFlows',
132
- 'recap.md': 'recap'
133
- };
134
-
135
- for (const item of affected) {
136
- const key = outputKeyMap[item.file];
137
- if (key && stateFeature.outputs[key]) {
138
- stateFeature.outputs[key].created = false;
139
- }
140
- }
141
-
142
- // Reset approval gates for later phases
143
- for (const phase of PHASE_ORDER) {
144
- if (phase.order > targetDef.order && stateFeature.approvalGates?.[phase.key]) {
145
- stateFeature.approvalGates[phase.key].approved = false;
146
- stateFeature.approvalGates[phase.key].timestamp = null;
147
- stateFeature.approvalGates[phase.key].approvedBy = null;
148
- }
149
- }
150
-
151
- // Reset tasks if reverting before tasks phase
152
- if (targetDef.order < 5) {
153
- stateFeature.tasks = { total: 0, completed: 0, inProgress: 0, pending: 0 };
154
- }
155
-
156
- saveState(state);
157
-
158
- // Delete files if requested
159
- if (options.deleteOutputs && existingAffected.length > 0) {
160
- for (const item of existingAffected) {
161
- const filePath = path.join(outputDir, item.file);
162
- if (fs.existsSync(filePath)) {
163
- fs.unlinkSync(filePath);
164
- }
165
- }
166
- console.log(chalk.yellow(`Deleted ${existingAffected.length} file(s).`));
167
- }
168
-
169
- console.log(chalk.green(`✓ Feature '${featureName}' reverted to phase: ${targetPhase}`));
170
- console.log(chalk.gray(` State updated. Run 'morph-spec status ${featureName}' to see current status.\n`));
171
- }
172
-
173
- export default revertCommand;
@@ -1,80 +0,0 @@
1
- /**
2
- * Standards CLI Commands
3
- *
4
- * Provides browsable access to the framework standards registry.
5
- * Usage:
6
- * morph-spec standards --list
7
- * morph-spec standards --search <query>
8
- * morph-spec standards --show <id>
9
- * morph-spec standards --category <cat>
10
- */
11
-
12
- import { readFileSync } from 'fs';
13
- import { join } from 'path';
14
- import { fileURLToPath } from 'url';
15
- import { logger } from '../../utils/logger.js';
16
-
17
- const __dirname = fileURLToPath(new URL('.', import.meta.url));
18
- const REGISTRY_PATH = join(__dirname, '..', '..', '..', 'framework', 'standards', 'STANDARDS.json');
19
-
20
- function loadRegistry() {
21
- return JSON.parse(readFileSync(REGISTRY_PATH, 'utf8')).standards;
22
- }
23
-
24
- /**
25
- * List all standards, optionally filtered by category.
26
- * @param {{ json?: boolean, category?: string }} opts
27
- * @returns {Promise<Array>}
28
- */
29
- export async function listStandards({ json, category } = {}) {
30
- let standards = loadRegistry();
31
- if (category) {
32
- standards = standards.filter(s => s.category === category);
33
- }
34
- if (json) return standards;
35
- standards.forEach(s =>
36
- logger.info(`${s.id.padEnd(55)} ${s.category}`)
37
- );
38
- return standards;
39
- }
40
-
41
- /**
42
- * Search standards by query string (matches id, name, or tags).
43
- * @param {string} query
44
- * @param {{ json?: boolean }} opts
45
- * @returns {Promise<Array>}
46
- */
47
- export async function searchStandards(query, { json } = {}) {
48
- const q = query.toLowerCase();
49
- const results = loadRegistry().filter(s =>
50
- s.name.toLowerCase().includes(q) ||
51
- s.id.toLowerCase().includes(q) ||
52
- s.tags.some(t => t.toLowerCase().includes(q))
53
- );
54
- if (json) return results;
55
- if (results.length === 0) {
56
- logger.warn(`No standards found matching "${query}"`);
57
- } else {
58
- results.forEach(s => logger.info(`${s.id}: ${s.name} [${s.category}]`));
59
- }
60
- return results;
61
- }
62
-
63
- /**
64
- * Show the full content of a standard by its id.
65
- * @param {string} id
66
- * @returns {Promise<string|null>}
67
- */
68
- export async function showStandard(id) {
69
- const standard = loadRegistry().find(s => s.id === id);
70
- if (!standard) {
71
- logger.error(`Standard '${id}' not found. Use --list to see available standards.`);
72
- return null;
73
- }
74
- const content = readFileSync(
75
- join(__dirname, '..', '..', '..', 'framework', 'standards', standard.path),
76
- 'utf8'
77
- );
78
- logger.info(content);
79
- return content;
80
- }