smart-context-mcp 1.10.0 → 1.11.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 +1 -1
- package/package.json +1 -1
- package/scripts/init-clients.js +1 -3
- package/scripts/report-adoption-metrics.js +0 -9
- package/scripts/report-workflow-metrics.js +0 -1
- package/server.json +2 -2
- package/src/index.js +1 -3
- package/src/missed-opportunities.js +0 -1
- package/src/server.js +4 -6
- package/src/tools/smart-context.js +2 -14
- package/src/tools/smart-read.js +0 -5
- package/src/tools/smart-search.js +1 -5
- package/src/tools/smart-shell.js +47 -6
- package/src/usage-feedback.js +0 -1
- package/src/workflow-tracker.js +0 -10
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Restart your AI client. Done.
|
|
|
56
56
|
# Check installed version
|
|
57
57
|
npm list -g smart-context-mcp
|
|
58
58
|
|
|
59
|
-
# Should show: smart-context-mcp@1.
|
|
59
|
+
# Should show: smart-context-mcp@1.11.0 (or later)
|
|
60
60
|
|
|
61
61
|
# Update to latest version
|
|
62
62
|
npm update -g smart-context-mcp
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smart-context-mcp",
|
|
3
3
|
"mcpName": "io.github.Arrayo/smart-context-mcp",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.11.0",
|
|
5
5
|
"description": "MCP server that reduces agent token usage by 90% with intelligent context compression, task checkpoint persistence, and workflow-aware agent guidance.",
|
|
6
6
|
"author": "Francisco Caballero Portero <fcp1978@hotmail.com>",
|
|
7
7
|
"type": "module",
|
package/scripts/init-clients.js
CHANGED
|
@@ -461,11 +461,9 @@ const updateCursorRule = (targetDir, dryRun) => {
|
|
|
461
461
|
const rulesDir = path.join(targetDir, '.cursor', 'rules');
|
|
462
462
|
const profilesDir = path.join(rulesDir, 'profiles-compact');
|
|
463
463
|
|
|
464
|
-
// Write base rule (always active)
|
|
465
464
|
const baseFilePath = path.join(rulesDir, 'devctx.mdc');
|
|
466
465
|
writeFile(baseFilePath, cursorRuleContent, dryRun);
|
|
467
|
-
|
|
468
|
-
// Write profiles README
|
|
466
|
+
|
|
469
467
|
const profilesReadmePath = path.join(profilesDir, 'README.md');
|
|
470
468
|
writeFile(profilesReadmePath, cursorProfilesNote, dryRun);
|
|
471
469
|
|
|
@@ -73,7 +73,6 @@ const calculateAdoptionMetrics = (days = 30) => {
|
|
|
73
73
|
return withStateDb((db) => {
|
|
74
74
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
75
75
|
|
|
76
|
-
// Get all sessions since cutoff
|
|
77
76
|
const sessions = db
|
|
78
77
|
.prepare(
|
|
79
78
|
`
|
|
@@ -94,7 +93,6 @@ const calculateAdoptionMetrics = (days = 30) => {
|
|
|
94
93
|
toolUsage: {},
|
|
95
94
|
};
|
|
96
95
|
|
|
97
|
-
// Initialize workflow stats
|
|
98
96
|
Object.keys(WORKFLOW_DEFINITIONS).forEach((type) => {
|
|
99
97
|
results.byWorkflow[type] = {
|
|
100
98
|
total: 0,
|
|
@@ -103,12 +101,10 @@ const calculateAdoptionMetrics = (days = 30) => {
|
|
|
103
101
|
};
|
|
104
102
|
});
|
|
105
103
|
|
|
106
|
-
// Analyze each session
|
|
107
104
|
sessions.forEach((session) => {
|
|
108
105
|
const snapshot = JSON.parse(session.snapshot_json || '{}');
|
|
109
106
|
const sessionId = session.session_id;
|
|
110
107
|
|
|
111
|
-
// Get events for this session
|
|
112
108
|
const sessionEvents = db
|
|
113
109
|
.prepare('SELECT * FROM session_events WHERE session_id = ?')
|
|
114
110
|
.all(sessionId);
|
|
@@ -117,25 +113,21 @@ const calculateAdoptionMetrics = (days = 30) => {
|
|
|
117
113
|
.prepare('SELECT * FROM metrics_events WHERE session_id = ?')
|
|
118
114
|
.all(sessionId);
|
|
119
115
|
|
|
120
|
-
// Check if non-trivial
|
|
121
116
|
if (!isNonTrivialTask(sessionEvents, metricsEvents)) {
|
|
122
117
|
return;
|
|
123
118
|
}
|
|
124
119
|
|
|
125
120
|
results.nonTrivialTasks++;
|
|
126
121
|
|
|
127
|
-
// Check if used devctx
|
|
128
122
|
const hasDevctx = usedDevctx(metricsEvents);
|
|
129
123
|
if (hasDevctx) {
|
|
130
124
|
results.tasksWithDevctx++;
|
|
131
125
|
}
|
|
132
126
|
|
|
133
|
-
// Track tool usage
|
|
134
127
|
metricsEvents.forEach((m) => {
|
|
135
128
|
results.toolUsage[m.tool] = (results.toolUsage[m.tool] || 0) + 1;
|
|
136
129
|
});
|
|
137
130
|
|
|
138
|
-
// Classify by workflow if possible
|
|
139
131
|
const goal = snapshot.goal || '';
|
|
140
132
|
let workflowType = null;
|
|
141
133
|
|
|
@@ -154,7 +146,6 @@ const calculateAdoptionMetrics = (days = 30) => {
|
|
|
154
146
|
}
|
|
155
147
|
});
|
|
156
148
|
|
|
157
|
-
// Calculate rates
|
|
158
149
|
if (results.nonTrivialTasks > 0) {
|
|
159
150
|
results.adoptionRate = (results.tasksWithDevctx / results.nonTrivialTasks) * 100;
|
|
160
151
|
}
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/Arrayo/smart-context-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.11.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "smart-context-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.11.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/src/index.js
CHANGED
|
@@ -726,9 +726,7 @@ export const buildIndex = (root, progress = null) => {
|
|
|
726
726
|
if (sym.snippet) entry.snippet = sym.snippet;
|
|
727
727
|
invertedIndex[key].push(entry);
|
|
728
728
|
}
|
|
729
|
-
} catch {
|
|
730
|
-
// skip unreadable files
|
|
731
|
-
}
|
|
729
|
+
} catch { /* unreadable */ }
|
|
732
730
|
|
|
733
731
|
processed++;
|
|
734
732
|
|
|
@@ -141,7 +141,6 @@ export const formatMissedOpportunities = () => {
|
|
|
141
141
|
lines.push('⚠️ **Missed devctx opportunities detected:**');
|
|
142
142
|
lines.push('');
|
|
143
143
|
|
|
144
|
-
// Show session stats
|
|
145
144
|
lines.push(`**Session stats:**`);
|
|
146
145
|
lines.push(`- Duration: ${analysis.sessionDuration}s`);
|
|
147
146
|
lines.push(`- devctx operations: ${analysis.devctxOperations}`);
|
package/src/server.js
CHANGED
|
@@ -57,10 +57,8 @@ export const createDevctxServer = () => {
|
|
|
57
57
|
version,
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
// Enable streaming progress notifications
|
|
61
60
|
setServerForStreaming(server);
|
|
62
61
|
|
|
63
|
-
// Register prompts
|
|
64
62
|
server.prompt(
|
|
65
63
|
'use-devctx',
|
|
66
64
|
'Force the agent to use devctx tools for the current task. Use this prompt at the start of your message to ensure devctx is used instead of native tools.',
|
|
@@ -128,7 +126,7 @@ This ensures optimal performance and context recovery.`,
|
|
|
128
126
|
|
|
129
127
|
server.tool(
|
|
130
128
|
'smart_read',
|
|
131
|
-
'Read a file with token-efficient modes. outline/signatures
|
|
129
|
+
'Read a file with token-efficient modes. PREFER outline/signatures/symbol — full mode saves 0 tokens (same content as native Read, capped at 12k chars). Mode guide: outline (~90% savings): file structure, exports, top-level symbols — use for orientation, code review, deciding what to read next. signatures (~85% savings): function/method signatures only — use when you need parameter names and return types without bodies. symbol: extract one or more functions/classes/methods by name (string or array for batch) — use when you know exactly what to read; add context=true to include callers, tests, and referenced types from the dependency graph (returns graphCoverage: full|partial|none). range: specific line range with line numbers — use only when you need exact lines. full: raw file content, no compression, no savings — only use when the exact byte-for-byte content is required (e.g. config files, lock files). maxTokens: token budget — auto-selects the most detailed mode that fits (full → outline → signatures → truncated). Responses are cached in memory per session and invalidated by file mtime; cached=true when served from cache. Every response includes a unified confidence block: { parser, truncated, cached, graphCoverage? }. Supports JS/TS, Python, Go, Rust, Java, C#, Kotlin, PHP, Swift, shell, Terraform, Dockerfile, SQL, JSON, TOML, YAML.',
|
|
132
130
|
{
|
|
133
131
|
filePath: z.string(),
|
|
134
132
|
mode: z.enum(['full', 'outline', 'signatures', 'range', 'symbol']).optional(),
|
|
@@ -144,7 +142,7 @@ This ensures optimal performance and context recovery.`,
|
|
|
144
142
|
|
|
145
143
|
server.tool(
|
|
146
144
|
'smart_read_batch',
|
|
147
|
-
'Read multiple files in one call. Each item accepts path, mode, symbol, startLine, endLine, maxTokens (per-file budget). Optional global maxTokens budget with early stop when exceeded. Max 20 files per call.',
|
|
145
|
+
'Read multiple files in one call. Each item accepts path, mode (prefer outline/signatures/symbol — full saves 0 tokens), symbol, startLine, endLine, maxTokens (per-file budget). Optional global maxTokens budget with early stop when exceeded. Max 20 files per call.',
|
|
148
146
|
{
|
|
149
147
|
files: z.array(z.object({
|
|
150
148
|
path: z.string(),
|
|
@@ -190,7 +188,7 @@ This ensures optimal performance and context recovery.`,
|
|
|
190
188
|
|
|
191
189
|
server.tool(
|
|
192
190
|
'smart_shell',
|
|
193
|
-
'Run a diagnostic shell command from an allowlist. Allowed: pwd, ls, find, rg, git (status/diff/show/log/branch/rev-parse), npm/pnpm/yarn/bun (test/run/lint/build/typecheck/check). Blocks shell operators, pipes, and unsafe commands. Includes a unified confidence block: { blocked, timedOut }.',
|
|
191
|
+
'Run a diagnostic shell command from an allowlist. Allowed: pwd, ls, find, rg, git (status/diff/show/log/branch/rev-parse), npm/pnpm/yarn/bun (test/run/lint/build/typecheck/check). Blocks shell operators, pipes, and unsafe commands. For large diffs: output is split by file (up to 8 files, 60 lines each) with a hint to run git show -- <file> for the full body of any truncated file; prefer git diff --stat first to see which files changed, then git show -- <file> per file for targeted reading. Includes a unified confidence block: { blocked, timedOut }.',
|
|
194
192
|
{
|
|
195
193
|
command: z.string(),
|
|
196
194
|
},
|
|
@@ -487,7 +485,7 @@ This ensures optimal performance and context recovery.`,
|
|
|
487
485
|
|
|
488
486
|
server.tool(
|
|
489
487
|
'smart_turn',
|
|
490
|
-
'Orchestrate start/end of a meaningful agent turn
|
|
488
|
+
'Orchestrate start/end of a meaningful agent turn for multi-session tasks where context continuity matters. SKIP for single-session point-in-time tasks (reviewing a specific commit, answering a quick question, one-off lookup) — the setup overhead exceeds the benefit if the session will never be resumed. USE when: the task spans multiple chat sessions, you may return to it the next day, or the codebase context is large enough that re-reading is expensive. `phase: "start"` rehydrates persisted context, classifies prompt continuity against the saved session, optionally auto-creates a planning session for a new substantial task, returns `recommendedPath` guidance for the next safe devctx actions, and can include compact metrics. `phase: "end"` writes a checkpoint through smart_summary, returns follow-up `recommendedPath` guidance, and can optionally include compact metrics. Both phases expose `mutationSafety` when repo-safety blocks persisted writes and surface `storageHealth` when SQLite state is missing, oversized, locked, or corrupted.',
|
|
491
489
|
{
|
|
492
490
|
phase: z.enum(['start', 'end']),
|
|
493
491
|
sessionId: z.string().optional(),
|
|
@@ -412,17 +412,11 @@ export const smartContext = async ({
|
|
|
412
412
|
|
|
413
413
|
await ensureIndexReady({ root });
|
|
414
414
|
|
|
415
|
-
// Get detailed diff stats
|
|
416
415
|
const detailedChanges = await getDetailedDiff(changed.ref, root);
|
|
417
416
|
const index = loadIndex(root);
|
|
418
|
-
|
|
419
|
-
// Analyze impact and prioritize
|
|
420
417
|
const prioritized = analyzeChangeImpact(detailedChanges, index);
|
|
421
|
-
|
|
422
|
-
// Expand to include related files (importers, dependencies, tests)
|
|
423
418
|
const expandedFiles = expandChangedContext(changed.files, index, 10);
|
|
424
|
-
|
|
425
|
-
// Build primary seeds with priority and impact data
|
|
419
|
+
|
|
426
420
|
primarySeeds = Array.from(expandedFiles).map(rel => {
|
|
427
421
|
const changeInfo = prioritized.find(c => c.file === rel);
|
|
428
422
|
const evidence = [{
|
|
@@ -432,7 +426,6 @@ export const smartContext = async ({
|
|
|
432
426
|
impact: changeInfo?.impactScore || 0,
|
|
433
427
|
}];
|
|
434
428
|
|
|
435
|
-
// Mark files that were expanded (not directly changed)
|
|
436
429
|
if (!changed.files.includes(rel)) {
|
|
437
430
|
evidence[0].expanded = true;
|
|
438
431
|
}
|
|
@@ -444,7 +437,6 @@ export const smartContext = async ({
|
|
|
444
437
|
};
|
|
445
438
|
});
|
|
446
439
|
|
|
447
|
-
// Sort by impact (critical changes first)
|
|
448
440
|
primarySeeds.sort((a, b) => {
|
|
449
441
|
const impactA = a.evidence[0].impact || 0;
|
|
450
442
|
const impactB = b.evidence[0].impact || 0;
|
|
@@ -771,17 +763,13 @@ export const smartContext = async ({
|
|
|
771
763
|
timestamp: new Date().toISOString(),
|
|
772
764
|
});
|
|
773
765
|
|
|
774
|
-
// Record usage for feedback
|
|
775
766
|
recordToolUsage({
|
|
776
767
|
tool: 'smart_context',
|
|
777
768
|
savedTokens,
|
|
778
769
|
target: task,
|
|
779
770
|
});
|
|
780
|
-
|
|
781
|
-
// Record devctx operation for missed opportunity detection
|
|
782
771
|
recordDevctxOperation();
|
|
783
|
-
|
|
784
|
-
// Record decision explanation
|
|
772
|
+
|
|
785
773
|
let reason = DECISION_REASONS.TASK_CONTEXT;
|
|
786
774
|
if (diff) {
|
|
787
775
|
reason = DECISION_REASONS.DIFF_ANALYSIS;
|
package/src/tools/smart-read.js
CHANGED
|
@@ -536,17 +536,12 @@ export const smartRead = async ({ filePath, mode = 'outline', startLine, endLine
|
|
|
536
536
|
|
|
537
537
|
await persistMetrics(metrics);
|
|
538
538
|
|
|
539
|
-
// Record usage for feedback
|
|
540
539
|
recordToolUsage({
|
|
541
540
|
tool: 'smart_read',
|
|
542
541
|
savedTokens: metrics.savedTokens,
|
|
543
542
|
target: path.relative(effectiveRoot, fullPath),
|
|
544
543
|
});
|
|
545
|
-
|
|
546
|
-
// Record devctx operation for missed opportunity detection
|
|
547
544
|
recordDevctxOperation();
|
|
548
|
-
|
|
549
|
-
// Record decision explanation
|
|
550
545
|
const lineCount = content.split('\n').length;
|
|
551
546
|
let reason = DECISION_REASONS.LARGE_FILE;
|
|
552
547
|
let expectedBenefit = EXPECTED_BENEFITS.TOKEN_SAVINGS(metrics.savedTokens);
|
|
@@ -475,17 +475,13 @@ export const smartSearch = async ({ query, cwd = '.', intent, _testForceWalk = f
|
|
|
475
475
|
|
|
476
476
|
await persistMetrics(metrics);
|
|
477
477
|
|
|
478
|
-
// Record usage for feedback
|
|
479
478
|
recordToolUsage({
|
|
480
479
|
tool: 'smart_search',
|
|
481
480
|
savedTokens: metrics.savedTokens,
|
|
482
481
|
target: query,
|
|
483
482
|
});
|
|
484
|
-
|
|
485
|
-
// Record devctx operation for missed opportunity detection
|
|
486
483
|
recordDevctxOperation();
|
|
487
|
-
|
|
488
|
-
// Record decision explanation
|
|
484
|
+
|
|
489
485
|
let reason = DECISION_REASONS.MULTIPLE_FILES;
|
|
490
486
|
if (validIntent) {
|
|
491
487
|
reason = DECISION_REASONS.INTENT_AWARE;
|
package/src/tools/smart-shell.js
CHANGED
|
@@ -176,6 +176,52 @@ const validateCommand = (command, tokens) => {
|
|
|
176
176
|
return null;
|
|
177
177
|
};
|
|
178
178
|
|
|
179
|
+
const DIFF_FILE_HEADER = /^diff --git a\/.+ b\/.+/;
|
|
180
|
+
const DIFF_HUNK_HEADER = /^@@ /;
|
|
181
|
+
const MAX_DIFF_FILES = 8;
|
|
182
|
+
const MAX_LINES_PER_FILE = 60;
|
|
183
|
+
const DIFF_TOTAL_LIMIT = 4000;
|
|
184
|
+
|
|
185
|
+
const splitDiffByFile = (text) => {
|
|
186
|
+
const files = [];
|
|
187
|
+
let current = null;
|
|
188
|
+
|
|
189
|
+
for (const line of text.split('\n')) {
|
|
190
|
+
if (DIFF_FILE_HEADER.test(line)) {
|
|
191
|
+
if (current) files.push(current);
|
|
192
|
+
current = { header: line, lines: [] };
|
|
193
|
+
} else if (current) {
|
|
194
|
+
current.lines.push(line);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (current) files.push(current);
|
|
198
|
+
return files;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const compressDiff = (text) => {
|
|
202
|
+
if (!DIFF_FILE_HEADER.test(text)) return text;
|
|
203
|
+
|
|
204
|
+
const files = splitDiffByFile(text);
|
|
205
|
+
if (files.length === 0) return text;
|
|
206
|
+
|
|
207
|
+
const shown = files.slice(0, MAX_DIFF_FILES);
|
|
208
|
+
const skipped = files.length - shown.length;
|
|
209
|
+
|
|
210
|
+
const parts = shown.map(({ header, lines }) => {
|
|
211
|
+
const truncatedLines = lines.slice(0, MAX_LINES_PER_FILE);
|
|
212
|
+
const skippedLines = lines.length - truncatedLines.length;
|
|
213
|
+
const hunkCount = lines.filter((l) => DIFF_HUNK_HEADER.test(l)).length;
|
|
214
|
+
const suffix = skippedLines > 0 ? [`... (${skippedLines} more lines — use smart_read(symbol) for full body)`] : [];
|
|
215
|
+
return [header, `# ${hunkCount} hunk(s)`, ...truncatedLines, ...suffix].join('\n');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const footer = skipped > 0
|
|
219
|
+
? `\n# ${skipped} more file(s) not shown — run git show -- <file> for each`
|
|
220
|
+
: '';
|
|
221
|
+
|
|
222
|
+
return truncate(parts.join('\n\n'), DIFF_TOTAL_LIMIT) + footer;
|
|
223
|
+
};
|
|
224
|
+
|
|
179
225
|
const buildBlockedResult = async (command, message) => {
|
|
180
226
|
const metrics = buildMetrics({
|
|
181
227
|
tool: 'smart_shell',
|
|
@@ -254,7 +300,7 @@ export const smartShell = async ({ command }) => {
|
|
|
254
300
|
]);
|
|
255
301
|
const shouldPrioritizeRelevant = execution.code !== 0 || execution.timedOut;
|
|
256
302
|
const compressedSource = shouldPrioritizeRelevant && relevant ? relevant : rawText;
|
|
257
|
-
const compressedText = truncate(uniqueLines(compressedSource), 5000);
|
|
303
|
+
const compressedText = truncate(compressDiff(uniqueLines(compressedSource)), 5000);
|
|
258
304
|
const metrics = buildMetrics({
|
|
259
305
|
tool: 'smart_shell',
|
|
260
306
|
target: command,
|
|
@@ -264,17 +310,12 @@ export const smartShell = async ({ command }) => {
|
|
|
264
310
|
|
|
265
311
|
await persistMetrics(metrics);
|
|
266
312
|
|
|
267
|
-
// Record usage for feedback
|
|
268
313
|
recordToolUsage({
|
|
269
314
|
tool: 'smart_shell',
|
|
270
315
|
savedTokens: metrics.savedTokens,
|
|
271
316
|
target: command,
|
|
272
317
|
});
|
|
273
|
-
|
|
274
|
-
// Record devctx operation for missed opportunity detection
|
|
275
318
|
recordDevctxOperation();
|
|
276
|
-
|
|
277
|
-
// Record decision explanation
|
|
278
319
|
const outputLines = rawText.split('\n').length;
|
|
279
320
|
let reason = DECISION_REASONS.COMMAND_OUTPUT;
|
|
280
321
|
if (shouldPrioritizeRelevant && relevant) {
|
package/src/usage-feedback.js
CHANGED
|
@@ -60,7 +60,6 @@ export const formatUsageFeedback = () => {
|
|
|
60
60
|
lines.push('');
|
|
61
61
|
lines.push('📊 **devctx usage this session:**');
|
|
62
62
|
|
|
63
|
-
// Sort by count descending
|
|
64
63
|
const sorted = usage.tools.sort((a, b) => b.count - a.count);
|
|
65
64
|
|
|
66
65
|
for (const { tool, count, savedTokens, targets } of sorted) {
|
package/src/workflow-tracker.js
CHANGED
|
@@ -183,7 +183,6 @@ export const endWorkflow = (workflowId) => {
|
|
|
183
183
|
return null;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
// Get workflow start time and session
|
|
187
186
|
const workflow = db
|
|
188
187
|
.prepare(
|
|
189
188
|
`
|
|
@@ -203,7 +202,6 @@ export const endWorkflow = (workflowId) => {
|
|
|
203
202
|
const endTime = new Date(now);
|
|
204
203
|
const durationMs = endTime - startTime;
|
|
205
204
|
|
|
206
|
-
// Get all metrics for this session since workflow start
|
|
207
205
|
const metrics = db
|
|
208
206
|
.prepare(
|
|
209
207
|
`
|
|
@@ -215,7 +213,6 @@ export const endWorkflow = (workflowId) => {
|
|
|
215
213
|
)
|
|
216
214
|
.all(workflow.session_id, workflow.start_time);
|
|
217
215
|
|
|
218
|
-
// Calculate totals
|
|
219
216
|
const rawTokens = metrics.reduce((sum, m) => sum + (m.raw_tokens || 0), 0);
|
|
220
217
|
const compressedTokens = metrics.reduce((sum, m) => sum + (m.compressed_tokens || 0), 0);
|
|
221
218
|
const savedTokens = metrics.reduce((sum, m) => sum + (m.saved_tokens || 0), 0);
|
|
@@ -231,7 +228,6 @@ export const endWorkflow = (workflowId) => {
|
|
|
231
228
|
const savingsPct = rawTokens > 0 ? ((savedTokens / rawTokens) * 100).toFixed(2) : 0;
|
|
232
229
|
const netSavingsPct = rawTokens > 0 ? ((netSavedTokens / rawTokens) * 100).toFixed(2) : 0;
|
|
233
230
|
|
|
234
|
-
// Calculate vs baseline
|
|
235
231
|
const baselineTokens = workflow.baseline_tokens || 0;
|
|
236
232
|
const vsBaselinePct = baselineTokens > 0 ? (((baselineTokens - compressedTokens) / baselineTokens) * 100).toFixed(2) : 0;
|
|
237
233
|
const vsBaselineNetPct = baselineTokens > 0 ? (((baselineTokens - (compressedTokens + overheadTokens)) / baselineTokens) * 100).toFixed(2) : 0;
|
|
@@ -247,10 +243,7 @@ export const endWorkflow = (workflowId) => {
|
|
|
247
243
|
},
|
|
248
244
|
};
|
|
249
245
|
|
|
250
|
-
// Get unique tools used
|
|
251
246
|
const toolsUsed = [...new Set(metrics.map((m) => m.tool))];
|
|
252
|
-
|
|
253
|
-
// Update workflow
|
|
254
247
|
const stmt = db.prepare(`
|
|
255
248
|
UPDATE workflow_metrics
|
|
256
249
|
SET end_time = ?,
|
|
@@ -584,7 +577,6 @@ export const autoTrackWorkflow = (sessionId, sessionGoal) => {
|
|
|
584
577
|
return null;
|
|
585
578
|
}
|
|
586
579
|
|
|
587
|
-
// Check if workflow already tracked for this session
|
|
588
580
|
const existing = db
|
|
589
581
|
.prepare(
|
|
590
582
|
`
|
|
@@ -601,7 +593,6 @@ export const autoTrackWorkflow = (sessionId, sessionGoal) => {
|
|
|
601
593
|
return existing.workflow_id;
|
|
602
594
|
}
|
|
603
595
|
|
|
604
|
-
// Get tools used so far in this session
|
|
605
596
|
const metrics = db
|
|
606
597
|
.prepare(
|
|
607
598
|
`
|
|
@@ -621,7 +612,6 @@ export const autoTrackWorkflow = (sessionId, sessionGoal) => {
|
|
|
621
612
|
return null;
|
|
622
613
|
}
|
|
623
614
|
|
|
624
|
-
// Start tracking
|
|
625
615
|
return startWorkflow(workflowType, sessionId, { autoDetected: true, goal: sessionGoal });
|
|
626
616
|
});
|
|
627
617
|
} catch {
|