smart-context-mcp 1.0.4 → 1.2.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/README.md +196 -586
- package/package.json +11 -7
- package/scripts/init-clients.js +56 -27
- package/scripts/report-metrics.js +5 -0
- package/scripts/report-workflow-metrics.js +255 -0
- package/src/analytics/adoption.js +197 -0
- package/src/cache-warming.js +131 -0
- package/src/context-patterns.js +192 -0
- package/src/cross-project.js +343 -0
- package/src/diff-analysis.js +291 -0
- package/src/git-blame.js +324 -0
- package/src/index.js +54 -5
- package/src/metrics.js +6 -1
- package/src/server.js +199 -13
- package/src/storage/sqlite.js +50 -1
- package/src/streaming.js +152 -0
- package/src/tools/smart-context.js +115 -6
- package/src/tools/smart-metrics.js +7 -0
- package/src/tools/smart-read-batch.js +9 -0
- package/src/tools/smart-read.js +21 -1
- package/src/tools/smart-shell.js +33 -9
- package/src/tools/smart-turn.js +1 -0
- package/src/workflow-tracker-stub.js +53 -0
- package/src/workflow-tracker.js +410 -0
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smart-context-mcp",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "MCP server that reduces agent token usage
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "MCP server that reduces agent token usage by 90% with intelligent context compression, task checkpoint persistence, and workflow-aware agent guidance.",
|
|
5
5
|
"author": "Francisco Caballero Portero <fcp1978@hotmail.com>",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/Arrayo/
|
|
9
|
+
"url": "git+https://github.com/Arrayo/smart-context-mcp.git"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://github.com/Arrayo/
|
|
11
|
+
"homepage": "https://github.com/Arrayo/smart-context-mcp#readme",
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/Arrayo/
|
|
13
|
+
"url": "https://github.com/Arrayo/smart-context-mcp/issues"
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
|
16
16
|
"smart-context-headless": "scripts/headless-wrapper.js",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"scripts/devctx-server.js",
|
|
31
31
|
"scripts/headless-wrapper.js",
|
|
32
32
|
"scripts/init-clients.js",
|
|
33
|
-
"scripts/report-metrics.js"
|
|
33
|
+
"scripts/report-metrics.js",
|
|
34
|
+
"scripts/report-workflow-metrics.js"
|
|
34
35
|
],
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=18"
|
|
@@ -51,12 +52,15 @@
|
|
|
51
52
|
"init:clients": "node ./scripts/init-clients.js",
|
|
52
53
|
"smoke:formats": "node ./scripts/format-smoke.js",
|
|
53
54
|
"test": "node --test --test-concurrency=1 ./tests/*.test.js",
|
|
55
|
+
"verify": "node ./scripts/verify-features-direct.js",
|
|
56
|
+
"benchmark": "node ./scripts/run-benchmark.js",
|
|
54
57
|
"eval": "node ./evals/harness.js",
|
|
55
58
|
"eval:context": "node ./evals/harness.js --tool=context",
|
|
56
59
|
"eval:both": "node ./evals/harness.js --tool=both",
|
|
57
60
|
"eval:self": "node ./evals/harness.js --root=../.. --corpus=./evals/corpus/self-tasks.json",
|
|
58
61
|
"eval:report": "node ./evals/report.js",
|
|
59
|
-
"report:metrics": "node ./scripts/report-metrics.js"
|
|
62
|
+
"report:metrics": "node ./scripts/report-metrics.js",
|
|
63
|
+
"report:workflows": "node ./scripts/report-workflow-metrics.js"
|
|
60
64
|
},
|
|
61
65
|
"dependencies": {
|
|
62
66
|
"@modelcontextprotocol/sdk": "^1.13.0",
|
package/scripts/init-clients.js
CHANGED
|
@@ -375,42 +375,71 @@ const updatePreCommitHook = (targetDir, dryRun) => {
|
|
|
375
375
|
// Agent rules — instruct agents to prefer devctx tools over built-in ones
|
|
376
376
|
// ---------------------------------------------------------------------------
|
|
377
377
|
|
|
378
|
-
const agentRuleBody =
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
-
|
|
382
|
-
-
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
|
|
386
|
-
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
- Review: smart_turn(start) → smart_search with intent=implementation → read outline/signatures, focus on changed symbols, minimal changes → smart_turn(end event=milestone).
|
|
398
|
-
- Refactor: smart_turn(start) → smart_search with intent=implementation → signatures for public API, preserve behavior, small edits, verify with tests → smart_turn(end event=milestone).
|
|
399
|
-
- Tests: smart_turn(start) → smart_search with intent=tests → find existing tests, read symbol of function under test → smart_turn(end event=milestone).
|
|
400
|
-
- Config: smart_search with intent=config → find settings, env vars, infrastructure files.
|
|
401
|
-
- Architecture: smart_turn(start) → smart_search with intent=explore → directory structure, outlines of key modules and API boundaries → smart_turn(end event=task_switch or milestone).`;
|
|
378
|
+
const agentRuleBody = `**First time in project?** Run build_index to enable search/context quality.
|
|
379
|
+
|
|
380
|
+
Prefer devctx MCP for non-trivial tasks:
|
|
381
|
+
- smart_read(outline|signatures|symbol) instead of Read → 90% savings
|
|
382
|
+
- smart_search(intent=...) instead of Grep → ranked results
|
|
383
|
+
- smart_context instead of multiple reads → one-call builder
|
|
384
|
+
- smart_shell instead of Shell → safe diagnostics
|
|
385
|
+
|
|
386
|
+
For non-trivial tasks: smart_turn(start) → [work with devctx tools] → smart_turn(end)
|
|
387
|
+
|
|
388
|
+
Reading cascade: outline → signatures → symbol → full (last resort)
|
|
389
|
+
|
|
390
|
+
Detailed workflows: .cursor/rules/profiles-compact/ (debugging, code-review, refactoring, testing, architecture)
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
If you didn't use devctx tools in a non-trivial programming task, add at the end:
|
|
395
|
+
|
|
396
|
+
**Note:** devctx not used because: [task too simple | MCP unavailable | index not built | already had sufficient context | native tool more direct for this case]. To use devctx next time: "Use smart-context-mcp: smart_turn(start) → smart_context/smart_search → smart_read → smart_turn(end)"`;
|
|
402
397
|
|
|
403
398
|
const cursorRuleContent = `---
|
|
404
|
-
description: Prefer devctx MCP tools for
|
|
399
|
+
description: Prefer devctx MCP tools for non-trivial tasks
|
|
405
400
|
alwaysApply: true
|
|
406
401
|
---
|
|
407
402
|
|
|
408
403
|
${agentRuleBody}
|
|
409
404
|
`;
|
|
410
405
|
|
|
406
|
+
const cursorProfilesNote = `
|
|
407
|
+
# Task-Specific Profiles
|
|
408
|
+
|
|
409
|
+
For detailed workflows, see rules in this directory:
|
|
410
|
+
- debugging.mdc - Error-first, symbol-focused (90% savings)
|
|
411
|
+
- code-review.mdc - Diff-aware, API-focused (87% savings)
|
|
412
|
+
- refactoring.mdc - Graph-aware, test-verified (89% savings)
|
|
413
|
+
- testing.mdc - Coverage-aware, TDD-friendly (90% savings)
|
|
414
|
+
- architecture.mdc - Index-first, minimal-detail (90% savings)
|
|
415
|
+
|
|
416
|
+
These profiles are **conditionally applied** based on file globs and task context.
|
|
417
|
+
The base rule (devctx.mdc) is **always active** but kept minimal to reduce fixed context cost.
|
|
418
|
+
`;
|
|
419
|
+
|
|
411
420
|
const updateCursorRule = (targetDir, dryRun) => {
|
|
412
|
-
const
|
|
413
|
-
|
|
421
|
+
const rulesDir = path.join(targetDir, '.cursor', 'rules');
|
|
422
|
+
const profilesDir = path.join(rulesDir, 'profiles-compact');
|
|
423
|
+
|
|
424
|
+
// Write base rule (always active)
|
|
425
|
+
const baseFilePath = path.join(rulesDir, 'devctx.mdc');
|
|
426
|
+
writeFile(baseFilePath, cursorRuleContent, dryRun);
|
|
427
|
+
|
|
428
|
+
// Write profiles README
|
|
429
|
+
const profilesReadmePath = path.join(profilesDir, 'README.md');
|
|
430
|
+
writeFile(profilesReadmePath, cursorProfilesNote, dryRun);
|
|
431
|
+
|
|
432
|
+
// Copy compact profiles from package
|
|
433
|
+
const sourceProfilesDir = path.join(devctxDir, 'agent-rules', 'profiles-compact');
|
|
434
|
+
if (fs.existsSync(sourceProfilesDir)) {
|
|
435
|
+
const profiles = fs.readdirSync(sourceProfilesDir).filter(f => f.endsWith('.mdc'));
|
|
436
|
+
profiles.forEach(profile => {
|
|
437
|
+
const sourcePath = path.join(sourceProfilesDir, profile);
|
|
438
|
+
const targetPath = path.join(profilesDir, profile);
|
|
439
|
+
const content = fs.readFileSync(sourcePath, 'utf8');
|
|
440
|
+
writeFile(targetPath, content, dryRun);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
414
443
|
};
|
|
415
444
|
|
|
416
445
|
const SECTION_START = '<!-- devctx:start -->';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { smartMetrics } from '../src/tools/smart-metrics.js';
|
|
5
|
+
import { formatAdoptionReport } from '../src/analytics/adoption.js';
|
|
5
6
|
|
|
6
7
|
const requireValue = (argv, index, flag) => {
|
|
7
8
|
const value = argv[index + 1];
|
|
@@ -88,6 +89,10 @@ const printHuman = (report) => {
|
|
|
88
89
|
` ${tool.tool.padEnd(14)} count=${formatNumber(tool.count)} raw=${formatNumber(tool.rawTokens)} final=${formatNumber(tool.compressedTokens)} saved=${formatNumber(tool.savedTokens)} (${tool.savingsPct}%)`
|
|
89
90
|
);
|
|
90
91
|
}
|
|
92
|
+
|
|
93
|
+
if (report.adoption) {
|
|
94
|
+
console.log(formatAdoptionReport(report.adoption));
|
|
95
|
+
}
|
|
91
96
|
};
|
|
92
97
|
|
|
93
98
|
export const main = async () => {
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
// Dynamic import to handle SQLite availability
|
|
6
|
+
let workflowTracker;
|
|
7
|
+
try {
|
|
8
|
+
workflowTracker = await import('../src/workflow-tracker.js');
|
|
9
|
+
} catch {
|
|
10
|
+
workflowTracker = await import('../src/workflow-tracker-stub.js');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { getWorkflowMetrics, getWorkflowSummaryByType, WORKFLOW_DEFINITIONS } = workflowTracker;
|
|
14
|
+
|
|
15
|
+
const parseArgs = (argv) => {
|
|
16
|
+
const options = {
|
|
17
|
+
type: null,
|
|
18
|
+
sessionId: null,
|
|
19
|
+
limit: 10,
|
|
20
|
+
json: false,
|
|
21
|
+
summary: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
25
|
+
const token = argv[index];
|
|
26
|
+
|
|
27
|
+
if (token === '--type') {
|
|
28
|
+
options.type = argv[index + 1];
|
|
29
|
+
index += 1;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (token === '--session') {
|
|
34
|
+
options.sessionId = argv[index + 1];
|
|
35
|
+
index += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (token === '--limit') {
|
|
40
|
+
options.limit = parseInt(argv[index + 1], 10);
|
|
41
|
+
index += 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (token === '--json') {
|
|
46
|
+
options.json = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (token === '--summary') {
|
|
51
|
+
options.summary = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (token === '--help' || token === '-h') {
|
|
56
|
+
console.log(`
|
|
57
|
+
Usage: report-workflow-metrics [options]
|
|
58
|
+
|
|
59
|
+
Options:
|
|
60
|
+
--type <type> Filter by workflow type (debugging, code-review, refactoring, testing, architecture)
|
|
61
|
+
--session <id> Filter by session ID
|
|
62
|
+
--limit <n> Limit number of workflows (default: 10)
|
|
63
|
+
--summary Show summary by workflow type
|
|
64
|
+
--json Output as JSON
|
|
65
|
+
--help, -h Show this help
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
# Show summary by workflow type
|
|
69
|
+
npm run report:workflows -- --summary
|
|
70
|
+
|
|
71
|
+
# Show last 10 debugging workflows
|
|
72
|
+
npm run report:workflows -- --type debugging
|
|
73
|
+
|
|
74
|
+
# Show workflows for specific session
|
|
75
|
+
npm run report:workflows -- --session abc123
|
|
76
|
+
|
|
77
|
+
# Output as JSON
|
|
78
|
+
npm run report:workflows -- --json
|
|
79
|
+
`);
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
throw new Error(`Unknown argument: ${token}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return options;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const formatNumber = (value) => new Intl.NumberFormat('en-US').format(value);
|
|
90
|
+
|
|
91
|
+
const formatDuration = (ms) => {
|
|
92
|
+
if (ms < 1000) return `${ms}ms`;
|
|
93
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
94
|
+
if (ms < 3600000) return `${(ms / 60000).toFixed(1)}m`;
|
|
95
|
+
return `${(ms / 3600000).toFixed(1)}h`;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const printSummary = (summary) => {
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log('Workflow Metrics Summary');
|
|
101
|
+
console.log('═'.repeat(120));
|
|
102
|
+
console.log('');
|
|
103
|
+
|
|
104
|
+
if (!Array.isArray(summary) || summary.length === 0) {
|
|
105
|
+
console.log('No completed workflows found.');
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log('Workflows are tracked when:');
|
|
108
|
+
console.log(' 1. Agent calls smart_turn(start) with a task description');
|
|
109
|
+
console.log(' 2. Task matches a workflow pattern (debugging, review, refactor, testing, architecture)');
|
|
110
|
+
console.log(' 3. Agent calls smart_turn(end) to complete the workflow');
|
|
111
|
+
console.log('');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const totalWorkflows = summary.reduce((sum, s) => sum + s.count, 0);
|
|
116
|
+
const totalRaw = summary.reduce((sum, s) => sum + s.total_raw_tokens, 0);
|
|
117
|
+
const totalCompressed = summary.reduce((sum, s) => sum + s.total_compressed_tokens, 0);
|
|
118
|
+
const totalSaved = summary.reduce((sum, s) => sum + s.total_saved_tokens, 0);
|
|
119
|
+
const totalBaseline = summary.reduce((sum, s) => sum + s.total_baseline_tokens, 0);
|
|
120
|
+
|
|
121
|
+
console.log(`Total Workflows: ${formatNumber(totalWorkflows)}`);
|
|
122
|
+
console.log(`Total Raw Tokens: ${formatNumber(totalRaw)}`);
|
|
123
|
+
console.log(`Total Compressed Tokens: ${formatNumber(totalCompressed)}`);
|
|
124
|
+
console.log(`Total Saved Tokens: ${formatNumber(totalSaved)} (${((totalSaved / totalRaw) * 100).toFixed(2)}%)`);
|
|
125
|
+
console.log(`Total Baseline Tokens: ${formatNumber(totalBaseline)}`);
|
|
126
|
+
console.log(`Savings vs Baseline: ${formatNumber(totalBaseline - totalCompressed)} (${(((totalBaseline - totalCompressed) / totalBaseline) * 100).toFixed(2)}%)`);
|
|
127
|
+
console.log('');
|
|
128
|
+
console.log('By Workflow Type:');
|
|
129
|
+
console.log('─'.repeat(120));
|
|
130
|
+
console.log(
|
|
131
|
+
'Type'.padEnd(20) +
|
|
132
|
+
'Count'.padStart(8) +
|
|
133
|
+
'Avg Steps'.padStart(12) +
|
|
134
|
+
'Avg Duration'.padStart(15) +
|
|
135
|
+
'Avg Savings'.padStart(15) +
|
|
136
|
+
'vs Baseline'.padStart(15),
|
|
137
|
+
);
|
|
138
|
+
console.log('─'.repeat(120));
|
|
139
|
+
|
|
140
|
+
for (const s of summary) {
|
|
141
|
+
const def = WORKFLOW_DEFINITIONS[s.workflow_type];
|
|
142
|
+
const name = def ? def.name : s.workflow_type;
|
|
143
|
+
|
|
144
|
+
console.log(
|
|
145
|
+
name.padEnd(20) +
|
|
146
|
+
formatNumber(s.count).padStart(8) +
|
|
147
|
+
s.avgStepsCount.toString().padStart(12) +
|
|
148
|
+
formatDuration(s.avgDurationMs).padStart(15) +
|
|
149
|
+
`${s.avgSavingsPct}%`.padStart(15) +
|
|
150
|
+
`${s.avgVsBaselinePct}%`.padStart(15),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log('─'.repeat(120));
|
|
155
|
+
console.log('');
|
|
156
|
+
|
|
157
|
+
// Show detailed breakdown for each workflow type
|
|
158
|
+
console.log('Detailed Breakdown:');
|
|
159
|
+
console.log('');
|
|
160
|
+
|
|
161
|
+
for (const s of summary) {
|
|
162
|
+
const def = WORKFLOW_DEFINITIONS[s.workflow_type];
|
|
163
|
+
const name = def ? def.name : s.workflow_type;
|
|
164
|
+
|
|
165
|
+
console.log(`${name}:`);
|
|
166
|
+
console.log(` Workflows: ${formatNumber(s.count)}`);
|
|
167
|
+
console.log(` Avg Steps: ${s.avgStepsCount}`);
|
|
168
|
+
console.log(` Avg Duration: ${formatDuration(s.avgDurationMs)}`);
|
|
169
|
+
console.log(` Total Raw Tokens: ${formatNumber(s.total_raw_tokens)}`);
|
|
170
|
+
console.log(` Total Compressed Tokens: ${formatNumber(s.total_compressed_tokens)}`);
|
|
171
|
+
console.log(` Total Saved Tokens: ${formatNumber(s.total_saved_tokens)} (${s.avgSavingsPct}%)`);
|
|
172
|
+
console.log(` Baseline Tokens: ${formatNumber(s.total_baseline_tokens)}`);
|
|
173
|
+
console.log(` Savings vs Baseline: ${formatNumber(s.total_baseline_tokens - s.total_compressed_tokens)} (${s.avgVsBaselinePct}%)`);
|
|
174
|
+
console.log('');
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const printWorkflows = (workflows) => {
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log('Recent Workflows');
|
|
181
|
+
console.log('═'.repeat(120));
|
|
182
|
+
console.log('');
|
|
183
|
+
|
|
184
|
+
if (workflows.length === 0) {
|
|
185
|
+
console.log('No workflows found.');
|
|
186
|
+
console.log('');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const w of workflows) {
|
|
191
|
+
const def = WORKFLOW_DEFINITIONS[w.workflow_type];
|
|
192
|
+
const name = def ? def.name : w.workflow_type;
|
|
193
|
+
const status = w.end_time ? '✓ Completed' : '⏳ In Progress';
|
|
194
|
+
|
|
195
|
+
console.log(`${name} (${status})`);
|
|
196
|
+
console.log(` Workflow ID: ${w.workflow_id}`);
|
|
197
|
+
console.log(` Session ID: ${w.session_id || 'N/A'}`);
|
|
198
|
+
console.log(` Started: ${new Date(w.start_time).toLocaleString()}`);
|
|
199
|
+
|
|
200
|
+
if (w.end_time) {
|
|
201
|
+
console.log(` Ended: ${new Date(w.end_time).toLocaleString()}`);
|
|
202
|
+
console.log(` Duration: ${formatDuration(w.duration_ms)}`);
|
|
203
|
+
console.log(` Steps: ${w.steps_count}`);
|
|
204
|
+
console.log(` Tools Used: ${w.toolsUsed.join(', ')}`);
|
|
205
|
+
console.log(` Raw Tokens: ${formatNumber(w.raw_tokens)}`);
|
|
206
|
+
console.log(` Compressed Tokens: ${formatNumber(w.compressed_tokens)}`);
|
|
207
|
+
console.log(` Saved Tokens: ${formatNumber(w.saved_tokens)} (${w.savings_pct}%)`);
|
|
208
|
+
console.log(` Baseline Tokens: ${formatNumber(w.baseline_tokens)}`);
|
|
209
|
+
console.log(` Savings vs Baseline: ${formatNumber(w.baseline_tokens - w.compressed_tokens)} (${w.vs_baseline_pct}%)`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log('');
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const main = async () => {
|
|
217
|
+
try {
|
|
218
|
+
const options = parseArgs(process.argv.slice(2));
|
|
219
|
+
|
|
220
|
+
if (options.summary) {
|
|
221
|
+
const summary = getWorkflowSummaryByType();
|
|
222
|
+
|
|
223
|
+
if (options.json) {
|
|
224
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
printSummary(summary);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const workflows = getWorkflowMetrics({
|
|
233
|
+
workflowType: options.type,
|
|
234
|
+
sessionId: options.sessionId,
|
|
235
|
+
limit: options.limit,
|
|
236
|
+
completed: true,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (options.json) {
|
|
240
|
+
console.log(JSON.stringify(workflows, null, 2));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
printWorkflows(workflows);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Error:', error.message);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const isDirectExecution = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
252
|
+
|
|
253
|
+
if (isDirectExecution) {
|
|
254
|
+
main();
|
|
255
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adoption analytics - measures how often agents use devctx tools in practice
|
|
3
|
+
*
|
|
4
|
+
* Limitations:
|
|
5
|
+
* - Complexity is inferred from operation count, not actual task complexity
|
|
6
|
+
* - Can't detect feedback shown (requires agent cooperation)
|
|
7
|
+
* - Can't detect forcing prompts (requires prompt analysis)
|
|
8
|
+
* - Can only measure when devctx IS used, not when it's ignored
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEVCTX_TOOLS = new Set([
|
|
12
|
+
'smart_read',
|
|
13
|
+
'smart_search',
|
|
14
|
+
'smart_context',
|
|
15
|
+
'smart_shell',
|
|
16
|
+
'smart_summary',
|
|
17
|
+
'smart_turn',
|
|
18
|
+
'smart_read_batch',
|
|
19
|
+
'smart_metrics',
|
|
20
|
+
'build_index',
|
|
21
|
+
'warm_cache',
|
|
22
|
+
'git_blame',
|
|
23
|
+
'cross_project',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const inferComplexity = (opCount, fileCount) => {
|
|
27
|
+
if (opCount <= 2 && fileCount <= 1) return 'trivial';
|
|
28
|
+
if (opCount <= 5 && fileCount <= 3) return 'simple';
|
|
29
|
+
if (opCount <= 15 && fileCount <= 10) return 'moderate';
|
|
30
|
+
return 'complex';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const groupBySession = (entries) => {
|
|
34
|
+
const sessions = new Map();
|
|
35
|
+
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const sessionId = entry.sessionId || entry.session_id || 'unknown';
|
|
38
|
+
|
|
39
|
+
if (!sessions.has(sessionId)) {
|
|
40
|
+
sessions.set(sessionId, {
|
|
41
|
+
sessionId,
|
|
42
|
+
operations: [],
|
|
43
|
+
devctxTools: new Set(),
|
|
44
|
+
nativeTools: new Set(),
|
|
45
|
+
filesAccessed: new Set(),
|
|
46
|
+
totalRawTokens: 0,
|
|
47
|
+
totalCompressedTokens: 0,
|
|
48
|
+
totalSavedTokens: 0,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const session = sessions.get(sessionId);
|
|
53
|
+
session.operations.push(entry);
|
|
54
|
+
|
|
55
|
+
const tool = entry.tool;
|
|
56
|
+
if (DEVCTX_TOOLS.has(tool)) {
|
|
57
|
+
session.devctxTools.add(tool);
|
|
58
|
+
} else if (tool) {
|
|
59
|
+
session.nativeTools.add(tool);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (entry.target) {
|
|
63
|
+
session.filesAccessed.add(entry.target);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
session.totalRawTokens += Number(entry.rawTokens || entry.raw_tokens || 0);
|
|
67
|
+
session.totalCompressedTokens += Number(entry.compressedTokens || entry.compressed_tokens || 0);
|
|
68
|
+
session.totalSavedTokens += Number(entry.savedTokens || entry.saved_tokens || 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Array.from(sessions.values());
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const analyzeAdoption = (entries) => {
|
|
75
|
+
const sessions = groupBySession(entries);
|
|
76
|
+
|
|
77
|
+
const sessionsWithDevctx = sessions.filter(s => s.devctxTools.size > 0);
|
|
78
|
+
const sessionsWithoutDevctx = sessions.filter(s => s.devctxTools.size === 0 && s.operations.length > 0);
|
|
79
|
+
|
|
80
|
+
const byComplexity = {
|
|
81
|
+
trivial: { total: 0, withDevctx: 0 },
|
|
82
|
+
simple: { total: 0, withDevctx: 0 },
|
|
83
|
+
moderate: { total: 0, withDevctx: 0 },
|
|
84
|
+
complex: { total: 0, withDevctx: 0 },
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
for (const session of sessions) {
|
|
88
|
+
const complexity = inferComplexity(session.operations.length, session.filesAccessed.size);
|
|
89
|
+
byComplexity[complexity].total += 1;
|
|
90
|
+
if (session.devctxTools.size > 0) {
|
|
91
|
+
byComplexity[complexity].withDevctx += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const nonTrivialSessions = sessions.filter(s => {
|
|
96
|
+
const complexity = inferComplexity(s.operations.length, s.filesAccessed.size);
|
|
97
|
+
return complexity !== 'trivial';
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const nonTrivialWithDevctx = nonTrivialSessions.filter(s => s.devctxTools.size > 0);
|
|
101
|
+
|
|
102
|
+
const toolUsageCount = {};
|
|
103
|
+
for (const session of sessionsWithDevctx) {
|
|
104
|
+
for (const tool of session.devctxTools) {
|
|
105
|
+
toolUsageCount[tool] = (toolUsageCount[tool] || 0) + 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
totalSessions: sessions.length,
|
|
111
|
+
sessionsWithDevctx: sessionsWithDevctx.length,
|
|
112
|
+
sessionsWithoutDevctx: sessionsWithoutDevctx.length,
|
|
113
|
+
adoptionRate: sessions.length > 0
|
|
114
|
+
? Number(((sessionsWithDevctx.length / sessions.length) * 100).toFixed(1))
|
|
115
|
+
: 0,
|
|
116
|
+
|
|
117
|
+
nonTrivial: {
|
|
118
|
+
total: nonTrivialSessions.length,
|
|
119
|
+
withDevctx: nonTrivialWithDevctx.length,
|
|
120
|
+
adoptionRate: nonTrivialSessions.length > 0
|
|
121
|
+
? Number(((nonTrivialWithDevctx.length / nonTrivialSessions.length) * 100).toFixed(1))
|
|
122
|
+
: 0,
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
byComplexity: Object.fromEntries(
|
|
126
|
+
Object.entries(byComplexity).map(([level, stats]) => [
|
|
127
|
+
level,
|
|
128
|
+
{
|
|
129
|
+
...stats,
|
|
130
|
+
adoptionRate: stats.total > 0
|
|
131
|
+
? Number(((stats.withDevctx / stats.total) * 100).toFixed(1))
|
|
132
|
+
: 0,
|
|
133
|
+
},
|
|
134
|
+
])
|
|
135
|
+
),
|
|
136
|
+
|
|
137
|
+
toolUsageCount,
|
|
138
|
+
|
|
139
|
+
avgToolsPerSession: sessionsWithDevctx.length > 0
|
|
140
|
+
? Number((sessionsWithDevctx.reduce((sum, s) => sum + s.devctxTools.size, 0) / sessionsWithDevctx.length).toFixed(1))
|
|
141
|
+
: 0,
|
|
142
|
+
|
|
143
|
+
avgTokenSavingsWhenUsed: sessionsWithDevctx.length > 0
|
|
144
|
+
? Number((sessionsWithDevctx.reduce((sum, s) => sum + s.totalSavedTokens, 0) / sessionsWithDevctx.length).toFixed(0))
|
|
145
|
+
: 0,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const formatAdoptionReport = (stats) => {
|
|
150
|
+
const lines = [];
|
|
151
|
+
|
|
152
|
+
lines.push('');
|
|
153
|
+
lines.push('Adoption Analysis (Inferred from Tool Usage)');
|
|
154
|
+
lines.push('');
|
|
155
|
+
lines.push(`Total sessions: ${stats.totalSessions}`);
|
|
156
|
+
lines.push(`Sessions with devctx: ${stats.sessionsWithDevctx} (${stats.adoptionRate}%)`);
|
|
157
|
+
lines.push(`Sessions without: ${stats.sessionsWithoutDevctx} (${100 - stats.adoptionRate}%)`);
|
|
158
|
+
lines.push('');
|
|
159
|
+
|
|
160
|
+
lines.push('Non-Trivial Tasks Only:');
|
|
161
|
+
lines.push(`Total: ${stats.nonTrivial.total}`);
|
|
162
|
+
lines.push(`With devctx: ${stats.nonTrivial.withDevctx} (${stats.nonTrivial.adoptionRate}%)`);
|
|
163
|
+
lines.push(`Without devctx: ${stats.nonTrivial.total - stats.nonTrivial.withDevctx} (${100 - stats.nonTrivial.adoptionRate}%)`);
|
|
164
|
+
lines.push('');
|
|
165
|
+
|
|
166
|
+
lines.push('By Inferred Complexity:');
|
|
167
|
+
for (const [level, data] of Object.entries(stats.byComplexity)) {
|
|
168
|
+
if (data.total === 0) continue;
|
|
169
|
+
lines.push(`- ${level.padEnd(10)} ${data.withDevctx}/${data.total} (${data.adoptionRate}%)`);
|
|
170
|
+
}
|
|
171
|
+
lines.push('');
|
|
172
|
+
|
|
173
|
+
if (stats.sessionsWithDevctx > 0) {
|
|
174
|
+
lines.push('When devctx IS used:');
|
|
175
|
+
lines.push(`Avg tools/session: ${stats.avgToolsPerSession}`);
|
|
176
|
+
lines.push(`Avg token savings: ${stats.avgTokenSavingsWhenUsed.toLocaleString()} tokens`);
|
|
177
|
+
lines.push('');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
lines.push('Top Tools Used:');
|
|
181
|
+
const sortedTools = Object.entries(stats.toolUsageCount)
|
|
182
|
+
.sort((a, b) => b[1] - a[1])
|
|
183
|
+
.slice(0, 5);
|
|
184
|
+
for (const [tool, count] of sortedTools) {
|
|
185
|
+
lines.push(`- ${tool.padEnd(20)} ${count} sessions`);
|
|
186
|
+
}
|
|
187
|
+
lines.push('');
|
|
188
|
+
|
|
189
|
+
lines.push('Limitations:');
|
|
190
|
+
lines.push('- Complexity inferred from operation count (not actual task complexity)');
|
|
191
|
+
lines.push('- Can only measure when devctx IS used (tool calls visible)');
|
|
192
|
+
lines.push('- Cannot measure feedback shown or forcing prompts (requires agent cooperation)');
|
|
193
|
+
lines.push('- Sessions without devctx may be simple tasks (not adoption failures)');
|
|
194
|
+
lines.push('');
|
|
195
|
+
|
|
196
|
+
return lines.join('\n');
|
|
197
|
+
};
|