@polymorphism-tech/morph-spec 4.8.19 → 4.9.0

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 (137) hide show
  1. package/CLAUDE.md +21 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +15 -56
  4. package/bin/task-manager.js +115 -14
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +21 -0
  10. package/framework/agents.json +698 -176
  11. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  12. package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
  13. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
  14. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
  15. package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
  16. package/framework/hooks/claude-code/statusline.py +76 -30
  17. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  18. package/framework/hooks/shared/activity-logger.js +0 -24
  19. package/framework/hooks/shared/phase-utils.js +3 -0
  20. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  21. package/framework/hooks/shared/stale-task-reset.js +57 -0
  22. package/framework/hooks/shared/state-reader.js +2 -2
  23. package/framework/hooks/shared/worktree-helpers.js +53 -0
  24. package/framework/phases.json +40 -8
  25. package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
  26. package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
  27. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
  28. package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
  29. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  30. package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
  31. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
  32. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  33. package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
  34. package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
  35. package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
  36. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
  37. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
  38. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
  39. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
  40. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
  41. package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
  42. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
  43. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
  44. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
  45. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
  46. package/framework/standards/STANDARDS.json +640 -88
  47. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  48. package/framework/templates/REGISTRY.json +1825 -1909
  49. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  50. package/framework/templates/docs/onboarding.md +1 -5
  51. package/package.json +2 -6
  52. package/src/commands/agents/dispatch-agents.js +55 -4
  53. package/src/commands/project/doctor.js +16 -47
  54. package/src/commands/project/init.js +1 -1
  55. package/src/commands/project/status.js +2 -2
  56. package/src/commands/project/update.js +381 -365
  57. package/src/commands/project/worktree.js +154 -0
  58. package/src/commands/state/advance-phase.js +120 -30
  59. package/src/commands/state/approve.js +2 -2
  60. package/src/commands/state/index.js +7 -8
  61. package/src/commands/state/phase-runner.js +1 -1
  62. package/src/commands/state/state.js +61 -6
  63. package/src/commands/tasks/task.js +78 -99
  64. package/src/commands/templates/template-render.js +93 -173
  65. package/src/commands/trust/trust.js +26 -21
  66. package/src/core/paths/output-schema.js +15 -0
  67. package/src/core/state/state-manager.js +28 -54
  68. package/src/core/workflows/workflow-detector.js +9 -87
  69. package/src/lib/phase-chain/phase-validator.js +330 -0
  70. package/src/lib/stack/stack-profile.js +88 -0
  71. package/src/lib/tasks/task-classifier.js +16 -0
  72. package/src/lib/tasks/test-runner.js +77 -0
  73. package/src/lib/trust/trust-manager.js +32 -144
  74. package/src/lib/validators/spec-validator.js +58 -4
  75. package/src/lib/validators/validation-runner.js +23 -11
  76. package/src/scripts/setup-infra.js +240 -224
  77. package/src/utils/agents-installer.js +2 -2
  78. package/src/utils/banner.js +1 -1
  79. package/src/utils/claude-settings-manager.js +1 -1
  80. package/src/utils/file-copier.js +1 -0
  81. package/src/utils/hooks-installer.js +258 -8
  82. package/framework/hooks/dev/check-sync-health.js +0 -117
  83. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  84. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  85. package/framework/hooks/dev/sync-template-registry.js +0 -60
  86. package/framework/hooks/dev/validate-skill-format.js +0 -70
  87. package/framework/hooks/dev/validate-standard-format.js +0 -73
  88. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  89. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  90. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  91. package/framework/workflows/configs/design-impl.json +0 -49
  92. package/framework/workflows/configs/express.json +0 -45
  93. package/framework/workflows/configs/fast-track.json +0 -42
  94. package/framework/workflows/configs/full-morph.json +0 -79
  95. package/framework/workflows/configs/fusion.json +0 -39
  96. package/framework/workflows/configs/long-running.json +0 -33
  97. package/framework/workflows/configs/spec-only.json +0 -43
  98. package/framework/workflows/configs/ui-refresh.json +0 -49
  99. package/framework/workflows/configs/zero-touch.json +0 -82
  100. package/src/commands/project/monitor.js +0 -295
  101. package/src/commands/project/tutorial.js +0 -115
  102. package/src/commands/state/validate-phase.js +0 -238
  103. package/src/commands/templates/generate-contracts.js +0 -445
  104. package/src/core/orchestrator.js +0 -171
  105. package/src/core/registry/command-registry.js +0 -28
  106. package/src/core/registry/index.js +0 -8
  107. package/src/core/registry/validator-registry.js +0 -204
  108. package/src/core/templates/template-validator.js +0 -296
  109. package/src/generator/config-generator.js +0 -206
  110. package/src/generator/templates/config.json.template +0 -40
  111. package/src/generator/templates/project.md.template +0 -67
  112. package/src/lib/agents/micro-agent-factory.js +0 -161
  113. package/src/lib/analysis/complexity-analyzer.js +0 -441
  114. package/src/lib/analysis/index.js +0 -7
  115. package/src/lib/analytics/analytics-engine.js +0 -345
  116. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  117. package/src/lib/checkpoints/index.js +0 -7
  118. package/src/lib/context/context-bundler.js +0 -241
  119. package/src/lib/context/context-optimizer.js +0 -212
  120. package/src/lib/context/context-tracker.js +0 -273
  121. package/src/lib/context/core-four-tracker.js +0 -201
  122. package/src/lib/context/mcp-optimizer.js +0 -200
  123. package/src/lib/execution/fusion-executor.js +0 -304
  124. package/src/lib/execution/parallel-executor.js +0 -270
  125. package/src/lib/hooks/stop-hook-executor.js +0 -286
  126. package/src/lib/hops/hop-composer.js +0 -221
  127. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  128. package/src/lib/threads/thread-coordinator.js +0 -238
  129. package/src/lib/threads/thread-manager.js +0 -317
  130. package/src/lib/tracking/artifact-trail.js +0 -202
  131. package/src/scanner/project-scanner.js +0 -242
  132. package/src/ui/diff-display.js +0 -91
  133. package/src/ui/interactive-wizard.js +0 -96
  134. package/src/ui/user-review.js +0 -211
  135. package/src/ui/wizard-questions.js +0 -188
  136. package/src/utils/color-utils.js +0 -70
  137. package/src/utils/process-handler.js +0 -97
