@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,167 +0,0 @@
1
- import { join } from 'path';
2
- import { readdirSync, existsSync } from 'fs';
3
- import ora from 'ora';
4
- import chalk from 'chalk';
5
- import { logger } from '../../utils/logger.js';
6
- import { readFile, writeFile, ensureDir } from '../../utils/file-copier.js';
7
- import { analyzeConversation } from '../../lib/detectors/conversation-analyzer.js';
8
-
9
- export async function syncCommand(options) {
10
- const targetPath = options.path || process.cwd();
11
-
12
- logger.header('MORPH-SPEC Standards Sync');
13
- logger.dim(`Project: ${targetPath}`);
14
- logger.blank();
15
-
16
- const spinner = ora('Analyzing feature decisions...').start();
17
-
18
- try {
19
- // 1. Find all decisions.md files
20
- const outputsPath = join(targetPath, '.morph', 'project', 'outputs');
21
-
22
- if (!existsSync(outputsPath)) {
23
- spinner.warn('No features found');
24
- logger.dim('Create features first using MORPH workflow.');
25
- return;
26
- }
27
-
28
- const features = readdirSync(outputsPath, { withFileTypes: true })
29
- .filter(dirent => dirent.isDirectory())
30
- .map(dirent => dirent.name);
31
-
32
- if (features.length === 0) {
33
- spinner.warn('No features found');
34
- return;
35
- }
36
-
37
- spinner.text = `Found ${features.length} features, analyzing decisions...`;
38
-
39
- // 2. Analyze all decisions
40
- const conversation = await analyzeConversation(targetPath);
41
-
42
- if (conversation.decisions.length === 0) {
43
- spinner.warn('No decisions found to sync');
44
- logger.dim('Features may not have decisions.md files yet.');
45
- return;
46
- }
47
-
48
- spinner.succeed(`Found ${conversation.decisions.length} decisions`);
49
- logger.blank();
50
-
51
- // 3. Group by category
52
- const byCategory = groupByCategory(conversation.decisions);
53
-
54
- // 4. Display candidates
55
- logger.header('Decisions by Category:');
56
- logger.blank();
57
-
58
- let totalCandidates = 0;
59
-
60
- for (const [category, decisions] of Object.entries(byCategory)) {
61
- if (decisions.length > 0) {
62
- logger.info(chalk.cyan(`${formatCategory(category)} (${decisions.length})`));
63
- decisions.slice(0, 3).forEach(d => {
64
- const preview = d.text.split('\n')[0].substring(0, 70);
65
- logger.dim(` - ${preview}...`);
66
- });
67
- if (decisions.length > 3) {
68
- logger.dim(` ... and ${decisions.length - 3} more`);
69
- }
70
- logger.blank();
71
- totalCandidates += decisions.length;
72
- }
73
- }
74
-
75
- // 5. Sync (for now, just report - interactive approval would be added later)
76
- if (options.dryRun) {
77
- logger.warn('Dry run mode - no files updated');
78
- logger.dim(`Would update ${Object.keys(byCategory).length} standard files`);
79
- return;
80
- }
81
-
82
- spinner.start('Updating standards...');
83
-
84
- const standardsDir = join(targetPath, '.morph', 'project', 'standards');
85
- await ensureDir(standardsDir);
86
-
87
- let updatedFiles = [];
88
-
89
- for (const [category, decisions] of Object.entries(byCategory)) {
90
- if (decisions.length === 0) continue;
91
-
92
- const fileName = `${category}.md`;
93
- const filePath = join(standardsDir, fileName);
94
-
95
- // Read existing or create new
96
- let content = '';
97
- if (existsSync(filePath)) {
98
- content = await readFile(filePath);
99
- } else {
100
- content = `# ${formatCategory(category)} Standards\n\n> Project-specific standards for ${category}\n\n`;
101
- }
102
-
103
- // Append new decisions
104
- content += `\n## Decisions from Features (${new Date().toISOString().split('T')[0]})\n\n`;
105
-
106
- decisions.forEach((d, idx) => {
107
- const preview = d.text.split('\n').slice(0, 5).join('\n');
108
- content += `### Decision ${idx + 1}\n\n${preview}\n\n`;
109
- });
110
-
111
- await writeFile(filePath, content);
112
- updatedFiles.push(fileName);
113
- }
114
-
115
- spinner.succeed(`Updated ${updatedFiles.length} standard files`);
116
-
117
- updatedFiles.forEach(file => {
118
- logger.dim(` - ${file}`);
119
- });
120
-
121
- logger.blank();
122
- logger.success('Standards sync complete!');
123
- logger.dim('Commit these changes with a sync: message.');
124
-
125
- } catch (error) {
126
- spinner.fail('Sync failed');
127
- logger.error(error.message);
128
- process.exit(1);
129
- }
130
- }
131
-
132
- /**
133
- * Group decisions by category
134
- */
135
- function groupByCategory(decisions) {
136
- const categories = {
137
- 'coding': [],
138
- 'architecture': [],
139
- 'azure': [],
140
- 'integrations': [],
141
- 'ui-ux': []
142
- };
143
-
144
- decisions.forEach(decision => {
145
- const category = decision.category || 'coding';
146
- if (categories[category]) {
147
- categories[category].push(decision);
148
- }
149
- });
150
-
151
- return categories;
152
- }
153
-
154
- /**
155
- * Format category name
156
- */
157
- function formatCategory(category) {
158
- const names = {
159
- 'coding': 'Coding Standards',
160
- 'architecture': 'Architecture Patterns',
161
- 'azure': 'Azure & Infrastructure',
162
- 'integrations': 'Integration Patterns',
163
- 'ui-ux': 'UI/UX Standards'
164
- };
165
-
166
- return names[category] || category;
167
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * update-agents command
3
- *
4
- * Refreshes .claude/agents/ with the latest tier-1 and tier-2 MORPH agents
5
- * from the framework. Useful after upgrading morph-spec to pick up new or
6
- * updated agent definitions without re-running full init.
7
- */
8
-
9
- import { join, dirname } from 'path';
10
- import { fileURLToPath } from 'url';
11
- import { installAgents } from '../../utils/agents-installer.js';
12
- import { logger } from '../../utils/logger.js';
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const FRAMEWORK_DIR = join(__dirname, '..', '..', '..', 'framework');
16
-
17
- export async function updateAgentsCommand(args, options) {
18
- const projectDir = process.cwd();
19
-
20
- logger.info('Updating native subagents in .claude/agents/...');
21
- await installAgents(projectDir, FRAMEWORK_DIR);
22
- logger.success('Native subagents updated. Claude Code will discover them on next session start.');
23
- }
@@ -1,185 +0,0 @@
1
- /**
2
- * MORPH-SPEC Rollback Phase Command
3
- *
4
- * Rolls back a feature to a previous phase, marking outputs as draft.
5
- *
6
- * Usage:
7
- * morph-spec rollback <feature-name> <target-phase>
8
- *
9
- * Example:
10
- * morph-spec rollback scheduled-reports design
11
- */
12
-
13
- import fs from 'fs';
14
- import path from 'path';
15
- import chalk from 'chalk';
16
- import { loadState, saveState, getFeature, updateFeature } from '../../core/state/state-manager.js';
17
-
18
- // Phase order
19
- const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
20
-
21
- /**
22
- * Get phase index
23
- */
24
- function getPhaseIndex(phase) {
25
- return PHASE_ORDER.indexOf(phase);
26
- }
27
-
28
- /**
29
- * Get outputs that should be marked as draft when rolling back
30
- */
31
- function getOutputsToReset(fromPhase, toPhase) {
32
- const fromIndex = getPhaseIndex(fromPhase);
33
- const toIndex = getPhaseIndex(toPhase);
34
-
35
- // Map phases to their outputs
36
- const phaseOutputs = {
37
- 'proposal': ['proposal'],
38
- 'setup': [],
39
- 'uiux': ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
40
- 'design': ['spec', 'contracts', 'decisions'],
41
- 'clarify': [],
42
- 'tasks': ['tasks'],
43
- 'implement': ['recap'],
44
- 'sync': []
45
- };
46
-
47
- const outputsToReset = [];
48
-
49
- // Collect all outputs from phases after target
50
- for (let i = toIndex + 1; i <= fromIndex; i++) {
51
- const phase = PHASE_ORDER[i];
52
- if (phaseOutputs[phase]) {
53
- outputsToReset.push(...phaseOutputs[phase]);
54
- }
55
- }
56
-
57
- return outputsToReset;
58
- }
59
-
60
- /**
61
- * Main command handler
62
- */
63
- export async function rollbackPhaseCommand(feature, targetPhase, options = {}) {
64
- console.log(chalk.cyan('\n╔════════════════════════════════════════════════╗'));
65
- console.log(chalk.cyan('ā•‘ MORPH-SPEC ROLLBACK ā•‘'));
66
- console.log(chalk.cyan('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n'));
67
-
68
- // Validate target phase
69
- if (!PHASE_ORDER.includes(targetPhase)) {
70
- console.log(chalk.red(`āŒ Invalid phase: ${targetPhase}`));
71
- console.log(chalk.gray('\nValid phases:'));
72
- PHASE_ORDER.forEach(p => console.log(chalk.gray(` - ${p}`)));
73
- process.exit(1);
74
- }
75
-
76
- // Get current feature state
77
- let featureState;
78
- try {
79
- featureState = getFeature(feature);
80
- } catch (error) {
81
- console.log(chalk.red(`āŒ Feature not found: ${feature}`));
82
- console.log(chalk.gray(' Run: morph-spec state list'));
83
- process.exit(1);
84
- }
85
-
86
- if (!featureState) {
87
- console.log(chalk.red(`āŒ Feature not found: ${feature}`));
88
- process.exit(1);
89
- }
90
-
91
- const currentPhase = featureState.phase || 'proposal';
92
- const currentIndex = getPhaseIndex(currentPhase);
93
- const targetIndex = getPhaseIndex(targetPhase);
94
-
95
- // Check if rollback makes sense
96
- if (targetIndex >= currentIndex) {
97
- console.log(chalk.yellow(`āš ļø Cannot rollback: target phase '${targetPhase}' is not before current phase '${currentPhase}'`));
98
- process.exit(1);
99
- }
100
-
101
- // Get outputs to reset
102
- const outputsToReset = getOutputsToReset(currentPhase, targetPhase);
103
-
104
- // Show what will happen
105
- console.log(chalk.gray('Feature:'), feature);
106
- console.log(chalk.gray('Current Phase:'), currentPhase);
107
- console.log(chalk.gray('Target Phase:'), targetPhase);
108
- console.log('');
109
-
110
- if (outputsToReset.length > 0) {
111
- console.log(chalk.yellow('Outputs that will be marked as draft:'));
112
- outputsToReset.forEach(o => {
113
- console.log(chalk.gray(` - ${o}`));
114
- });
115
- console.log('');
116
- }
117
-
118
- // Confirm unless --force
119
- if (!options.force) {
120
- console.log(chalk.yellow('āš ļø This will:'));
121
- console.log(chalk.gray(' 1. Set phase back to: ' + targetPhase));
122
- console.log(chalk.gray(' 2. Reset task progress'));
123
- console.log(chalk.gray(' 3. Mark outputs as not created (files remain on disk)'));
124
- console.log('');
125
- console.log(chalk.gray('Run with --force to confirm.'));
126
- process.exit(1);
127
- }
128
-
129
- // Perform rollback
130
- try {
131
- const state = loadState();
132
-
133
- // Update phase
134
- state.features[feature].phase = targetPhase;
135
- state.features[feature].status = 'in_progress';
136
- state.features[feature].updatedAt = new Date().toISOString();
137
-
138
- // Reset outputs
139
- for (const output of outputsToReset) {
140
- if (state.features[feature].outputs[output]) {
141
- state.features[feature].outputs[output].created = false;
142
- }
143
- }
144
-
145
- // Reset tasks if rolling back before tasks phase
146
- if (targetIndex < getPhaseIndex('tasks')) {
147
- state.features[feature].tasks = {
148
- total: 0,
149
- completed: 0,
150
- inProgress: 0,
151
- pending: 0
152
- };
153
- }
154
-
155
- // Add checkpoint for rollback
156
- state.features[feature].checkpoints = state.features[feature].checkpoints || [];
157
- state.features[feature].checkpoints.push({
158
- timestamp: new Date().toISOString(),
159
- phase: targetPhase,
160
- completedTasks: 0,
161
- note: `Rollback from ${currentPhase} to ${targetPhase}`
162
- });
163
-
164
- // Track skipped phases (if any were skipped)
165
- if (!state.features[feature].skippedPhases) {
166
- state.features[feature].skippedPhases = [];
167
- }
168
-
169
- saveState(state);
170
-
171
- console.log(chalk.green('āœ… Rollback completed!'));
172
- console.log('');
173
- console.log(chalk.gray('Current state:'));
174
- console.log(chalk.gray(` Phase: ${targetPhase}`));
175
- console.log(chalk.gray(` Status: in_progress`));
176
- console.log('');
177
- console.log(chalk.cyan('Next steps:'));
178
- console.log(chalk.gray(` 1. Review and update outputs for phase: ${targetPhase}`));
179
- console.log(chalk.gray(` 2. Run: morph-spec session-summary ${feature}`));
180
-
181
- } catch (error) {
182
- console.log(chalk.red(`āŒ Rollback failed: ${error.message}`));
183
- process.exit(1);
184
- }
185
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * Template Customize Command - Copy a framework template to project for customization
3
- */
4
-
5
- import { copyFileSync, existsSync, mkdirSync } from 'fs';
6
- import { join, dirname } from 'path';
7
- import chalk from 'chalk';
8
- import { logger } from '../../utils/logger.js';
9
- import { getTemplateById, resolveTemplatePathById } from '../../core/templates/template-registry.js';
10
-
11
- /**
12
- * Customize template command - copies framework template to project .morph/framework/templates/
13
- */
14
- export async function templateCustomizeCommand(templateId, options) {
15
- logger.header(`Customizing Template: ${templateId}`);
16
- logger.blank();
17
-
18
- try {
19
- // 1. Validate template exists
20
- const template = getTemplateById(templateId);
21
-
22
- if (!template) {
23
- logger.error(`Template not found: ${templateId}`);
24
- logger.dim('\nšŸ’” Use `morph-spec template list` to see available templates');
25
- process.exit(1);
26
- }
27
-
28
- // 3. Resolve source path (framework template)
29
- const projectPath = options.projectPath || process.cwd();
30
- const sourcePath = resolveTemplatePathById(templateId, projectPath);
31
-
32
- if (!sourcePath) {
33
- logger.error(`Framework template file not found for ${templateId}`);
34
- process.exit(1);
35
- }
36
-
37
- // 4. Destination is always project-local .morph/framework/templates/
38
- const destDir = join(projectPath, '.morph/framework/templates', dirname(template.path));
39
- const destPath = join(projectPath, '.morph/framework/templates', template.path);
40
-
41
- // 5. Check if already exists
42
- if (existsSync(destPath) && !options.force) {
43
- logger.warn('Template already customized at this location');
44
- logger.dim(` ${destPath}`);
45
- logger.dim('\n Use --force to overwrite');
46
- process.exit(1);
47
- }
48
-
49
- // 6. Copy template
50
- logger.dim(`Source: ${sourcePath}`);
51
- logger.dim(`Destination: ${destPath}`);
52
- logger.blank();
53
-
54
- mkdirSync(destDir, { recursive: true });
55
- copyFileSync(sourcePath, destPath);
56
-
57
- logger.success('āœ… Template copied for customization');
58
- logger.blank();
59
-
60
- // 7. Show next steps
61
- logger.info(chalk.bold('šŸ“ Next Steps'));
62
- console.log(chalk.dim(' 1. Edit the template file:'));
63
- console.log(chalk.cyan(` ${destPath}`));
64
- console.log(chalk.dim('\n 2. Template will now be used instead of framework version'));
65
- console.log(chalk.dim(' (Stack-specific templates take precedence)'));
66
- console.log(chalk.dim('\n 3. Test rendering:'));
67
- console.log(chalk.cyan(` morph-spec template render ${templateId} output.md '{"FEATURE_NAME":"test"}'`));
68
-
69
- if (!options.local) {
70
- console.log(chalk.dim('\n 4. To share across stacks, use --local flag'));
71
- }
72
-
73
- logger.blank();
74
-
75
- // 8. Show warning about updates
76
- logger.warn('āš ļø Customized templates won\'t receive framework updates');
77
- logger.dim(' To get updates: delete customized version and re-run this command');
78
- logger.blank();
79
-
80
- } catch (error) {
81
- logger.error(`Failed to customize template: ${error.message}`);
82
- if (options.verbose) {
83
- console.error(error.stack);
84
- }
85
- process.exit(1);
86
- }
87
- }
@@ -1,114 +0,0 @@
1
- /**
2
- * Template List Command - List all available templates with filtering
3
- */
4
-
5
- import chalk from 'chalk';
6
- import { logger } from '../../utils/logger.js';
7
- import {
8
- getAllTemplates,
9
- getTemplatesByCategory,
10
- getTemplatesByPhase,
11
- getRequiredTemplates,
12
- getRegistryStats
13
- } from '../../core/templates/template-registry.js';
14
-
15
- /**
16
- * Format template for display
17
- */
18
- function formatTemplate(template) {
19
- const { id, name, category, phase, required, stackSpecific, location, engine, deprecated } = template;
20
-
21
- const badges = [];
22
- if (required) badges.push(chalk.green('[REQUIRED]'));
23
- if (deprecated) badges.push(chalk.red('[DEPRECATED]'));
24
- if (stackSpecific) badges.push(chalk.yellow('[STACK-SPECIFIC]'));
25
- else badges.push(chalk.cyan('[UNIVERSAL]'));
26
- if (engine === 'handlebars') badges.push(chalk.blue('[HBS]'));
27
-
28
- const badgeStr = badges.join(' ');
29
-
30
- return {
31
- id: chalk.bold(id),
32
- name,
33
- location: location === 'framework' ? chalk.cyan('framework') : chalk.yellow('stack'),
34
- category: chalk.dim(category),
35
- phase: chalk.dim(phase || 'N/A'),
36
- badges: badgeStr
37
- };
38
- }
39
-
40
- /**
41
- * List templates command
42
- */
43
- export async function templateListCommand(options) {
44
- logger.header('MORPH-SPEC Templates');
45
- logger.blank();
46
-
47
- try {
48
- let templates;
49
-
50
- // Apply filters
51
- if (options.category) {
52
- templates = getTemplatesByCategory(options.category);
53
- logger.dim(`Category: ${options.category}`);
54
- } else if (options.phase) {
55
- templates = getTemplatesByPhase(options.phase);
56
- logger.dim(`Phase: ${options.phase}`);
57
- } else if (options.required) {
58
- templates = getRequiredTemplates();
59
- logger.dim('Required templates only');
60
- } else {
61
- templates = getAllTemplates();
62
- logger.dim('All templates');
63
- }
64
-
65
- logger.blank();
66
-
67
- if (templates.length === 0) {
68
- logger.warn('No templates found matching criteria');
69
- return;
70
- }
71
-
72
- // Group by location if not filtered
73
- if (!options.category && !options.phase) {
74
- const frameworkTemplates = templates.filter(t => t.location === 'framework');
75
- const stackTemplates = templates.filter(t => t.location === 'stack');
76
-
77
- if (frameworkTemplates.length > 0) {
78
- logger.info(chalk.bold.cyan(`\nšŸ“¦ Universal Templates (${frameworkTemplates.length})`));
79
- frameworkTemplates.forEach(t => {
80
- const formatted = formatTemplate(t);
81
- console.log(` ${formatted.id.padEnd(30)} ${formatted.name.padEnd(40)} ${formatted.badges}`);
82
- });
83
- }
84
-
85
- } else {
86
- // Flat list
87
- templates.forEach(t => {
88
- const formatted = formatTemplate(t);
89
- console.log(` ${formatted.id.padEnd(30)} ${formatted.name.padEnd(40)} ${formatted.location.padEnd(15)} ${formatted.badges}`);
90
- });
91
- }
92
-
93
- // Show statistics
94
- if (!options.category && !options.phase && !options.stack && !options.required) {
95
- logger.blank();
96
- const stats = getRegistryStats();
97
- logger.info(chalk.bold('\nšŸ“Š Statistics'));
98
- console.log(` Total Templates: ${chalk.green(stats.total)}`);
99
- console.log(` Framework: ${chalk.cyan(stats.framework)}`);
100
- console.log(` Required: ${chalk.green(stats.required)}`);
101
- console.log(` Optional: ${chalk.dim(stats.optional)}`);
102
- }
103
-
104
- logger.blank();
105
-
106
- // Show usage hint
107
- logger.dim('šŸ’” Use `morph-spec template show <id>` to see template details');
108
- logger.blank();
109
-
110
- } catch (error) {
111
- logger.error(`Failed to list templates: ${error.message}`);
112
- process.exit(1);
113
- }
114
- }