smart-context-mcp 1.4.0 → 1.6.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # smart-context-mcp
2
2
 
3
- MCP server that reduces AI agent token usage by 90% with intelligent context compression.
3
+ MCP server that reduces AI agent token usage by up to 90% with intelligent context compression (measured on this project).
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/smart-context-mcp.svg)](https://www.npmjs.com/package/smart-context-mcp)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -14,6 +14,14 @@ npx smart-context-init --target . --clients cursor
14
14
  ```
15
15
  Restart Cursor. Done.
16
16
 
17
+ Optional assisted mode for long tasks:
18
+ ```bash
19
+ ./.devctx/bin/cursor-devctx task --prompt "your task" -- <agent-command> [args...]
20
+ ./.devctx/bin/cursor-devctx implement --prompt "implement the auth guard" -- <agent-command> [args...]
21
+ ./.devctx/bin/cursor-devctx review --prompt "review the latest diff" -- <agent-command> [args...]
22
+ ./.devctx/bin/cursor-devctx doctor
23
+ ```
24
+
17
25
  ### Codex CLI
18
26
  ```bash
19
27
  npm install -g smart-context-mcp
@@ -44,6 +52,62 @@ Restart your AI client. Done.
44
52
 
45
53
  ---
46
54
 
55
+ ## `1.6.0` Task Runner
56
+
57
+ `1.6.0` adds `smart-context-task`, a workflow-oriented CLI on top of the raw MCP tools.
58
+
59
+ Use it when you want a more repeatable path than “agent reads rules and hopefully picks the right flow”.
60
+
61
+ ```bash
62
+ smart-context-task task --prompt "inspect the auth flow and continue the bugfix"
63
+ smart-context-task implement --prompt "add a token guard to loginHandler"
64
+ smart-context-task continue --session-id my-session-id
65
+ smart-context-task review --prompt "review the latest diff"
66
+ smart-context-task doctor
67
+ ```
68
+
69
+ The runner now covers:
70
+
71
+ - `task`
72
+ - `implement`
73
+ - `continue`
74
+ - `resume`
75
+ - `review`
76
+ - `debug`
77
+ - `refactor`
78
+ - `test`
79
+ - `doctor`
80
+ - `status`
81
+ - `checkpoint`
82
+ - `cleanup`
83
+
84
+ For Cursor projects, `smart-context-init` also generates `./.devctx/bin/cursor-devctx`, which routes through the same runner/policy stack.
85
+
86
+ See [Task Runner Workflows](../../docs/task-runner.md) for the full behavior and command guidance.
87
+
88
+ ---
89
+
90
+ ## 📊 Real Metrics
91
+
92
+ **Production use on this project:**
93
+ - ~7M tokens → ~800K tokens (approximately 89% reduction)
94
+ - 1,500+ operations tracked
95
+ - Compression ratios: 3x to 46x
96
+
97
+ **Workflow savings:**
98
+ - Debugging: ~85-90% reduction
99
+ - Code Review: ~85-90% reduction
100
+ - Refactoring: ~85-90% reduction
101
+ - Testing: ~85-90% reduction
102
+ - Architecture: ~85-90% reduction
103
+
104
+ **Real adoption:**
105
+ - Approximately 70-75% of complex tasks use devctx
106
+ - Top tools: `smart_read` (850+), `smart_search` (280+), `smart_shell` (220+)
107
+ - Non-usage: task too simple, no index built, native tools preferred
108
+
109
+ ---
110
+
47
111
  ## 🚀 How to Invoke the MCP
48
112
 
49
113
  The MCP doesn't intercept prompts automatically. **You need to tell the agent to use it.**
@@ -98,6 +162,36 @@ The agent *should* use devctx automatically for complex tasks because:
98
162
 
99
163
  ---
100
164
 
165
+ ## 🚨 Agent Ignored devctx? → Paste This Next
166
+
167
+ <table>
168
+ <tr>
169
+ <td width="100%" bgcolor="#FFF3CD">
170
+
171
+ ### 📋 Official Prompt (Copy & Paste)
172
+
173
+ ```
174
+ Use smart-context-mcp for this task.
175
+ Start with smart_turn(start), then use smart_context or smart_search before reading full files.
176
+ End with smart_turn(end) if you make progress.
177
+ ```
178
+
179
+ ### ⚡ Ultra-Short
180
+
181
+ ```
182
+ Use devctx: smart_turn(start) → smart_context → smart_turn(end)
183
+ ```
184
+
185
+ </td>
186
+ </tr>
187
+ </table>
188
+
189
+ **When:** Agent read large files with `Read`, used `Grep` repeatedly, or no devctx tools in complex task.
190
+
191
+ **Why:** Task seemed simple, no index built, native tools appeared more direct, or rules weren't strong enough.
192
+
193
+ ---
194
+
101
195
  ## How it Works in Practice
102
196
 
103
197
  **The reality:** This MCP does not intercept prompts automatically. Here's the actual flow:
@@ -116,11 +210,17 @@ The agent *should* use devctx automatically for complex tasks because:
116
210
  - ✅ Rules **guide** the agent (not enforce)
117
211
  - ✅ Agent can use built-in tools when appropriate
118
212
  - ✅ Token savings: 85-90% on complex tasks
213
+ - ✅ Reports can show both gross savings and net savings after context overhead
214
+ - ✅ Workflow JSON/reporting now exposes net-metrics coverage, so historical rows without persisted overhead are explicit
215
+ - ✅ `smart_metrics` now exposes measured orchestration-quality signals from `smart_turn` (continuity recovery, blocked-state remediation coverage, context-refresh signals)
216
+ - ✅ If `.devctx/state.sqlite` is tracked or staged, runtime SQLite mutations pause across checkpoints, workflow tracking, hook state, and pattern learning
119
217
 
120
218
  Check actual usage:
121
219
  - **Real-time feedback** - Enabled by default (disable with `export DEVCTX_SHOW_USAGE=false`)
122
220
  - `npm run report:metrics` - Tool-level savings + adoption analysis
123
221
  - `npm run report:workflows` - Workflow-level savings (requires `DEVCTX_WORKFLOW_TRACKING=true`)
222
+ - `npm run benchmark:orchestration` - Repeatable orchestration regression suite for continuity, refresh, blocked-state remediation, and checkpoint quality
223
+ - `npm run benchmark:orchestration:release` - Same suite with a checked-in release baseline, used by CI and `prepublishOnly`
124
224
 
125
225
  ## What it does
126
226
 
@@ -341,6 +441,23 @@ Maintain task checkpoint:
341
441
  ```
342
442
 
343
443
  Stores compressed task state (~100 tokens: goal, status, decisions, blockers), not full conversation. Supports both flat and nested parameter formats.
444
+ When git hygiene or SQLite storage health affects persisted state, responses expose `mutationSafety`, `repoSafety`, `degradedMode`, and `storageHealth` so clients can remediate consistently.
445
+
446
+ ### smart_doctor
447
+
448
+ Run a single operational health check across repo hygiene, SQLite state, compaction hygiene, and legacy cleanup:
449
+
450
+ ```javascript
451
+ smart_doctor({})
452
+ smart_doctor({ verifyIntegrity: false })
453
+ ```
454
+
455
+ CLI:
456
+
457
+ ```bash
458
+ smart-context-doctor --json
459
+ smart-context-doctor --no-integrity
460
+ ```
344
461
 
345
462
  ### smart_status
346
463
 
@@ -352,6 +469,17 @@ Display current session context:
352
469
  ```
353
470
 
354
471
  Shows goal, status, recent decisions, touched files, and progress. Updates automatically with each MCP operation.
472
+ When repo safety or SQLite health blocks normal state access, `smart_status` still exposes the same safety contract plus `storageHealth`.
473
+
474
+ ### SQLite Recovery
475
+
476
+ If `.devctx/state.sqlite` is unhealthy, use the surfaced `storageHealth.issue`:
477
+
478
+ - `missing`: initialize local state with a persisted action
479
+ - `oversized`: run `smart_summary compact`
480
+ - `locked`: stop competing devctx writers, then retry
481
+ - `corrupted`: back up and remove the file so devctx can recreate it
482
+ - broader inspection: run `smart_doctor` / `smart-context-doctor`
355
483
 
356
484
  ### smart_edit
357
485
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-context-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
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",
@@ -14,9 +14,11 @@
14
14
  },
