smart-context-mcp 1.5.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
@@ -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,41 @@ 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
+
47
90
  ## 📊 Real Metrics
48
91
 
49
92
  **Production use on this project:**
@@ -167,11 +210,17 @@ Use devctx: smart_turn(start) → smart_context → smart_turn(end)
167
210
  - ✅ Rules **guide** the agent (not enforce)
168
211
  - ✅ Agent can use built-in tools when appropriate
169
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
170
217
 
171
218
  Check actual usage:
172
219
  - **Real-time feedback** - Enabled by default (disable with `export DEVCTX_SHOW_USAGE=false`)
173
220
  - `npm run report:metrics` - Tool-level savings + adoption analysis
174
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`
175
224
 
176
225
  ## What it does
177
226
 
@@ -392,6 +441,23 @@ Maintain task checkpoint:
392
441
  ```
393
442
 
394
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
+ ```
395
461
 
396
462
  ### smart_status
397
463
 
@@ -403,6 +469,17 @@ Display current session context:
403
469
  ```
404
470
 
405
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`
406
483
 
407
484
  ### smart_edit
408
485
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-context-mcp",
3
- "version": "1.5.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,9 +29,11 @@
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
38
  "scripts/report-workflow-metrics.js",
35
39
  "scripts/report-adoption-metrics.js"
@@ -55,6 +59,8 @@
55
59
  "test": "node --test --test-concurrency=1 ./tests/*.test.js",
56
60
  "verify": "node ./scripts/verify-features-direct.js",
57
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",
58
64
  "eval": "node ./evals/harness.js",
59
65
  "eval:context": "node ./evals/harness.js --tool=context",
60
66
  "eval:both": "node ./evals/harness.js --tool=both",
@@ -62,7 +68,8 @@
62
68
  "eval:report": "node ./evals/report.js",
63
69
  "report:metrics": "node ./scripts/report-metrics.js",
64
70
  "report:workflows": "node ./scripts/report-workflow-metrics.js",
65
- "report:adoption": "node ./scripts/report-adoption-metrics.js"
71
+ "report:adoption": "node ./scripts/report-adoption-metrics.js",
72
+ "prepublishOnly": "npm test && npm run benchmark:orchestration:release"
66
73
  },
67
74
  "dependencies": {
68
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')) {
@@ -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 () => {
@@ -116,14 +116,31 @@ const printSummary = (summary) => {
116
116
  const totalRaw = summary.reduce((sum, s) => sum + s.total_raw_tokens, 0);
117
117
  const totalCompressed = summary.reduce((sum, s) => sum + s.total_compressed_tokens, 0);
118
118
  const totalSaved = summary.reduce((sum, s) => sum + s.total_saved_tokens, 0);
119
+ const totalOverhead = summary.reduce((sum, s) => sum + (s.total_overhead_tokens || 0), 0);
120
+ const totalNetSaved = summary.reduce((sum, s) => sum + (s.total_net_saved_tokens || 0), 0);
121
+ const totalNetCoverage = summary.reduce(
122
+ (sum, s) => sum + (s.netMetricsCoverage?.coveredWorkflows ?? s.net_metrics_count ?? 0),
123
+ 0,
124
+ );
119
125
  const totalBaseline = summary.reduce((sum, s) => sum + s.total_baseline_tokens, 0);
126
+ const totalSavedPct = totalRaw > 0 ? ((totalSaved / totalRaw) * 100).toFixed(2) : '0.00';
127
+ const totalNetSavedPct = totalRaw > 0 ? ((totalNetSaved / totalRaw) * 100).toFixed(2) : '0.00';
128
+ const baselineSavingsPct = totalBaseline > 0
129
+ ? (((totalBaseline - totalCompressed) / totalBaseline) * 100).toFixed(2)
130
+ : '0.00';
120
131
 
121
132
  console.log(`Total Workflows: ${formatNumber(totalWorkflows)}`);
122
133
  console.log(`Total Raw Tokens: ${formatNumber(totalRaw)}`);
123
134
  console.log(`Total Compressed Tokens: ${formatNumber(totalCompressed)}`);
124
- console.log(`Total Saved Tokens: ${formatNumber(totalSaved)} (${((totalSaved / totalRaw) * 100).toFixed(2)}%)`);
135
+ console.log(`Total Saved Tokens: ${formatNumber(totalSaved)} (${totalSavedPct}%)`);
136
+ if (totalNetCoverage > 0) {
137
+ console.log(`Total Overhead Tokens: ${formatNumber(totalOverhead)}`);
138
+ console.log(
139
+ `Total Net Saved Tokens${totalNetCoverage < totalWorkflows ? ` (${formatNumber(totalNetCoverage)}/${formatNumber(totalWorkflows)} workflows)` : ''}: ${formatNumber(totalNetSaved)} (${totalNetSavedPct}%)`,
140
+ );
141
+ }
125
142
  console.log(`Total Baseline Tokens: ${formatNumber(totalBaseline)}`);
126
- console.log(`Savings vs Baseline: ${formatNumber(totalBaseline - totalCompressed)} (${(((totalBaseline - totalCompressed) / totalBaseline) * 100).toFixed(2)}%)`);
143
+ console.log(`Savings vs Baseline: ${formatNumber(totalBaseline - totalCompressed)} (${baselineSavingsPct}%)`);
127
144
  console.log('');
128
145
  console.log('By Workflow Type:');
129
146
  console.log('─'.repeat(120));
@@ -169,6 +186,13 @@ const printSummary = (summary) => {
169
186
  console.log(` Total Raw Tokens: ${formatNumber(s.total_raw_tokens)}`);
170
187
  console.log(` Total Compressed Tokens: ${formatNumber(s.total_compressed_tokens)}`);
171
188
  console.log(` Total Saved Tokens: ${formatNumber(s.total_saved_tokens)} (${s.avgSavingsPct}%)`);
189
+ const coveredWorkflows = s.netMetricsCoverage?.coveredWorkflows ?? s.net_metrics_count ?? 0;
190
+ if (coveredWorkflows > 0) {
191
+ console.log(` Total Overhead Tokens: ${formatNumber(s.total_overhead_tokens || 0)}`);
192
+ console.log(
193
+ ` Total Net Saved Tokens${coveredWorkflows < s.count ? ` (${formatNumber(coveredWorkflows)}/${formatNumber(s.count)} workflows)` : ''}: ${formatNumber(s.total_net_saved_tokens || 0)}`,
194
+ );
195
+ }
172
196
  console.log(` Baseline Tokens: ${formatNumber(s.total_baseline_tokens)}`);
173
197
  console.log(` Savings vs Baseline: ${formatNumber(s.total_baseline_tokens - s.total_compressed_tokens)} (${s.avgVsBaselinePct}%)`);
174
198
  console.log('');
@@ -205,6 +229,15 @@ const printWorkflows = (workflows) => {
205
229
  console.log(` Raw Tokens: ${formatNumber(w.raw_tokens)}`);
206
230
  console.log(` Compressed Tokens: ${formatNumber(w.compressed_tokens)}`);
207
231
  console.log(` Saved Tokens: ${formatNumber(w.saved_tokens)} (${w.savings_pct}%)`);
232
+ if (w.overheadTokens !== undefined) {
233
+ console.log(` Overhead Tokens: ${formatNumber(w.overheadTokens)}`);
234
+ }
235
+ if (w.netSavedTokens !== undefined) {
236
+ console.log(` Net Saved Tokens: ${formatNumber(w.netSavedTokens)}`);
237
+ }
238
+ if (w.netMetricsCoverage?.available === false) {
239
+ console.log(' Net Metrics Coverage: unavailable');
240
+ }
208
241
  console.log(` Baseline Tokens: ${formatNumber(w.baseline_tokens)}`);
209
242
  console.log(` Savings vs Baseline: ${formatNumber(w.baseline_tokens - w.compressed_tokens)} (${w.vs_baseline_pct}%)`);
210
243
  }