@polymorphism-tech/morph-spec 4.2.0 → 4.3.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 (132) hide show
  1. package/bin/morph-spec.js +283 -8
  2. package/bin/validate.js +4 -4
  3. package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
  4. package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
  5. package/docs/next-generation/EXECUTION-FLOW.md +274 -0
  6. package/docs/next-generation/META-PROMPTS.md +235 -0
  7. package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
  8. package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
  9. package/package.json +5 -5
  10. package/src/commands/agents/agents-fuse.js +96 -0
  11. package/src/commands/agents/micro-agent.js +112 -0
  12. package/src/commands/agents/spawn-team.js +69 -4
  13. package/src/commands/agents/squad-template.js +146 -0
  14. package/src/commands/analytics/analytics.js +176 -0
  15. package/src/commands/context/context-prime.js +63 -0
  16. package/src/commands/context/core-four.js +54 -0
  17. package/src/commands/mcp/mcp.js +102 -0
  18. package/src/commands/project/detect-agents.js +1 -1
  19. package/src/commands/project/doctor.js +573 -356
  20. package/src/commands/project/init.js +1 -1
  21. package/src/commands/project/update.js +1 -1
  22. package/src/commands/state/advance-phase.js +433 -416
  23. package/src/commands/templates/template-render.js +80 -1
  24. package/src/commands/threads/thread-template.js +103 -0
  25. package/src/commands/threads/threads.js +261 -0
  26. package/src/commands/trust/trust.js +205 -0
  27. package/src/{orchestrator.js → core/orchestrator.js} +8 -8
  28. package/src/core/state/state-manager.js +18 -2
  29. package/src/core/workflows/workflow-detector.js +100 -2
  30. package/src/lib/agents/micro-agent-factory.js +161 -0
  31. package/src/lib/analytics/analytics-engine.js +345 -0
  32. package/src/lib/checkpoints/checkpoint-hooks.js +293 -258
  33. package/src/lib/context/context-bundler.js +240 -0
  34. package/src/lib/context/context-optimizer.js +212 -0
  35. package/src/lib/context/context-tracker.js +273 -0
  36. package/src/lib/context/core-four-tracker.js +201 -0
  37. package/src/lib/context/mcp-optimizer.js +200 -0
  38. package/src/lib/execution/fusion-executor.js +304 -0
  39. package/src/lib/execution/parallel-executor.js +270 -0
  40. package/src/lib/generators/context-generator.js +3 -3
  41. package/src/lib/generators/recap-generator.js +2 -2
  42. package/src/lib/hooks/hook-executor.js +169 -0
  43. package/src/lib/hooks/stop-hook-executor.js +286 -0
  44. package/src/lib/hops/hop-composer.js +221 -0
  45. package/src/lib/threads/thread-coordinator.js +238 -0
  46. package/src/lib/threads/thread-manager.js +317 -0
  47. package/src/lib/tracking/artifact-trail.js +202 -0
  48. package/src/lib/trust/trust-manager.js +269 -0
  49. package/src/lib/validators/design-system/design-system-validator.js +2 -2
  50. package/src/lib/validators/validation-runner.js +6 -6
  51. package/stacks/blazor-azure/.morph/config/agents.json +72 -3
  52. package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
  53. package/CLAUDE.md +0 -993
  54. package/docs/llm-interaction-config.md +0 -735
  55. package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
  56. package/src/commands/utils/migrate-state.js +0 -158
  57. package/src/commands/utils/upgrade.js +0 -346
  58. package/src/lib/validators/architecture-validator.js +0 -60
  59. package/src/lib/validators/content-validator.js +0 -164
  60. package/src/lib/validators/package-validator.js +0 -61
  61. package/src/lib/validators/ui-contrast-validator.js +0 -44
  62. package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
  63. package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
  64. package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
  65. package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
  66. package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
  67. package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
  68. package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
  69. package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
  70. package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
  71. package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
  72. package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
  73. package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
  74. package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
  75. package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
  76. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
  77. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
  78. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
  79. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
  80. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
  81. package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
  82. package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
  83. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
  84. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
  85. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
  86. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
  87. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
  88. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
  89. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
  90. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
  91. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
  92. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
  93. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
  94. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
  95. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
  96. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
  97. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
  98. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
  99. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
  100. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
  101. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
  102. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
  103. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
  104. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
  105. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
  106. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
  107. package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
  108. package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
  109. package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
  110. package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
  111. package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
  112. package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
  113. package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
  114. package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
  115. package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
  116. package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
  117. package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
  118. package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
  119. package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
  120. package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
  121. package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
  122. package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
  123. package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
  124. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
  125. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
  126. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
  127. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
  128. /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
  129. /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
  130. /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
  131. /package/docs/{v3.0 → next-generation}/README.md +0 -0
  132. /package/docs/{v3.0 → next-generation}/ROADMAP.md +0 -0
@@ -0,0 +1,273 @@
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
+ }
@@ -0,0 +1,201 @@
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
+ }
@@ -0,0 +1,200 @@
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
+ }