15
15
  "bin": {
16
16
  "smart-context-headless": "scripts/headless-wrapper.js",
17
+ "smart-context-task": "scripts/task-runner.js",
17
18
  "smart-context-server": "scripts/devctx-server.js",
18
19
  "smart-context-init": "scripts/init-clients.js",
19
20
  "smart-context-report": "scripts/report-metrics.js",
21
+ "smart-context-doctor": "scripts/doctor-state.js",
20
22
  "smart-context-protect": "scripts/check-repo-safety.js"
21
23
  },
22
24
  "exports": {
@@ -27,11 +29,14 @@
27
29
  "src/",
28
30
  "scripts/claude-hook.js",
29
31
  "scripts/check-repo-safety.js",
32
+ "scripts/doctor-state.js",
30
33
  "scripts/devctx-server.js",
31
34
  "scripts/headless-wrapper.js",
32
35
  "scripts/init-clients.js",
36
+ "scripts/task-runner.js",
33
37
  "scripts/report-metrics.js",
34
- "scripts/report-workflow-metrics.js"
38
+ "scripts/report-workflow-metrics.js",
39
+ "scripts/report-adoption-metrics.js"
35
40
  ],
36
41
  "engines": {
37
42
  "node": ">=18"
@@ -54,13 +59,17 @@
54
59
  "test": "node --test --test-concurrency=1 ./tests/*.test.js",
55
60
  "verify": "node ./scripts/verify-features-direct.js",
56
61
  "benchmark": "node ./scripts/run-benchmark.js",
62
+ "benchmark:orchestration": "node ./evals/orchestration-benchmark.js",
63
+ "benchmark:orchestration:release": "node ./evals/orchestration-benchmark.js --baseline=./evals/orchestration-release-baseline.json",
57
64
  "eval": "node ./evals/harness.js",
58
65
  "eval:context": "node ./evals/harness.js --tool=context",
59
66
  "eval:both": "node ./evals/harness.js --tool=both",
60
67
  "eval:self": "node ./evals/harness.js --root=../.. --corpus=./evals/corpus/self-tasks.json",
61
68
  "eval:report": "node ./evals/report.js",
62
69
  "report:metrics": "node ./scripts/report-metrics.js",
63
- "report:workflows": "node ./scripts/report-workflow-metrics.js"
70
+ "report:workflows": "node ./scripts/report-workflow-metrics.js",
71
+ "report:adoption": "node ./scripts/report-adoption-metrics.js",
72
+ "prepublishOnly": "npm test && npm run benchmark:orchestration:release"
64
73
  },
