@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.
- package/.morph/analytics/threads-log.jsonl +54 -0
- package/.morph/state.json +198 -0
- package/LICENSE +1 -2
- package/README.md +379 -414
- package/bin/morph-spec.js +57 -403
- package/bin/validate.js +2 -26
- package/claude-plugin.json +2 -2
- package/docs/ARCHITECTURE.md +43 -46
- package/docs/CHEATSHEET.md +203 -221
- package/docs/COMMAND-FLOWS.md +319 -289
- package/docs/QUICKSTART.md +2 -8
- package/docs/plans/2026-02-22-claude-docs-morph-alignment-analysis.md +2 -0
- package/docs/plans/2026-02-22-claude-settings.md +2 -0
- package/docs/plans/2026-02-22-morph-cc-alignment-impl.md +2 -0
- package/docs/plans/2026-02-22-morph-spec-next.md +2 -0
- package/docs/plans/2026-02-22-native-alignment-design.md +2 -0
- package/docs/plans/2026-02-22-native-alignment-impl.md +2 -0
- package/docs/plans/2026-02-22-native-enrichment-design.md +2 -0
- package/docs/plans/2026-02-22-native-enrichment.md +2 -0
- package/docs/plans/2026-02-23-ddd-architecture-refactor.md +2 -0
- package/docs/plans/2026-02-23-ddd-nextsteps.md +2 -0
- package/docs/plans/2026-02-23-infra-architect-refactor.md +2 -0
- package/docs/plans/2026-02-23-nextjs-code-review-design.md +2 -1
- package/docs/plans/2026-02-23-nextjs-code-review-impl.md +2 -0
- package/docs/plans/2026-02-23-nextjs-standards-design.md +2 -1
- package/docs/plans/2026-02-23-nextjs-standards-impl.md +2 -0
- package/docs/plans/2026-02-24-cli-radical-simplification.md +592 -0
- package/docs/plans/2026-02-24-framework-failure-points.md +125 -0
- package/docs/plans/2026-02-24-morph-init-design.md +337 -0
- package/docs/plans/2026-02-24-morph-init-impl.md +1269 -0
- package/docs/plans/2026-02-24-tutorial-command-design.md +71 -0
- package/docs/plans/2026-02-24-tutorial-command.md +298 -0
- package/framework/CLAUDE.md +2 -2
- package/framework/commands/morph-proposal.md +3 -3
- package/framework/hooks/README.md +11 -10
- package/framework/hooks/claude-code/notification/approval-reminder.js +2 -0
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +1 -1
- package/framework/hooks/claude-code/pre-tool-use/protect-readonly-files.js +4 -55
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +20 -5
- package/framework/hooks/claude-code/statusline.py +6 -1
- package/framework/hooks/claude-code/stop/validate-completion.js +1 -1
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +1 -1
- package/framework/hooks/dev/check-sync-health.js +117 -0
- package/framework/hooks/dev/guard-version-numbers.js +57 -0
- package/framework/hooks/dev/sync-standards-registry.js +60 -0
- package/framework/hooks/dev/sync-template-registry.js +60 -0
- package/framework/hooks/dev/validate-skill-format.js +70 -0
- package/framework/hooks/dev/validate-standard-format.js +73 -0
- package/framework/hooks/shared/payload-utils.js +39 -0
- package/framework/hooks/shared/state-reader.js +25 -1
- package/framework/rules/morph-workflow.md +1 -1
- package/framework/skills/level-0-meta/morph-init/SKILL.md +216 -0
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +4 -4
- package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +192 -191
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +181 -180
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +339 -338
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +254 -253
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +168 -170
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +284 -283
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +246 -245
- package/framework/templates/examples/design-system-examples.md +1 -1
- package/framework/templates/ui/FluentDesignTheme.cs +1 -1
- package/framework/templates/ui/MudTheme.cs +1 -1
- package/framework/templates/ui/design-system.css +1 -1
- package/package.json +4 -2
- package/scripts/bump-version.js +248 -0
- package/scripts/install-dev-hooks.js +138 -0
- package/src/commands/agents/index.js +1 -2
- package/src/commands/index.js +13 -16
- package/src/commands/project/doctor.js +100 -14
- package/src/commands/project/index.js +7 -10
- package/src/commands/project/init.js +398 -555
- package/src/commands/project/install-plugin-cmd.js +28 -0
- package/src/commands/project/setup-infra-cmd.js +12 -0
- package/src/commands/project/tutorial.js +115 -0
- package/src/commands/project/update.js +22 -37
- package/src/commands/state/approve.js +213 -221
- package/src/commands/state/index.js +0 -1
- package/src/commands/state/state.js +337 -365
- package/src/commands/templates/index.js +0 -4
- package/src/commands/trust/trust.js +1 -93
- package/src/commands/utils/index.js +1 -5
- package/src/commands/validation/index.js +1 -5
- package/src/core/registry/command-registry.js +11 -285
- package/src/core/state/state-manager.js +5 -2
- package/src/lib/detectors/index.js +81 -87
- package/src/lib/detectors/structure-detector.js +275 -273
- package/src/lib/generators/recap-generator.js +232 -225
- package/src/lib/installers/mcp-installer.js +18 -3
- package/src/scripts/global-install.js +34 -0
- package/src/scripts/install-plugin.js +126 -0
- package/src/scripts/setup-infra.js +203 -0
- package/src/utils/agents-installer.js +10 -1
- package/src/utils/hooks-installer.js +70 -17
- package/CLAUDE.md +0 -77
- package/docs/claude-alignment-report.md +0 -137
- package/docs/examples/order-management/contracts.cs +0 -84
- package/docs/examples/order-management/proposal.md +0 -24
- package/docs/examples/order-management/spec.md +0 -162
- package/src/commands/feature/create-story.js +0 -362
- package/src/commands/feature/index.js +0 -6
- package/src/commands/feature/shard-spec.js +0 -225
- package/src/commands/feature/sprint-status.js +0 -250
- package/src/commands/generation/generate-onboarding.js +0 -169
- package/src/commands/generation/generate.js +0 -276
- package/src/commands/generation/index.js +0 -5
- package/src/commands/learning/capture-pattern.js +0 -121
- package/src/commands/learning/index.js +0 -5
- package/src/commands/learning/search-patterns.js +0 -126
- package/src/commands/mcp/mcp.js +0 -102
- package/src/commands/project/changes.js +0 -66
- package/src/commands/project/cost.js +0 -179
- package/src/commands/project/detect.js +0 -114
- package/src/commands/project/diff.js +0 -278
- package/src/commands/project/revert.js +0 -173
- package/src/commands/project/standards.js +0 -80
- package/src/commands/project/sync.js +0 -167
- package/src/commands/project/update-agents.js +0 -23
- package/src/commands/state/rollback-phase.js +0 -185
- package/src/commands/templates/template-customize.js +0 -87
- package/src/commands/templates/template-list.js +0 -114
- package/src/commands/templates/template-show.js +0 -129
- package/src/commands/templates/template-validate.js +0 -91
- package/src/commands/utils/troubleshoot.js +0 -222
- package/src/commands/validation/analyze-blazor-concurrency.js +0 -193
- package/src/commands/validation/lint-fluent.js +0 -352
- package/src/commands/validation/validate-blazor-state.js +0 -210
- package/src/commands/validation/validate-blazor.js +0 -156
- package/src/commands/validation/validate-css.js +0 -84
- package/src/lib/detectors/conversation-analyzer.js +0 -163
- package/src/lib/learning/index.js +0 -7
- package/src/lib/learning/learning-system.js +0 -520
- package/src/lib/troubleshooting/index.js +0 -8
- package/src/lib/troubleshooting/troubleshoot-grep.js +0 -198
- package/src/lib/troubleshooting/troubleshoot-index.js +0 -144
- package/src/llm/environment-detector.js +0 -43
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { appendFileSync, existsSync, readFileSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Capture Pattern Command - Document lessons learned
|
|
8
|
-
*
|
|
9
|
-
* Appends patterns to patterns-learned.md for future reference
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const program = new Command();
|
|
13
|
-
|
|
14
|
-
const VALID_CATEGORIES = ['success', 'avoid', 'optimization', 'security', 'convention', 'best-practice'];
|
|
15
|
-
|
|
16
|
-
program
|
|
17
|
-
.name('capture-pattern')
|
|
18
|
-
.description('Capture a pattern or lesson learned from a feature')
|
|
19
|
-
.argument('<feature>', 'Feature name')
|
|
20
|
-
.argument('<category>', `Pattern category (${VALID_CATEGORIES.join(', ')})`)
|
|
21
|
-
.argument('<description>', 'Brief description of the pattern')
|
|
22
|
-
.option('--auto', 'Auto-capture without opening editor', false)
|
|
23
|
-
.action(async (featureName, category, description, options) => {
|
|
24
|
-
try {
|
|
25
|
-
// Validate category
|
|
26
|
-
if (!VALID_CATEGORIES.includes(category)) {
|
|
27
|
-
console.error(chalk.red(`\n❌ Invalid category: ${category}`));
|
|
28
|
-
console.log(chalk.gray(`Valid categories: ${VALID_CATEGORIES.join(', ')}\n`));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Get patterns file path
|
|
33
|
-
const patternsPath = join(process.cwd(), '.morph/memory/patterns-learned.md');
|
|
34
|
-
|
|
35
|
-
if (!existsSync(patternsPath)) {
|
|
36
|
-
console.error(chalk.red(`\n❌ Patterns file not found: ${patternsPath}`));
|
|
37
|
-
console.log(chalk.yellow('Run "morph-spec init" to create the file.\n'));
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Create pattern entry
|
|
42
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
43
|
-
const categoryDisplay = category === 'avoid' ? 'Anti-Pattern to Avoid' :
|
|
44
|
-
category === 'success' ? 'Best Practice' :
|
|
45
|
-
category === 'optimization' ? 'Performance Optimization' :
|
|
46
|
-
category === 'security' ? 'Security Best Practice' :
|
|
47
|
-
category === 'convention' ? 'Convention' :
|
|
48
|
-
'Best Practice';
|
|
49
|
-
|
|
50
|
-
const patternTemplate = `
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Pattern: ${description}
|
|
55
|
-
|
|
56
|
-
**Source:** Feature \`${featureName}\` (${timestamp})
|
|
57
|
-
**Category:** ${categoryDisplay}
|
|
58
|
-
**Date:** ${timestamp}
|
|
59
|
-
|
|
60
|
-
**Problem:**
|
|
61
|
-
[Describe the problem this pattern solves]
|
|
62
|
-
|
|
63
|
-
**Implementation:**
|
|
64
|
-
\`\`\`csharp
|
|
65
|
-
// Add code example here
|
|
66
|
-
\`\`\`
|
|
67
|
-
|
|
68
|
-
**Why:**
|
|
69
|
-
- [Reason 1]
|
|
70
|
-
- [Reason 2]
|
|
71
|
-
|
|
72
|
-
**When to Use:**
|
|
73
|
-
[Describe scenarios where this pattern applies]
|
|
74
|
-
|
|
75
|
-
**Reuse:** [List features/scenarios where this can be reused]
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
`;
|
|
79
|
-
|
|
80
|
-
// Append to file
|
|
81
|
-
appendFileSync(patternsPath, patternTemplate, 'utf8');
|
|
82
|
-
|
|
83
|
-
console.log(chalk.green(`\n✅ Pattern captured successfully`));
|
|
84
|
-
console.log(chalk.cyan(` Feature: ${featureName}`));
|
|
85
|
-
console.log(chalk.cyan(` Category: ${categoryDisplay}`));
|
|
86
|
-
console.log(chalk.cyan(` Description: ${description}\n`));
|
|
87
|
-
|
|
88
|
-
if (!options.auto) {
|
|
89
|
-
console.log(chalk.bold('📝 Next Steps:'));
|
|
90
|
-
console.log(chalk.gray(`1. Edit ${patternsPath}`));
|
|
91
|
-
console.log(chalk.gray('2. Fill in: Problem, Implementation (code), Why, When to Use'));
|
|
92
|
-
console.log(chalk.gray('3. Add code examples and rationale\n'));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Show location in file
|
|
96
|
-
const content = readFileSync(patternsPath, 'utf8');
|
|
97
|
-
const lines = content.split('\n');
|
|
98
|
-
const patternLine = lines.findIndex(line => line.includes(`Pattern: ${description}`));
|
|
99
|
-
|
|
100
|
-
if (patternLine !== -1) {
|
|
101
|
-
console.log(chalk.gray(`Pattern added at line ${patternLine + 1}`));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
console.log(chalk.gray(`\nFile: ${patternsPath}\n`));
|
|
105
|
-
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error(chalk.red(`\n❌ Error capturing pattern: ${error.message}\n`));
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Only parse if run directly (not imported as module)
|
|
113
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
114
|
-
if (process.argv.length > 2) {
|
|
115
|
-
program.parse(process.argv);
|
|
116
|
-
} else {
|
|
117
|
-
program.help();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export default program;
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { readFileSync, existsSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Search Patterns Command - Find relevant patterns
|
|
8
|
-
*
|
|
9
|
-
* Searches patterns-learned.md for keywords
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const program = new Command();
|
|
13
|
-
|
|
14
|
-
program
|
|
15
|
-
.name('search-patterns')
|
|
16
|
-
.description('Search for patterns by keyword')
|
|
17
|
-
.argument('<keyword>', 'Keyword to search for')
|
|
18
|
-
.option('--category <category>', 'Filter by category')
|
|
19
|
-
.option('--limit <number>', 'Limit number of results', '10')
|
|
20
|
-
.option('--json', 'Output as JSON', false)
|
|
21
|
-
.action(async (keyword, options) => {
|
|
22
|
-
try {
|
|
23
|
-
// Get patterns file path
|
|
24
|
-
const patternsPath = join(process.cwd(), '.morph/memory/patterns-learned.md');
|
|
25
|
-
|
|
26
|
-
if (!existsSync(patternsPath)) {
|
|
27
|
-
console.error(chalk.red(`\n❌ Patterns file not found: ${patternsPath}`));
|
|
28
|
-
console.log(chalk.yellow('No patterns learned yet. Complete some features first!\n'));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Read patterns file
|
|
33
|
-
const content = readFileSync(patternsPath, 'utf8');
|
|
34
|
-
|
|
35
|
-
// Split into pattern sections
|
|
36
|
-
const sections = content.split('---').filter(s => s.trim().length > 0);
|
|
37
|
-
|
|
38
|
-
// Search patterns
|
|
39
|
-
const keywordLower = keyword.toLowerCase();
|
|
40
|
-
const matches = [];
|
|
41
|
-
|
|
42
|
-
sections.forEach(section => {
|
|
43
|
-
if (section.toLowerCase().includes(keywordLower)) {
|
|
44
|
-
// Extract pattern name
|
|
45
|
-
const nameMatch = section.match(/## Pattern: (.+)/);
|
|
46
|
-
const categoryMatch = section.match(/\*\*Category:\*\* (.+)/);
|
|
47
|
-
const sourceMatch = section.match(/\*\*Source:\*\* (.+)/);
|
|
48
|
-
|
|
49
|
-
if (nameMatch) {
|
|
50
|
-
const patternName = nameMatch[1].trim();
|
|
51
|
-
const category = categoryMatch ? categoryMatch[1].trim() : 'Unknown';
|
|
52
|
-
const source = sourceMatch ? sourceMatch[1].trim() : 'Unknown';
|
|
53
|
-
|
|
54
|
-
// Filter by category if specified
|
|
55
|
-
if (options.category && !category.toLowerCase().includes(options.category.toLowerCase())) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
matches.push({
|
|
60
|
-
name: patternName,
|
|
61
|
-
category,
|
|
62
|
-
source,
|
|
63
|
-
content: section.trim(),
|
|
64
|
-
preview: section.substring(0, 300).trim() + '...'
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// Limit results
|
|
71
|
-
const limit = parseInt(options.limit, 10);
|
|
72
|
-
const limitedMatches = matches.slice(0, limit);
|
|
73
|
-
|
|
74
|
-
if (options.json) {
|
|
75
|
-
// JSON output
|
|
76
|
-
console.log(JSON.stringify({ keyword, matches: limitedMatches, total: matches.length }, null, 2));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Pretty output
|
|
81
|
-
if (limitedMatches.length === 0) {
|
|
82
|
-
console.log(chalk.yellow(`\n⚠️ No patterns found for keyword: "${keyword}"\n`));
|
|
83
|
-
console.log(chalk.gray('Try a different keyword or broader search term.\n'));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
console.log(chalk.bold(`\n🔍 Found ${matches.length} pattern(s) for: "${keyword}"`));
|
|
88
|
-
if (matches.length > limit) {
|
|
89
|
-
console.log(chalk.gray(`Showing first ${limit} results (use --limit to see more)\n`));
|
|
90
|
-
}
|
|
91
|
-
console.log('━'.repeat(60));
|
|
92
|
-
|
|
93
|
-
limitedMatches.forEach((match, index) => {
|
|
94
|
-
console.log(chalk.green(`\n${index + 1}. ${match.name}`));
|
|
95
|
-
console.log(chalk.cyan(` Category: ${match.category}`));
|
|
96
|
-
console.log(chalk.gray(` Source: ${match.source}`));
|
|
97
|
-
|
|
98
|
-
// Show first few lines of the pattern
|
|
99
|
-
const lines = match.content.split('\n').slice(0, 15);
|
|
100
|
-
console.log(chalk.gray('\n ' + lines.join('\n ')));
|
|
101
|
-
|
|
102
|
-
if (match.content.split('\n').length > 15) {
|
|
103
|
-
console.log(chalk.gray(` ... (see full pattern in ${patternsPath})`));
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
console.log('\n' + '━'.repeat(60));
|
|
108
|
-
console.log(chalk.bold('\n💡 Usage:'));
|
|
109
|
-
console.log(chalk.gray(` Edit ${patternsPath} to see full pattern details\n`));
|
|
110
|
-
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error(chalk.red(`\n❌ Error searching patterns: ${error.message}\n`));
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Only parse if run directly (not imported as module)
|
|
118
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
119
|
-
if (process.argv.length > 2) {
|
|
120
|
-
program.parse(process.argv);
|
|
121
|
-
} else {
|
|
122
|
-
program.help();
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export default program;
|
package/src/commands/mcp/mcp.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP CLI command — MCP server analysis and optimization
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* morph-spec mcp optimize
|
|
6
|
-
* morph-spec mcp list
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import chalk from 'chalk';
|
|
10
|
-
import { analyzeMCP } from '../../lib/context/mcp-optimizer.js';
|
|
11
|
-
|
|
12
|
-
const STATUS_COLORS = {
|
|
13
|
-
used: chalk.green,
|
|
14
|
-
unused: chalk.red,
|
|
15
|
-
unknown: chalk.gray
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
function formatTokens(n) {
|
|
19
|
-
return n >= 1000 ? `${(n / 1000).toFixed(1)}K` : String(n);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function mcpOptimizeCommand(options) {
|
|
23
|
-
try {
|
|
24
|
-
const analysis = analyzeMCP();
|
|
25
|
-
|
|
26
|
-
if (!analysis.found) {
|
|
27
|
-
console.log(chalk.yellow(`\n ⚠ ${analysis.message}\n`));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
console.log(chalk.cyan('\n MCP Optimization Analysis\n'));
|
|
32
|
-
console.log(' ' + '─'.repeat(60));
|
|
33
|
-
|
|
34
|
-
console.log(`\n Settings file: ${chalk.gray(analysis.settingsPath)}`);
|
|
35
|
-
console.log(` Total servers: ${analysis.totalServers}`);
|
|
36
|
-
console.log(` Total context overhead: ${chalk.yellow(formatTokens(analysis.totalOverhead) + ' tokens')}`);
|
|
37
|
-
|
|
38
|
-
if (analysis.unusedCount > 0) {
|
|
39
|
-
console.log(` Unused servers: ${chalk.red(analysis.unusedCount)}`);
|
|
40
|
-
console.log(` Potential savings: ${chalk.green(formatTokens(analysis.potentialSavings) + ' tokens')}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (analysis.recommendations.length === 0) {
|
|
44
|
-
console.log(chalk.green('\n ✓ MCP configuration looks optimized!\n'));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log(chalk.yellow('\n Recommendations:\n'));
|
|
49
|
-
for (const rec of analysis.recommendations) {
|
|
50
|
-
console.log(` ${chalk.bold(rec.title)}`);
|
|
51
|
-
console.log(` ${rec.description}`);
|
|
52
|
-
console.log(` Savings: ${chalk.green(formatTokens(rec.estimatedSavings) + ' tokens')}`);
|
|
53
|
-
console.log(` Action: ${chalk.cyan(rec.action)}`);
|
|
54
|
-
console.log('');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
console.log(' ' + '─'.repeat(60));
|
|
58
|
-
console.log('');
|
|
59
|
-
} catch (err) {
|
|
60
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function mcpListCommand(options) {
|
|
66
|
-
try {
|
|
67
|
-
const analysis = analyzeMCP();
|
|
68
|
-
|
|
69
|
-
if (!analysis.found) {
|
|
70
|
-
console.log(chalk.yellow(`\n ⚠ ${analysis.message}\n`));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (analysis.servers.length === 0) {
|
|
75
|
-
console.log(chalk.gray('\n No MCP servers configured.\n'));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
console.log(chalk.cyan('\n Configured MCP Servers\n'));
|
|
80
|
-
console.log(' ' + 'Name'.padEnd(25) + ' ' + 'Status'.padEnd(10) + ' ' + 'Overhead');
|
|
81
|
-
console.log(' ' + '─'.repeat(50));
|
|
82
|
-
|
|
83
|
-
for (const server of analysis.servers) {
|
|
84
|
-
const colorFn = STATUS_COLORS[server.usageStatus] || chalk.white;
|
|
85
|
-
console.log(
|
|
86
|
-
' ' +
|
|
87
|
-
server.name.padEnd(25) + ' ' +
|
|
88
|
-
colorFn(server.usageStatus.padEnd(10)) + ' ' +
|
|
89
|
-
formatTokens(server.overhead) + ' tokens'
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log('\n ' + `Total overhead: ${formatTokens(analysis.totalOverhead)} tokens`);
|
|
94
|
-
if (analysis.recommendations.length > 0) {
|
|
95
|
-
console.log(chalk.yellow(` Run 'morph-spec mcp optimize' for recommendations`));
|
|
96
|
-
}
|
|
97
|
-
console.log('');
|
|
98
|
-
} catch (err) {
|
|
99
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Changes Command
|
|
3
|
-
*
|
|
4
|
-
* Lists files created/modified by a feature, grouped by phase.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { logger } from '../../utils/logger.js';
|
|
9
|
-
import { getFileChanges } from '../../core/state/state-manager.js';
|
|
10
|
-
|
|
11
|
-
export async function changesCommand(feature, options = {}) {
|
|
12
|
-
if (!feature) {
|
|
13
|
-
logger.error('Usage: morph-spec changes <feature>');
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const grouped = getFileChanges(feature, { groupByPhase: true });
|
|
19
|
-
|
|
20
|
-
if (options.json) {
|
|
21
|
-
console.log(JSON.stringify(grouped, null, 2));
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const phases = Object.keys(grouped);
|
|
26
|
-
|
|
27
|
-
if (phases.length === 0) {
|
|
28
|
-
logger.warn(`No file changes tracked for feature '${feature}'.`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
logger.header(`File Changes — ${feature}`);
|
|
33
|
-
|
|
34
|
-
let totalFiles = 0;
|
|
35
|
-
|
|
36
|
-
for (const phase of phases) {
|
|
37
|
-
const changes = grouped[phase];
|
|
38
|
-
totalFiles += changes.length;
|
|
39
|
-
|
|
40
|
-
console.log(chalk.cyan(`\n Phase: ${phase} (${changes.length} files)`));
|
|
41
|
-
console.log(' ' + '─'.repeat(50));
|
|
42
|
-
|
|
43
|
-
for (const change of changes) {
|
|
44
|
-
const actionIcon = {
|
|
45
|
-
created: chalk.green('+'),
|
|
46
|
-
modified: chalk.yellow('~'),
|
|
47
|
-
deleted: chalk.red('-')
|
|
48
|
-
}[change.action] || ' ';
|
|
49
|
-
|
|
50
|
-
const timestamp = change.timestamp
|
|
51
|
-
? chalk.dim(` (${new Date(change.timestamp).toLocaleDateString()})`)
|
|
52
|
-
: '';
|
|
53
|
-
|
|
54
|
-
console.log(` ${actionIcon} ${change.path}${timestamp}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
logger.blank();
|
|
59
|
-
logger.dim(`Total: ${totalFiles} file(s) across ${phases.length} phase(s)`);
|
|
60
|
-
logger.blank();
|
|
61
|
-
|
|
62
|
-
} catch (error) {
|
|
63
|
-
logger.error(error.message);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Cost Tracking Command
|
|
3
|
-
*
|
|
4
|
-
* Track and estimate costs (token usage, Azure resources) across phases.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* morph-spec cost <feature-name>
|
|
8
|
-
* morph-spec cost <feature-name> --json
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import chalk from 'chalk';
|
|
12
|
-
import { getFeature, loadState, saveState } from '../../core/state/state-manager.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Phase cost weights (relative token usage)
|
|
16
|
-
*/
|
|
17
|
-
const PHASE_COST_WEIGHTS = {
|
|
18
|
-
proposal: 5,
|
|
19
|
-
setup: 3,
|
|
20
|
-
uiux: 12,
|
|
21
|
-
design: 18,
|
|
22
|
-
clarify: 20,
|
|
23
|
-
tasks: 17,
|
|
24
|
-
implement: 80
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Cost per 1K tokens (approximate for Claude models)
|
|
29
|
-
*/
|
|
30
|
-
const COST_PER_1K_INPUT = 0.003;
|
|
31
|
-
const COST_PER_1K_OUTPUT = 0.015;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Record token usage for a phase
|
|
35
|
-
*/
|
|
36
|
-
export function recordPhaseUsage(featureName, phase, inputTokens, outputTokens) {
|
|
37
|
-
const state = loadState();
|
|
38
|
-
const feature = state.features[featureName];
|
|
39
|
-
if (!feature) return;
|
|
40
|
-
|
|
41
|
-
if (!feature.costs) {
|
|
42
|
-
feature.costs = { phases: {}, estimated: 0, approved: false };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!feature.costs.phases[phase]) {
|
|
46
|
-
feature.costs.phases[phase] = { inputTokens: 0, outputTokens: 0, usd: 0 };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
feature.costs.phases[phase].inputTokens += inputTokens;
|
|
50
|
-
feature.costs.phases[phase].outputTokens += outputTokens;
|
|
51
|
-
feature.costs.phases[phase].usd =
|
|
52
|
-
(feature.costs.phases[phase].inputTokens / 1000 * COST_PER_1K_INPUT) +
|
|
53
|
-
(feature.costs.phases[phase].outputTokens / 1000 * COST_PER_1K_OUTPUT);
|
|
54
|
-
|
|
55
|
-
saveState(state);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Calculate estimated remaining cost
|
|
60
|
-
*/
|
|
61
|
-
function estimateRemaining(feature) {
|
|
62
|
-
const currentPhaseIndex = Object.keys(PHASE_COST_WEIGHTS).indexOf(feature.phase);
|
|
63
|
-
const completedWeight = Object.entries(PHASE_COST_WEIGHTS)
|
|
64
|
-
.filter(([_, __], i) => i <= currentPhaseIndex)
|
|
65
|
-
.reduce((sum, [_, w]) => sum + w, 0);
|
|
66
|
-
|
|
67
|
-
const totalWeight = Object.values(PHASE_COST_WEIGHTS).reduce((a, b) => a + b, 0);
|
|
68
|
-
const remainingWeight = totalWeight - completedWeight;
|
|
69
|
-
|
|
70
|
-
// Estimate based on actual usage so far
|
|
71
|
-
const actualSpent = Object.values(feature.costs?.phases || {})
|
|
72
|
-
.reduce((sum, p) => sum + p.usd, 0);
|
|
73
|
-
|
|
74
|
-
if (completedWeight === 0 || actualSpent === 0) {
|
|
75
|
-
// No data yet, use default estimates
|
|
76
|
-
const estimatedTokens = remainingWeight * 1000;
|
|
77
|
-
return (estimatedTokens / 1000 * COST_PER_1K_INPUT) + (estimatedTokens / 1000 * COST_PER_1K_OUTPUT);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const costPerWeight = actualSpent / completedWeight;
|
|
81
|
-
return costPerWeight * remainingWeight;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Main cost command
|
|
86
|
-
*/
|
|
87
|
-
export async function costCommand(featureName, options = {}) {
|
|
88
|
-
if (!featureName) {
|
|
89
|
-
console.error(chalk.red('Error: Feature name required'));
|
|
90
|
-
console.error(chalk.gray('Usage: morph-spec cost <feature-name>'));
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const feature = getFeature(featureName);
|
|
95
|
-
if (!feature) {
|
|
96
|
-
console.error(chalk.red(`Error: Feature '${featureName}' not found`));
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const costs = feature.costs || { phases: {} };
|
|
101
|
-
const totalSpent = Object.values(costs.phases)
|
|
102
|
-
.reduce((sum, p) => sum + (p.usd || 0), 0);
|
|
103
|
-
const estimatedRemaining = estimateRemaining(feature);
|
|
104
|
-
const totalTokens = Object.values(costs.phases)
|
|
105
|
-
.reduce((sum, p) => sum + (p.inputTokens || 0) + (p.outputTokens || 0), 0);
|
|
106
|
-
|
|
107
|
-
// JSON output
|
|
108
|
-
if (options.json) {
|
|
109
|
-
const output = {
|
|
110
|
-
feature: featureName,
|
|
111
|
-
currentPhase: feature.phase,
|
|
112
|
-
totalTokens,
|
|
113
|
-
totalSpentUsd: Math.round(totalSpent * 100) / 100,
|
|
114
|
-
estimatedRemainingUsd: Math.round(estimatedRemaining * 100) / 100,
|
|
115
|
-
estimatedTotalUsd: Math.round((totalSpent + estimatedRemaining) * 100) / 100,
|
|
116
|
-
phases: Object.entries(costs.phases).map(([phase, data]) => ({
|
|
117
|
-
phase,
|
|
118
|
-
inputTokens: data.inputTokens,
|
|
119
|
-
outputTokens: data.outputTokens,
|
|
120
|
-
usd: Math.round(data.usd * 100) / 100
|
|
121
|
-
})),
|
|
122
|
-
infraCosts: {
|
|
123
|
-
estimated: costs.estimated || 0,
|
|
124
|
-
approved: costs.approved || false
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
console.log(JSON.stringify(output, null, 2));
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Visual output
|
|
132
|
-
console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
|
|
133
|
-
console.log(chalk.cyan('│ MORPH-SPEC COST TRACKER │'));
|
|
134
|
-
console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
|
|
135
|
-
|
|
136
|
-
console.log(chalk.white.bold(`Feature: ${featureName}`));
|
|
137
|
-
console.log(chalk.gray(`Current Phase: ${feature.phase}\n`));
|
|
138
|
-
|
|
139
|
-
console.log(chalk.white.bold('Token usage by phase:'));
|
|
140
|
-
|
|
141
|
-
const allPhases = Object.keys(PHASE_COST_WEIGHTS);
|
|
142
|
-
for (const phase of allPhases) {
|
|
143
|
-
const data = costs.phases[phase];
|
|
144
|
-
const weight = PHASE_COST_WEIGHTS[phase];
|
|
145
|
-
const phaseIndex = allPhases.indexOf(phase);
|
|
146
|
-
const currentIndex = allPhases.indexOf(feature.phase);
|
|
147
|
-
const isLast = phaseIndex === allPhases.length - 1;
|
|
148
|
-
const prefix = isLast ? '└─' : '├─';
|
|
149
|
-
|
|
150
|
-
if (data) {
|
|
151
|
-
const tokens = (data.inputTokens || 0) + (data.outputTokens || 0);
|
|
152
|
-
const usd = Math.round((data.usd || 0) * 100) / 100;
|
|
153
|
-
const tokensStr = tokens >= 1000 ? `${Math.round(tokens / 1000)}k` : `${tokens}`;
|
|
154
|
-
console.log(`${prefix} ${phase.toUpperCase()}: ${chalk.white(tokensStr)} tokens ${chalk.gray(`($${usd})`)}`);
|
|
155
|
-
} else if (phaseIndex <= currentIndex) {
|
|
156
|
-
console.log(`${prefix} ${phase.toUpperCase()}: ${chalk.gray('(no data recorded)')}`);
|
|
157
|
-
} else {
|
|
158
|
-
console.log(`${prefix} ${phase.toUpperCase()}: ${chalk.gray(`~${weight}k tokens (estimated)`)}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
console.log('');
|
|
163
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
164
|
-
|
|
165
|
-
const totalTokensStr = totalTokens >= 1000 ? `${Math.round(totalTokens / 1000)}k` : `${totalTokens}`;
|
|
166
|
-
console.log(chalk.white(`Total spent: ${totalTokensStr} tokens ($${Math.round(totalSpent * 100) / 100})`));
|
|
167
|
-
console.log(chalk.yellow(`Estimated remaining: ~$${Math.round(estimatedRemaining * 100) / 100}`));
|
|
168
|
-
console.log(chalk.white.bold(`Estimated total: ~$${Math.round((totalSpent + estimatedRemaining) * 100) / 100}`));
|
|
169
|
-
|
|
170
|
-
// Infrastructure costs
|
|
171
|
-
if (costs.estimated > 0) {
|
|
172
|
-
console.log(chalk.white(`\nInfra costs (monthly): $${costs.estimated}`));
|
|
173
|
-
console.log(costs.approved ? chalk.green(' ✓ Approved') : chalk.yellow(' ⚠ Not yet approved'));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log('');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export default costCommand;
|