@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.
- package/bin/morph-spec.js +283 -8
- package/bin/validate.js +4 -4
- package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
- package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
- package/docs/next-generation/EXECUTION-FLOW.md +274 -0
- package/docs/next-generation/META-PROMPTS.md +235 -0
- package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
- package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
- package/package.json +5 -5
- package/src/commands/agents/agents-fuse.js +96 -0
- package/src/commands/agents/micro-agent.js +112 -0
- package/src/commands/agents/spawn-team.js +69 -4
- package/src/commands/agents/squad-template.js +146 -0
- package/src/commands/analytics/analytics.js +176 -0
- package/src/commands/context/context-prime.js +63 -0
- package/src/commands/context/core-four.js +54 -0
- package/src/commands/mcp/mcp.js +102 -0
- package/src/commands/project/detect-agents.js +1 -1
- package/src/commands/project/doctor.js +573 -356
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/update.js +1 -1
- package/src/commands/state/advance-phase.js +433 -416
- package/src/commands/templates/template-render.js +80 -1
- package/src/commands/threads/thread-template.js +103 -0
- package/src/commands/threads/threads.js +261 -0
- package/src/commands/trust/trust.js +205 -0
- package/src/{orchestrator.js → core/orchestrator.js} +8 -8
- package/src/core/state/state-manager.js +18 -2
- package/src/core/workflows/workflow-detector.js +100 -2
- package/src/lib/agents/micro-agent-factory.js +161 -0
- package/src/lib/analytics/analytics-engine.js +345 -0
- package/src/lib/checkpoints/checkpoint-hooks.js +293 -258
- package/src/lib/context/context-bundler.js +240 -0
- package/src/lib/context/context-optimizer.js +212 -0
- package/src/lib/context/context-tracker.js +273 -0
- package/src/lib/context/core-four-tracker.js +201 -0
- package/src/lib/context/mcp-optimizer.js +200 -0
- package/src/lib/execution/fusion-executor.js +304 -0
- package/src/lib/execution/parallel-executor.js +270 -0
- package/src/lib/generators/context-generator.js +3 -3
- package/src/lib/generators/recap-generator.js +2 -2
- package/src/lib/hooks/hook-executor.js +169 -0
- package/src/lib/hooks/stop-hook-executor.js +286 -0
- package/src/lib/hops/hop-composer.js +221 -0
- package/src/lib/threads/thread-coordinator.js +238 -0
- package/src/lib/threads/thread-manager.js +317 -0
- package/src/lib/tracking/artifact-trail.js +202 -0
- package/src/lib/trust/trust-manager.js +269 -0
- package/src/lib/validators/design-system/design-system-validator.js +2 -2
- package/src/lib/validators/validation-runner.js +6 -6
- package/stacks/blazor-azure/.morph/config/agents.json +72 -3
- package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
- package/CLAUDE.md +0 -993
- package/docs/llm-interaction-config.md +0 -735
- package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
- package/src/commands/utils/migrate-state.js +0 -158
- package/src/commands/utils/upgrade.js +0 -346
- package/src/lib/validators/architecture-validator.js +0 -60
- package/src/lib/validators/content-validator.js +0 -164
- package/src/lib/validators/package-validator.js +0 -61
- package/src/lib/validators/ui-contrast-validator.js +0 -44
- package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
- package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
- package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
- package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
- package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
- package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
- package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
- package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
- package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
- package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
- package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
- package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
- package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
- package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
- package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
- package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
- package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
- package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
- package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
- package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
- package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
- package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
- package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
- package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
- package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
- package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
- package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
- package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
- package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
- package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
- /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
- /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
- /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
- /package/docs/{v3.0 → next-generation}/README.md +0 -0
- /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
|
+
}
|