65
74
  "dependencies": {
66
75
  "@modelcontextprotocol/sdk": "^1.13.0",
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { smartDoctor } from '../src/tools/smart-doctor.js';
5
+ import { setProjectRoot } from '../src/utils/runtime-config.js';
6
+
7
+ const writeStdout = (text) => {
8
+ fs.writeSync(process.stdout.fd, text);
9
+ };
10
+
11
+ const writeStderr = (text) => {
12
+ fs.writeSync(process.stderr.fd, text);
13
+ };
14
+
15
+ const parseArgs = (argv) => {
16
+ const options = {
17
+ projectRoot: process.cwd(),
18
+ json: false,
19
+ verifyIntegrity: true,
20
+ };
21
+
22
+ for (let index = 0; index < argv.length; index += 1) {
23
+ const token = argv[index];
24
+
25
+ if (token === '--project-root') {
26
+ const next = argv[index + 1];
27
+ if (!next || next.startsWith('--')) {
28
+ throw new Error('Missing value for --project-root');
29
+ }
30
+ options.projectRoot = path.resolve(next);
31
+ index += 1;
32
+ continue;
33
+ }
34
+
35
+ if (token === '--json') {
36
+ options.json = true;
37
+ continue;
38
+ }
39
+
40
+ if (token === '--no-integrity') {
41
+ options.verifyIntegrity = false;
42
+ continue;
43
+ }
44
+
45
+ throw new Error(`Unknown argument: ${token}`);
46
+ }
47
+
48
+ return options;
49
+ };
50
+
51
+ const printHuman = (result) => {
52
+ const writer = result.overall === 'error' ? writeStderr : writeStdout;
53
+ writer(`devctx doctor: ${result.overall}\n`);
54
+ writer(`${result.message}\n`);
55
+
56
+ for (const check of result.checks ?? []) {
57
+ writer(`\n[${check.status}] ${check.id}: ${check.message}\n`);
58
+ for (const action of check.recommendedActions ?? []) {
59
+ writer(`- ${action}\n`);
60
+ }
61
+ }
62
+ };
63
+
64
+ const main = async () => {
65
+ const options = parseArgs(process.argv.slice(2));
66
+ setProjectRoot(options.projectRoot);
67
+
68
+ const result = await smartDoctor({
69
+ verifyIntegrity: options.verifyIntegrity,
70
+ });
71
+
72
+ if (options.json) {
73
+ const output = `${JSON.stringify(result, null, 2)}\n`;
74
+ if (result.overall === 'error') {
75
+ writeStderr(output);
76
+ } else {
77
+ writeStdout(output);
78
+ }
79
+ } else {
80
+ printHuman(result);
81
+ }
82
+
83
+ process.exitCode = result.overall === 'error' ? 1 : 0;
84
+ };
85
+
86
+ main().catch((error) => {
87
+ writeStderr(`${error.message}\n`);
88
+ process.exitCode = 1;
89
+ });
@@ -3,6 +3,7 @@ import { execFileSync } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { CLIENT_CONTRACT_RULE_LINES } from '../src/client-contract.js';
6
7
 
7
8
  const currentFilePath = fileURLToPath(import.meta.url);
8
9
  const scriptsDir = path.dirname(currentFilePath);
@@ -164,6 +165,35 @@ const updateCursorConfig = (targetDir, serverConfig, dryRun) => {
164
165
  writeFile(filePath, `${JSON.stringify(current, null, 2)}\n`, dryRun);
165
166
  };
166
167
 
168
+ const buildCursorAssistedLauncher = (targetDir) => {
169
+ const runnerScript = normalizeCommandPath(path.relative(targetDir, path.join(devctxDir, 'scripts', 'task-runner.js')));
170
+ return `#!/bin/sh
171
+ set -eu
172
+
173
+ script_dir="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
174
+ project_root="$(CDPATH= cd -- "$script_dir/../.." && pwd)"
175
+
176
+ export DEVCTX_PROJECT_ROOT="$project_root"
177
+
178
+ if [ "$#" -gt 0 ] && [ "\${1#-}" = "$1" ]; then
179
+ subcommand="$1"
180
+ shift
181
+ exec "${process.execPath}" "$project_root/${runnerScript}" "$subcommand" --client cursor "$@"
182
+ fi
183
+
184
+ exec "${process.execPath}" "$project_root/${runnerScript}" task --client cursor "$@"
185
+ `;
186
+ };
187
+
188
+ const updateCursorAssistedLauncher = (targetDir, dryRun) => {
189
+ const filePath = path.join(targetDir, '.devctx', 'bin', 'cursor-devctx');
190
+ writeFile(filePath, buildCursorAssistedLauncher(targetDir), dryRun);
191
+
192
+ if (!dryRun) {
193
+ fs.chmodSync(filePath, 0o755);
194
+ }
195
+ };
196
+
167
197
  const updateClaudeConfig = (targetDir, serverConfig, dryRun) => {
168
198
  const filePath = path.join(targetDir, '.mcp.json');
169
199
  const current = readJson(filePath, { mcpServers: {} });
@@ -375,15 +405,25 @@ const updatePreCommitHook = (targetDir, dryRun) => {
375
405
  // Agent rules — instruct agents to prefer devctx tools over built-in ones
376
406
  // ---------------------------------------------------------------------------
377
407
 
378
- const agentRuleBody = `**First time in project?** Run build_index to enable search/context quality.
408
+ const agentRuleBody = `**First time in project?** Run build_index(incremental=true) to enable search/context quality.
379
409
 
380
410
  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
411
+ - smart_turn(start, userPrompt, ensureSession=true) before multi-step work
412
+ - smart_context(...) or smart_search(intent=...) to build context cheaply
413
+ - smart_read(outline|signatures|symbol) before full reads
414
+ - smart_shell instead of Shell for safe diagnostics
415
+ - smart_turn(end, event=milestone) after meaningful progress
416
+
417
+ If devctx MCP is installed and enabled, default to devctx on every non-trivial multi-file or multi-step task. Skip it only for genuinely trivial work, and then state why it was skipped.
418
+
419
+ Client contract:
420
+ - ${CLIENT_CONTRACT_RULE_LINES.join('\n- ')}
385
421
 
386
- For non-trivial tasks: smart_turn(start) → [work with devctx tools] → smart_turn(end)
422
+ Cursor assisted mode:
423
+ - For long or continuity-sensitive tasks, prefer the local launcher \`./.devctx/bin/cursor-devctx\`
424
+ - Usage: \`./.devctx/bin/cursor-devctx task --prompt "your task" -- <agent-command> [args...]\`
425
+ - Specialized flows: \`review\`, \`debug\`, \`refactor\`, \`test\`, \`doctor\`, \`status\`, \`checkpoint\`, \`cleanup\`
426
+ - This launcher wraps the prompt with smart_turn(start/end) orchestration and the shared operational contract
387
427
 
388
428
  Reading cascade: outline → signatures → symbol → full (last resort)
389
429
 
@@ -393,7 +433,7 @@ Detailed workflows: .cursor/rules/profiles-compact/ (debugging, code-review, ref
393
433
 
394
434
  If you didn't use devctx tools in a non-trivial programming task, add at the end:
395
435
 
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)"`;
436
+ **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, userPrompt, ensureSession=true) → smart_context/smart_search → smart_read → smart_turn(end, event=milestone)"`;
397
437
 
398
438
  const cursorRuleContent = `---
399
439
  description: Prefer devctx MCP tools for non-trivial tasks
@@ -515,6 +555,7 @@ const main = () => {
515
555
  if (clientSet.has('cursor')) {
516
556
  updateCursorConfig(targetDir, serverConfig, options.dryRun);
517
557
  updateCursorRule(targetDir, options.dryRun);
558
+ updateCursorAssistedLauncher(targetDir, options.dryRun);
518
559
  }
519
560
 
520
561
  if (clientSet.has('codex')) {
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Report adoption metrics - measures real MCP usage in non-trivial tasks
5
+ *
6
+ * Usage:
7
+ * npm run report:adoption
8
+ * npm run report:adoption -- --days 7
9
+ * npm run report:adoption -- --json
10
+ */
11
+
12
+ import { withStateDb } from '../src/storage/sqlite.js';
13
+ import { WORKFLOW_DEFINITIONS } from '../src/workflow-tracker.js';
14
+
15
+ const parseArgs = (argv) => {
16
+ const args = {};
17
+ for (let i = 2; i < argv.length; i++) {
18
+ if (argv[i] === '--days' && argv[i + 1]) {
19
+ args.days = parseInt(argv[i + 1], 10);
20
+ i++;
21
+ } else if (argv[i] === '--json') {
22
+ args.json = true;
23
+ }
24
+ }
25
+ return args;
26
+ };
27
+
28
+ const formatPct = (value) => `${value.toFixed(1)}%`;
29
+ const formatNumber = (value) => new Intl.NumberFormat('en-US').format(value);
30
+
31
+ /**
32
+ * Classify if a session represents a non-trivial task
33
+ */
34
+ const isNonTrivialTask = (sessionEvents, metricsEvents) => {
35
+ // Criteria 1: Multiple operations (≥5)
36
+ if (sessionEvents.length + metricsEvents.length >= 5) return true;
37
+
38
+ // Criteria 2: Large file reads (any file >500 lines)
39
+ const hasLargeFileRead = metricsEvents.some(
40
+ (m) => m.tool === 'Read' && m.raw_tokens > 1500 // ~500 lines
41
+ );
42
+ if (hasLargeFileRead) return true;
43
+
44
+ // Criteria 3: Multiple file reads (≥3)
45
+ const fileReads = metricsEvents.filter((m) => m.tool === 'Read' || m.tool === 'smart_read');
46
+ if (fileReads.length >= 3) return true;
47
+
48
+ // Criteria 4: Repeated searches (≥2)
49
+ const searches = metricsEvents.filter((m) => m.tool === 'Grep' || m.tool === 'smart_search');
50
+ if (searches.length >= 2) return true;
51
+
52
+ // Criteria 5: Workflow classification
53
+ const devctxTools = metricsEvents.filter((m) =>
54
+ ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell'].includes(m.tool)
55
+ );
56
+ if (devctxTools.length > 0) return true;
57
+
58
+ return false;
59
+ };
60
+
61
+ /**
62
+ * Check if session used devctx tools
63
+ */
64
+ const usedDevctx = (metricsEvents) => {
65
+ const devctxTools = ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell', 'smart_read_batch'];
66
+ return metricsEvents.some((m) => devctxTools.includes(m.tool));
67
+ };
68
+
69
+ /**
70
+ * Calculate adoption metrics
71
+ */
72
+ const calculateAdoptionMetrics = (days = 30) => {
73
+ return withStateDb((db) => {
74
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
75
+
76
+ // Get all sessions since cutoff
77
+ const sessions = db
78
+ .prepare(
79
+ `
80
+ SELECT session_id, snapshot_json, created_at
81
+ FROM sessions
82
+ WHERE created_at >= ?
83
+ ORDER BY created_at DESC
84
+ `
85
+ )
86
+ .all(cutoff);
87
+
88
+ const results = {
89
+ totalSessions: sessions.length,
90
+ nonTrivialTasks: 0,
91
+ tasksWithDevctx: 0,
92
+ adoptionRate: 0,
93
+ byWorkflow: {},
94
+ toolUsage: {},
95
+ };
96
+
97
+ // Initialize workflow stats
98
+ Object.keys(WORKFLOW_DEFINITIONS).forEach((type) => {
99
+ results.byWorkflow[type] = {
100
+ total: 0,
101
+ withDevctx: 0,
102
+ adoptionRate: 0,
103
+ };
104
+ });
105
+
106
+ // Analyze each session
107
+ sessions.forEach((session) => {
108
+ const snapshot = JSON.parse(session.snapshot_json || '{}');
109
+ const sessionId = session.session_id;
110
+
111
+ // Get events for this session
112
+ const sessionEvents = db
113
+ .prepare('SELECT * FROM session_events WHERE session_id = ?')
114
+ .all(sessionId);
115
+
116
+ const metricsEvents = db
117
+ .prepare('SELECT * FROM metrics_events WHERE session_id = ?')
118
+ .all(sessionId);
119
+
120
+ // Check if non-trivial
121
+ if (!isNonTrivialTask(sessionEvents, metricsEvents)) {
122
+ return;
123
+ }
124
+
125
+ results.nonTrivialTasks++;
126
+
127
+ // Check if used devctx
128
+ const hasDevctx = usedDevctx(metricsEvents);
129
+ if (hasDevctx) {
130
+ results.tasksWithDevctx++;
131
+ }
132
+
133
+ // Track tool usage
134
+ metricsEvents.forEach((m) => {
135
+ results.toolUsage[m.tool] = (results.toolUsage[m.tool] || 0) + 1;
136
+ });
137
+
138
+ // Classify by workflow if possible
139
+ const goal = snapshot.goal || '';
140
+ let workflowType = null;
141
+
142
+ for (const [type, def] of Object.entries(WORKFLOW_DEFINITIONS)) {
143
+ if (def.pattern.test(goal)) {
144
+ workflowType = type;
145
+ break;
146
+ }
147
+ }
148
+
149
+ if (workflowType) {
150
+ results.byWorkflow[workflowType].total++;
151
+ if (hasDevctx) {
152
+ results.byWorkflow[workflowType].withDevctx++;
153
+ }
154
+ }
155
+ });
156
+
157
+ // Calculate rates
158
+ if (results.nonTrivialTasks > 0) {
159
+ results.adoptionRate = (results.tasksWithDevctx / results.nonTrivialTasks) * 100;
160
+ }
161
+
162
+ Object.keys(results.byWorkflow).forEach((type) => {
163
+ const stats = results.byWorkflow[type];
164
+ if (stats.total > 0) {
165
+ stats.adoptionRate = (stats.withDevctx / stats.total) * 100;
166
+ }
167
+ });
168
+
169
+ return results;
170
+ });
171
+ };
172
+
173
+ /**
174
+ * Format and print report
175
+ */
176
+ const printReport = (metrics, days) => {
177
+ console.log(`\nAdoption Metrics (Last ${days} Days)`);
178
+ console.log('='.repeat(50));
179
+ console.log();
180
+
181
+ console.log(`Total Sessions: ${formatNumber(metrics.totalSessions)}`);
182
+ console.log(`Non-Trivial Tasks: ${formatNumber(metrics.nonTrivialTasks)}`);
183
+ console.log(`Tasks with devctx: ${formatNumber(metrics.tasksWithDevctx)}`);
184
+ console.log();
185
+
186
+ console.log(`Overall Adoption: ${formatPct(metrics.adoptionRate)}`);
187
+ console.log();
188
+
189
+ console.log('By Workflow:');
190
+ Object.entries(metrics.byWorkflow)
191
+ .filter(([, stats]) => stats.total > 0)
192
+ .sort((a, b) => b[1].adoptionRate - a[1].adoptionRate)
193
+ .forEach(([type, stats]) => {
194
+ const def = WORKFLOW_DEFINITIONS[type];
195
+ console.log(
196
+ ` ${def.name.padEnd(25)} ${formatPct(stats.adoptionRate).padStart(7)} (${stats.withDevctx}/${stats.total})`
197
+ );
198
+ });
199
+ console.log();
200
+
201
+ console.log('Top devctx Tools:');
202
+ const devctxTools = ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell'];
203
+ Object.entries(metrics.toolUsage)
204
+ .filter(([tool]) => devctxTools.includes(tool))
205
+ .sort((a, b) => b[1] - a[1])
206
+ .slice(0, 5)
207
+ .forEach(([tool, count]) => {
208
+ console.log(` ${tool.padEnd(20)} ${formatNumber(count)} uses`);
209
+ });
210
+ console.log();
211
+ };
212
+
213
+ // Main
214
+ const args = parseArgs(process.argv);
215
+ const days = args.days || 30;
216
+
217
+ try {
218
+ const metrics = calculateAdoptionMetrics(days);
219
+
220
+ if (args.json) {
221
+ console.log(JSON.stringify(metrics, null, 2));
222
+ } else {
223
+ printReport(metrics, days);
224
+ }
225
+ } catch (error) {
226
+ console.error('Error calculating adoption metrics:', error.message);
227
+ process.exit(1);
228
+ }
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { smartMetrics } from '../src/tools/smart-metrics.js';
5
5
  import { formatAdoptionReport } from '../src/analytics/adoption.js';
6
+ import { formatProductQualityReport } from '../src/analytics/product-quality.js';
6
7
 
7
8
  const requireValue = (argv, index, flag) => {
8
9
  const value = argv[index + 1];
@@ -60,6 +61,8 @@ export const createReport = async (options) => {
60
61
  toolFilter: options.tool,
61
62
  invalidLines: result.invalidLines,
62
63
  summary: result.summary,
64
+ adoption: result.adoption,
65
+ productQuality: result.productQuality,
63
66
  };
64
67
  };
65
68
 
@@ -73,6 +76,10 @@ const printHuman = (report) => {
73
76
  console.log(`Raw tokens: ${formatNumber(report.summary.rawTokens)}`);
74
77
  console.log(`Final tokens: ${formatNumber(report.summary.compressedTokens)}`);
75
78
  console.log(`Saved tokens: ${formatNumber(report.summary.savedTokens)} (${report.summary.savingsPct}%)`);
79
+ console.log(`Net saved: ${formatNumber(report.summary.netSavedTokens)} (${report.summary.netSavingsPct}%)`);
80
+ if (report.summary.overheadTokens > 0) {
81
+ console.log(`Overhead: ${formatNumber(report.summary.overheadTokens)} (${report.summary.overheadPctOfRaw}% of raw)`);
82
+ }
76
83
  if (report.invalidLines.length > 0) {
77
84
  console.log(`Invalid JSONL: ${report.invalidLines.join(', ')}`);
78
85
  }
@@ -86,13 +93,17 @@ const printHuman = (report) => {
86
93
 
87
94
  for (const tool of report.summary.tools) {
88
95
  console.log(
89
- ` ${tool.tool.padEnd(14)} count=${formatNumber(tool.count)} raw=${formatNumber(tool.rawTokens)} final=${formatNumber(tool.compressedTokens)} saved=${formatNumber(tool.savedTokens)} (${tool.savingsPct}%)`
96
+ ` ${tool.tool.padEnd(14)} count=${formatNumber(tool.count)} raw=${formatNumber(tool.rawTokens)} final=${formatNumber(tool.compressedTokens)} saved=${formatNumber(tool.savedTokens)} (${tool.savingsPct}%) net=${formatNumber(tool.netSavedTokens)} (${tool.netSavingsPct}%)`
90
97
  );
91
98
  }
92
99
 
93
100
  if (report.adoption) {
94
101
  console.log(formatAdoptionReport(report.adoption));
95
102
  }
103
+
104
+ if (report.productQuality?.turnsMeasured > 0) {
105
+ console.log(formatProductQualityReport(report.productQuality));
106
+ }
96
107
  };
97
108
 
98
109
  export const main = async () => {