@@ -1,273 +0,0 @@
1
- /**
2
- * Context Tracker — Real-time token monitoring
3
- *
4
- * Tracks token usage per thread/session, detects peak usage moments,
5
- * calculates averages, and identifies optimization opportunities.
6
- * Integrates with analytics-engine to record context events.
7
- */
8
-
9
- import { recordContextEvent } from '../analytics/analytics-engine.js';
10
- import { addThreadEvent } from '../threads/thread-manager.js';
11
-
12
- // ============================================================================
13
- // Constants
14
- // ============================================================================
15
-
16
- const WARNING_THRESHOLD = 0.75; // 75% of context window
17
- const CRITICAL_THRESHOLD = 0.90; // 90% of context window
18
-
19
- export const OPTIMIZATION_STRATEGIES = {
20
- MCP_HYGIENE: 'mcp_hygiene',
21
- MICRO_AGENT: 'micro_agent_switch',
22
- CONTEXT_BUNDLE: 'context_bundle',
23
- PRIMING_FILE: 'priming_file',
24
- STANDARDS_SUBSET: 'standards_subset'
25
- };
26
-
27
- // In-memory session state (not persisted — resets each session)
28
- const sessions = new Map();
29
-
30
- // ============================================================================
31
- // Session Management
32
- // ============================================================================
33
-
34
- /**
35
- * Start tracking a session
36
- * @param {Object} opts
37
- * @param {string} opts.feature - Feature name
38
- * @param {string} [opts.threadId] - Thread ID
39
- * @param {number} [opts.contextWindow=200000] - Model context window size
40
- * @param {string} [opts.model] - Model name
41
- * @returns {string} Session ID
42
- */
43
- export function startSession({ feature, threadId = null, contextWindow = 200000, model = 'claude-sonnet-4-6' }) {
44
- const sessionId = threadId || `session-${Date.now()}`;
45
-
46
- sessions.set(sessionId, {
47
- sessionId,
48
- feature,
49
- threadId,
50
- model,
51
- contextWindow,
52
- startedAt: new Date().toISOString(),
53
- peakUsage: 0,
54
- currentUsage: 0,
55
- samples: [],
56
- events: []
57
- });
58
-
59
- recordContextEvent({
60
- type: 'context_session_started',
61
- feature,
62
- threadId,
63
- data: { model, contextWindow }
64
- });
65
-
66
- return sessionId;
67
- }
68
-
69
- /**
70
- * Record a token usage sample for a session
71
- * @param {string} sessionId - Session ID
72
- * @param {Object} usage
73
- * @param {number} usage.tokensUsed - Total tokens in context
74
- * @param {Object} [usage.breakdown] - Token breakdown by source
75
- * @returns {Object} Current session state with any optimization recommendations
76
- */
77
- export function recordTokenUsage(sessionId, { tokensUsed, breakdown = {} }) {
78
- const session = sessions.get(sessionId);
79
- if (!session) {
80
- throw new Error(`Session not found: ${sessionId}`);
81
- }
82
-
83
- const utilization = tokensUsed / session.contextWindow;
84
- session.currentUsage = tokensUsed;
85
-
86
- if (tokensUsed > session.peakUsage) {
87
- session.peakUsage = tokensUsed;
88
- }
89
-
90
- const sample = {
91
- timestamp: new Date().toISOString(),
92
- tokensUsed,
93
- utilization: Math.round(utilization * 100) / 100,
94
- breakdown
95
- };
96
- session.samples.push(sample);
97
-
98
- // Record to analytics
99
- recordContextEvent({
100
- type: 'token_usage',
101
- feature: session.feature,
102
- threadId: session.threadId,
103
- data: { tokensUsed, utilization, breakdown }
104
- });
105
-
106
- // Check thresholds and identify opportunities
107
- const recommendations = identifyOptimizations(session, utilization);
108
-
109
- if (utilization >= CRITICAL_THRESHOLD) {
110
- const event = { type: 'context_critical', utilization, recommendations };
111
- session.events.push(event);
112
-
113
- if (session.threadId) {
114
- try {
115
- addThreadEvent(session.threadId, 'context_critical', { utilization });
116
- } catch {
117
- // Thread may not exist in state yet
118
- }
119
- }
120
-
121
- recordContextEvent({
122
- type: 'context_critical',
123
- feature: session.feature,
124
- threadId: session.threadId,
125
- data: { utilization, tokensUsed, recommendations }
126
- });
127
- } else if (utilization >= WARNING_THRESHOLD) {
128
- recordContextEvent({
129
- type: 'context_warning',
130
- feature: session.feature,
131
- threadId: session.threadId,
132
- data: { utilization, tokensUsed }
133
- });
134
- }
135
-
136
- return { session, recommendations };
137
- }
138
-
139
- /**
140
- * End a session and record summary
141
- * @param {string} sessionId - Session ID
142
- * @returns {Object} Session summary
143
- */
144
- export function endSession(sessionId) {
145
- const session = sessions.get(sessionId);
146
- if (!session) return null;
147
-
148
- const avgUsage = session.samples.length > 0
149
- ? Math.round(session.samples.reduce((sum, s) => sum + s.tokensUsed, 0) / session.samples.length)
150
- : 0;
151
-
152
- const summary = {
153
- sessionId,
154
- feature: session.feature,
155
- model: session.model,
156
- startedAt: session.startedAt,
157
- endedAt: new Date().toISOString(),
158
- peakUsage: session.peakUsage,
159
- avgUsage,
160
- samples: session.samples.length,
161
- criticalMoments: session.events.filter(e => e.type === 'context_critical').length
162
- };
163
-
164
- recordContextEvent({
165
- type: 'context_session_ended',
166
- feature: session.feature,
167
- threadId: session.threadId,
168
- data: summary
169
- });
170
-
171
- sessions.delete(sessionId);
172
- return summary;
173
- }
174
-
175
- // ============================================================================
176
- // Optimization Analysis
177
- // ============================================================================
178
-
179
- /**
180
- * Identify context optimization opportunities based on current utilization
181
- * @param {Object} session - Session object
182
- * @param {number} utilization - Current utilization (0-1)
183
- * @returns {Array} Array of optimization recommendations
184
- */
185
- export function identifyOptimizations(session, utilization) {
186
- const recommendations = [];
187
-
188
- if (utilization >= 0.5) {
189
- recommendations.push({
190
- strategy: OPTIMIZATION_STRATEGIES.MCP_HYGIENE,
191
- description: 'Disable unused MCP servers to free ~15-30K tokens',
192
- estimatedSavings: '15,000-30,000 tokens',
193
- priority: utilization >= 0.75 ? 'high' : 'medium'
194
- });
195
- }
196
-
197
- if (utilization >= 0.65) {
198
- recommendations.push({
199
- strategy: OPTIMIZATION_STRATEGIES.STANDARDS_SUBSET,
200
- description: 'Load only task-relevant standards instead of all standards',
201
- estimatedSavings: '10,000-20,000 tokens',
202
- priority: 'medium'
203
- });
204
- }
205
-
206
- if (utilization >= 0.80) {
207
- recommendations.push({
208
- strategy: OPTIMIZATION_STRATEGIES.MICRO_AGENT,
209
- description: 'Switch to a micro-agent with minimal context for focused tasks',
210
- estimatedSavings: '50,000-100,000 tokens',
211
- priority: 'high'
212
- });
213
- recommendations.push({
214
- strategy: OPTIMIZATION_STRATEGIES.PRIMING_FILE,
215
- description: 'Use a 500-token priming file instead of full CLAUDE.md',
216
- estimatedSavings: '22,000 tokens',
217
- priority: 'high'
218
- });
219
- }
220
-
221
- if (utilization >= 0.90) {
222
- recommendations.push({
223
- strategy: OPTIMIZATION_STRATEGIES.CONTEXT_BUNDLE,
224
- description: 'Create a context bundle checkpoint (180K → 15K token compression)',
225
- estimatedSavings: '165,000 tokens',
226
- priority: 'critical'
227
- });
228
- }
229
-
230
- return recommendations;
231
- }
232
-
233
- /**
234
- * Get statistics for a session
235
- * @param {string} sessionId - Session ID
236
- * @returns {Object|null} Session statistics
237
- */
238
- export function getSessionStats(sessionId) {
239
- const session = sessions.get(sessionId);
240
- if (!session) return null;
241
-
242
- const avgUsage = session.samples.length > 0
243
- ? Math.round(session.samples.reduce((sum, s) => sum + s.tokensUsed, 0) / session.samples.length)
244
- : 0;
245
-
246
- return {
247
- sessionId,
248
- feature: session.feature,
249
- model: session.model,
250
- contextWindow: session.contextWindow,
251
- currentUsage: session.currentUsage,
252
- peakUsage: session.peakUsage,
253
- avgUsage,
254
- peakUtilization: Math.round(session.peakUsage / session.contextWindow * 100),
255
- currentUtilization: Math.round(session.currentUsage / session.contextWindow * 100),
256
- samples: session.samples.length
257
- };
258
- }
259
-
260
- /**
261
- * Get all active sessions
262
- * @returns {Array} Active session summaries
263
- */
264
- export function getActiveSessions() {
265
- return Array.from(sessions.values()).map(s => ({
266
- sessionId: s.sessionId,
267
- feature: s.feature,
268
- model: s.model,
269
- currentUsage: s.currentUsage,
270
- peakUsage: s.peakUsage,
271
- utilization: Math.round(s.currentUsage / s.contextWindow * 100)
272
- }));
273
- }
@@ -1,201 +0,0 @@
1
- /**
2
- * Core Four Tracker — Context/Model/Prompt/Tools tracking
3
- *
4
- * Tracks the "Core Four" metrics that determine LLM behavior:
5
- * Context — Token count breakdown by source
6
- * Model — Model name and context window
7
- * Prompt — Current request summary
8
- * Tools — Tool usage counts (Read/Write/Edit/Bash/etc.)
9
- *
10
- * Reads from analytics-engine for historical data.
11
- * Formats output for ASCII terminal display.
12
- */
13
-
14
- import { readFileSync, existsSync } from 'fs';
15
- import { join } from 'path';
16
- import { recordContextEvent } from '../analytics/analytics-engine.js';
17
-
18
- // In-memory state for current session
19
- let currentSession = {
20
- model: null,
21
- contextWindow: 200000,
22
- contextBreakdown: {},
23
- promptSummary: '',
24
- toolCalls: {}
25
- };
26
-
27
- // ============================================================================
28
- // Session Initialization
29
- // ============================================================================
30
-
31
- /**
32
- * Initialize Core Four tracking for a session
33
- * @param {Object} opts
34
- * @param {string} opts.model - Model name
35
- * @param {number} [opts.contextWindow] - Context window size
36
- * @param {string} [opts.feature] - Feature being worked on
37
- */
38
- export function initCoreFour({ model, contextWindow = 200000, feature = '' }) {
39
- currentSession = {
40
- model,
41
- contextWindow,
42
- feature,
43
- contextBreakdown: {
44
- 'CLAUDE.md': 0,
45
- standards: 0,
46
- templates: 0,
47
- spec: 0,
48
- conversation: 0,
49
- other: 0
50
- },
51
- promptSummary: '',
52
- toolCalls: {
53
- Read: 0,
54
- Write: 0,
55
- Edit: 0,
56
- Bash: 0,
57
- Glob: 0,
58
- Grep: 0,
59
- Task: 0,
60
- other: 0
61
- },
62
- startedAt: new Date().toISOString()
63
- };
64
- }
65
-
66
- // ============================================================================
67
- // Tracking Updates
68
- // ============================================================================
69
-
70
- /**
71
- * Update context breakdown by source
72
- * @param {Object} breakdown - { sourceName: tokenCount }
73
- */
74
- export function updateContextBreakdown(breakdown) {
75
- for (const [source, tokens] of Object.entries(breakdown)) {
76
- currentSession.contextBreakdown[source] = tokens;
77
- }
78
- }
79
-
80
- /**
81
- * Update current prompt summary
82
- * @param {string} summary - Brief summary of current request
83
- */
84
- export function updatePromptSummary(summary) {
85
- currentSession.promptSummary = summary;
86
- }
87
-
88
- /**
89
- * Record a tool call
90
- * @param {string} toolName - Tool name (Read|Write|Edit|Bash|etc.)
91
- */
92
- export function recordToolCall(toolName) {
93
- const key = currentSession.toolCalls.hasOwnProperty(toolName) ? toolName : 'other';
94
- currentSession.toolCalls[key] = (currentSession.toolCalls[key] || 0) + 1;
95
-
96
- if (currentSession.feature) {
97
- recordContextEvent({
98
- type: 'tool_call',
99
- feature: currentSession.feature,
100
- data: { tool: toolName }
101
- });
102
- }
103
- }
104
-
105
- // ============================================================================
106
- // Display Formatting
107
- // ============================================================================
108
-
109
- /**
110
- * Get the current Core Four state
111
- * @returns {Object} Core Four metrics
112
- */
113
- export function getCoreFour() {
114
- const totalContextTokens = Object.values(currentSession.contextBreakdown)
115
- .reduce((sum, v) => sum + v, 0);
116
-
117
- const contextUtilization = currentSession.contextWindow > 0
118
- ? Math.round(totalContextTokens / currentSession.contextWindow * 100)
119
- : 0;
120
-
121
- return {
122
- context: {
123
- total: totalContextTokens,
124
- window: currentSession.contextWindow,
125
- utilization: contextUtilization,
126
- breakdown: currentSession.contextBreakdown
127
- },
128
- model: {
129
- name: currentSession.model || 'unknown',
130
- contextWindow: currentSession.contextWindow,
131
- provider: 'Anthropic'
132
- },
133
- prompt: {
134
- summary: currentSession.promptSummary || '(no active prompt)',
135
- length: currentSession.promptSummary?.length || 0
136
- },
137
- tools: {
138
- calls: { ...currentSession.toolCalls },
139
- total: Object.values(currentSession.toolCalls).reduce((sum, v) => sum + v, 0)
140
- }
141
- };
142
- }
143
-
144
- /**
145
- * Format Core Four as ASCII display string
146
- * @returns {string} ASCII formatted Core Four display
147
- */
148
- export function formatCoreFour() {
149
- const { context, model, prompt, tools } = getCoreFour();
150
- const lines = [];
151
-
152
- lines.push('╔══════════════════════════════════════════╗');
153
- lines.push('║ CORE FOUR DASHBOARD ║');
154
- lines.push('╠══════════════════════════════════════════╣');
155
-
156
- // Context
157
- lines.push('║ CONTEXT ║');
158
- const bar = buildProgressBar(context.utilization, 30);
159
- lines.push(`║ ${bar} ${String(context.utilization).padStart(3)}% ║`);
160
- lines.push(`║ Total: ${String(context.total.toLocaleString()).padEnd(10)} Window: ${String(context.window.toLocaleString()).padEnd(8)}║`);
161
-
162
- // Breakdown
163
- for (const [src, tokens] of Object.entries(context.breakdown)) {
164
- if (tokens > 0) {
165
- const pct = Math.round(tokens / Math.max(context.total, 1) * 100);
166
- lines.push(`║ ${src.padEnd(14)} ${String(tokens.toLocaleString()).padStart(8)} (${String(pct).padStart(2)}%) ║`);
167
- }
168
- }
169
- lines.push('║ ║');
170
-
171
- // Model
172
- lines.push('║ MODEL ║');
173
- lines.push(`║ ${model.name.padEnd(40)}║`);
174
- lines.push(`║ Window: ${String(model.contextWindow.toLocaleString()).padEnd(32)}║`);
175
- lines.push('║ ║');
176
-
177
- // Prompt
178
- lines.push('║ PROMPT ║');
179
- const pStr = (prompt.summary || '—').substring(0, 38);
180
- lines.push(`║ ${pStr.padEnd(40)}║`);
181
- lines.push('║ ║');
182
-
183
- // Tools
184
- lines.push('║ TOOLS ║');
185
- lines.push(`║ Total calls: ${String(tools.total).padEnd(27)}║`);
186
- for (const [tool, count] of Object.entries(tools.calls)) {
187
- if (count > 0) {
188
- lines.push(`║ ${tool.padEnd(10)} ${String(count).padStart(4)} calls${' '.repeat(20)}║`);
189
- }
190
- }
191
- lines.push('╚══════════════════════════════════════════╝');
192
-
193
- return lines.join('\n');
194
- }
195
-
196
- function buildProgressBar(pct, width) {
197
- const filled = Math.round(pct / 100 * width);
198
- const empty = width - filled;
199
- const color = pct >= 90 ? '█' : pct >= 75 ? '▓' : '░';
200
- return '[' + color.repeat(filled) + '░'.repeat(empty) + ']';
201
- }
@@ -1,200 +0,0 @@
1
- /**
2
- * MCP Optimizer — Analyze and optimize MCP server configurations
3
- *
4
- * Reads .claude/settings.json (or .claude/settings.local.json),
5
- * detects unused MCP servers based on analytics,
6
- * estimates token savings per server, and generates recommendations.
7
- */
8
-
9
- import { readFileSync, existsSync } from 'fs';
10
- import { join } from 'path';
11
-
12
- const SETTINGS_PATHS = [
13
- join(process.cwd(), '.claude/settings.local.json'),
14
- join(process.cwd(), '.claude/settings.json'),
15
- join(process.env.USERPROFILE || process.env.HOME || '', '.claude/settings.json')
16
- ];
17
-
18
- // Estimated token overhead per MCP server in context window
19
- const MCP_TOKEN_OVERHEAD = {
20
- default: 3000, // average per server
21
- heavy: 8000, // known heavy servers (filesystem, database)
22
- light: 1500 // known light servers (simple tools)
23
- };
24
-
25
- const HEAVY_SERVERS = ['filesystem', 'postgres', 'sqlite', 'mysql', 'mongodb'];
26
- const LIGHT_SERVERS = ['time', 'echo', 'fetch'];
27
-
28
- // ============================================================================
29
- // Settings Loading
30
- // ============================================================================
31
-
32
- /**
33
- * Load Claude settings file
34
- * @returns {Object|null} Settings object or null
35
- */
36
- export function loadClaudeSettings() {
37
- for (const path of SETTINGS_PATHS) {
38
- if (existsSync(path)) {
39
- try {
40
- return { settings: JSON.parse(readFileSync(path, 'utf8')), path };
41
- } catch {
42
- continue;
43
- }
44
- }
45
- }
46
- return null;
47
- }
48
-
49
- /**
50
- * Extract MCP server list from settings
51
- * @param {Object} settings - Claude settings object
52
- * @returns {Array} Array of { name, config } objects
53
- */
54
- export function extractMCPServers(settings) {
55
- const servers = [];
56
-
57
- // Claude settings format: mcpServers: { name: { command, args, env } }
58
- if (settings.mcpServers) {
59
- for (const [name, config] of Object.entries(settings.mcpServers)) {
60
- servers.push({ name, config });
61
- }
62
- }
63
-
64
- return servers;
65
- }
66
-
67
- // ============================================================================
68
- // Usage Analysis
69
- // ============================================================================
70
-
71
- /**
72
- * Detect unused MCP servers by checking analytics logs
73
- * @param {Array} servers - Array of MCP server descriptors
74
- * @param {number} [lookbackDays=30] - Days to look back in analytics
75
- * @returns {Array} servers with usage stats added
76
- */
77
- export function detectUnusedServers(servers, lookbackDays = 30) {
78
- // Read analytics for MCP tool usage patterns
79
- const analyticsPath = join(process.cwd(), '.morph/analytics/threads-log.jsonl');
80
- const usedServerNames = new Set();
81
-
82
- if (existsSync(analyticsPath)) {
83
- try {
84
- const cutoff = new Date();
85
- cutoff.setDate(cutoff.getDate() - lookbackDays);
86
-
87
- const lines = readFileSync(analyticsPath, 'utf8').trim().split('\n').filter(Boolean);
88
- for (const line of lines) {
89
- try {
90
- const event = JSON.parse(line);
91
- if (event.timestamp && new Date(event.timestamp) > cutoff) {
92
- // Check for MCP tool references in event data
93
- const eventStr = JSON.stringify(event);
94
- for (const server of servers) {
95
- if (eventStr.includes(server.name)) {
96
- usedServerNames.add(server.name);
97
- }
98
- }
99
- }
100
- } catch {
101
- // Skip malformed lines
102
- }
103
- }
104
- } catch {
105
- // Analytics not available — mark all as unknown usage
106
- }
107
- }
108
-
109
- return servers.map(server => {
110
- const isHeavy = HEAVY_SERVERS.some(h => server.name.toLowerCase().includes(h));
111
- const isLight = LIGHT_SERVERS.some(l => server.name.toLowerCase().includes(l));
112
- const overhead = isHeavy ? MCP_TOKEN_OVERHEAD.heavy : isLight ? MCP_TOKEN_OVERHEAD.light : MCP_TOKEN_OVERHEAD.default;
113
-
114
- const usageSeen = usedServerNames.has(server.name);
115
-
116
- return {
117
- ...server,
118
- overhead,
119
- usageSeen,
120
- usageStatus: usedServerNames.size === 0 ? 'unknown' : (usageSeen ? 'used' : 'unused')
121
- };
122
- });
123
- }
124
-
125
- // ============================================================================
126
- // Recommendations
127
- // ============================================================================
128
-
129
- /**
130
- * Analyze MCP configuration and generate optimization recommendations
131
- * @returns {Object} Analysis results with recommendations
132
- */
133
- export function analyzeMCP() {
134
- const result = loadClaudeSettings();
135
-
136
- if (!result) {
137
- return {
138
- found: false,
139
- message: 'No Claude settings file found. Checked: .claude/settings.json and .claude/settings.local.json',
140
- servers: [],
141
- recommendations: []
142
- };
143
- }
144
-
145
- const { settings, path: settingsPath } = result;
146
- const servers = extractMCPServers(settings);
147
-
148
- if (servers.length === 0) {
149
- return {
150
- found: true,
151
- settingsPath,
152
- servers: [],
153
- totalOverhead: 0,
154
- recommendations: [],
155
- message: 'No MCP servers configured.'
156
- };
157
- }
158
-
159
- const analyzedServers = detectUnusedServers(servers);
160
- const totalOverhead = analyzedServers.reduce((sum, s) => sum + s.overhead, 0);
161
-
162
- const unusedServers = analyzedServers.filter(s => s.usageStatus === 'unused');
163
- const unusedOverhead = unusedServers.reduce((sum, s) => sum + s.overhead, 0);
164
-
165
- const recommendations = [];
166
-
167
- if (unusedServers.length > 0) {
168
- recommendations.push({
169
- type: 'disable_unused',
170
- title: `Disable ${unusedServers.length} unused MCP server(s)`,
171
- description: `${unusedServers.map(s => s.name).join(', ')} have not been used recently.`,
172
- estimatedSavings: unusedOverhead,
173
- servers: unusedServers.map(s => s.name),
174
- action: 'Remove from mcpServers in .claude/settings.json'
175
- });
176
- }
177
-
178
- const heavyServers = analyzedServers.filter(s => s.overhead >= MCP_TOKEN_OVERHEAD.heavy && s.usageStatus !== 'unused');
179
- if (heavyServers.length > 0) {
180
- recommendations.push({
181
- type: 'defer_heavy',
182
- title: `Consider deferring ${heavyServers.length} heavy MCP server(s)`,
183
- description: `${heavyServers.map(s => s.name).join(', ')} use significant context window space.`,
184
- estimatedSavings: heavyServers.reduce((sum, s) => sum + s.overhead, 0) / 2,
185
- servers: heavyServers.map(s => s.name),
186
- action: 'Only enable when needed for that specific task'
187
- });
188
- }
189
-
190
- return {
191
- found: true,
192
- settingsPath,
193
- servers: analyzedServers,
194
- totalServers: servers.length,
195
- totalOverhead,
196
- unusedCount: unusedServers.length,
197
- potentialSavings: unusedOverhead,
198
- recommendations: recommendations.sort((a, b) => b.estimatedSavings - a.estimatedSavings)
199
- };
200
- }