ctxo-mcp 0.4.0 → 0.4.2
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 +58 -19
- package/dist/{chunk-BMB6SQIR.js → chunk-WZKXGKKI.js} +151 -10
- package/dist/chunk-WZKXGKKI.js.map +1 -0
- package/dist/{cli-router-Z5CSD6QN.js → cli-router-H557TJAM.js} +165 -2
- package/dist/cli-router-H557TJAM.js.map +1 -0
- package/dist/index.js +79 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-BMB6SQIR.js.map +0 -1
- package/dist/cli-router-Z5CSD6QN.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
[](https://github.com/alperhankendi/Ctxo/actions/workflows/ci.yml)
|
|
5
5
|
[](https://github.com/alperhankendi/Ctxo/actions/workflows/release.yml)
|
|
6
6
|
|
|
7
|
+
**Code intelligence for AI agents one call instead of hundreds.**
|
|
8
|
+
|
|
7
9
|
<picture>
|
|
8
10
|
<source media="(prefers-color-scheme: dark)" srcset="docs/img/hero-svg.svg">
|
|
9
11
|
<source media="(prefers-color-scheme: light)" srcset="docs/img/hero-svg.svg">
|
|
@@ -16,24 +18,24 @@
|
|
|
16
18
|
|
|
17
19
|
### The Problem
|
|
18
20
|
|
|
19
|
-
AI coding assistants like Copilot, Claude Code, and Cursor rely on generic tools
|
|
21
|
+
AI coding assistants like Copilot, Claude Code, and Cursor rely on generic tools `grep`, `find`, file reads to understand your codebase. On brownfield projects with thousands of files, this brute-force exploration creates a chain of problems:
|
|
20
22
|
|
|
21
|
-
* **Context window saturation**
|
|
22
|
-
* **Partial context hallucination**
|
|
23
|
-
* **Lost-in-the-middle**
|
|
24
|
-
* **Context poisoning**
|
|
25
|
-
* **Iteration overhead**
|
|
26
|
-
* **Stale reasoning**
|
|
23
|
+
* **Context window saturation** The agent fills its window reading files one by one, leaving little room for actual reasoning
|
|
24
|
+
* **Partial context hallucination** It sees a function but misses its dependencies, leading to wrong assumptions and broken suggestions
|
|
25
|
+
* **Lost-in-the-middle** Critical information buried deep in a long context gets ignored by the model
|
|
26
|
+
* **Context poisoning** Irrelevant code pulled in during exploration biases the model's output
|
|
27
|
+
* **Iteration overhead** Understanding one symbol takes 10-20 tool calls, each adding more noise to the context
|
|
28
|
+
* **Stale reasoning** After too many iterations, the agent contradicts its own earlier assumptions
|
|
27
29
|
|
|
28
30
|
The result: more tokens burned, slower responses, higher cost, and lower quality output.
|
|
29
31
|
|
|
30
32
|
### The Solution
|
|
31
33
|
|
|
32
|
-
Ctxo is an **MCP server** that **enhances** your existing AI tools with dependency-aware, history-enriched code intelligence. Instead of hundreds of `grep` and `read_file` calls, your agent gets the full picture
|
|
34
|
+
Ctxo is an **MCP server** that **enhances** your existing AI tools with dependency-aware, history-enriched code intelligence. Instead of hundreds of `grep` and `read_file` calls, your agent gets the full picture symbol graphs, blast radius, git intent, and risk scores in a **single MCP call**.
|
|
33
35
|
|
|
34
|
-
* **Clean context**
|
|
35
|
-
* **Fewer iterations**
|
|
36
|
-
* **Higher quality**
|
|
36
|
+
* **Clean context** Only relevant symbols and their transitive dependencies, nothing more
|
|
37
|
+
* **Fewer iterations** One call replaces an entire exploration cycle
|
|
38
|
+
* **Higher quality** The agent reasons over structured, complete context instead of fragmented file reads
|
|
37
39
|
|
|
38
40
|
> A senior developer takes \~10 minutes to gather context across files. Ctxo delivers that same context in **under 500ms**.
|
|
39
41
|
|
|
@@ -112,19 +114,55 @@ npx ctxo-mcp watch # File watcher for incremental re-index
|
|
|
112
114
|
npx ctxo-mcp init # Install git hooks (post-commit, post-merge)
|
|
113
115
|
npx ctxo-mcp status # Show index manifest
|
|
114
116
|
npx ctxo-mcp sync # Rebuild SQLite cache from committed JSON
|
|
117
|
+
npx ctxo-mcp stats # Show usage statistics
|
|
118
|
+
npx ctxo-mcp stats --json # Machine-readable output
|
|
119
|
+
npx ctxo-mcp stats --days 7 # Last 7 days only
|
|
120
|
+
npx ctxo-mcp stats --clear # Delete all session data
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Example output:**
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
npx ctxo-mcp stats
|
|
127
|
+
|
|
128
|
+
Usage Summary (all time)
|
|
129
|
+
────────────────────────────────────────
|
|
130
|
+
Total tool calls: 30
|
|
131
|
+
Total tokens served: 26.0K
|
|
132
|
+
|
|
133
|
+
Top Tools
|
|
134
|
+
────────────────────────────────────────
|
|
135
|
+
get_logic_slice 14 calls avg 352 tokens
|
|
136
|
+
get_blast_radius 3 calls avg 1,279 tokens
|
|
137
|
+
get_ranked_context 3 calls avg 898 tokens
|
|
138
|
+
find_importers 2 calls avg 1,342 tokens
|
|
139
|
+
get_context_for_task 2 calls avg 740 tokens
|
|
140
|
+
|
|
141
|
+
Top Queried Symbols
|
|
142
|
+
────────────────────────────────────────
|
|
143
|
+
SymbolNode 15 queries
|
|
144
|
+
LogicSliceQuery 4 queries
|
|
145
|
+
SqliteStorageAdapter 2 queries
|
|
146
|
+
|
|
147
|
+
Detail Level Distribution
|
|
148
|
+
────────────────────────────────────────
|
|
149
|
+
L1: ███░░░░░░░ 25%
|
|
150
|
+
L2: ███░░░░░░░ 25%
|
|
151
|
+
L3: ███░░░░░░░ 25%
|
|
152
|
+
L4: ███░░░░░░░ 25%
|
|
115
153
|
```
|
|
116
154
|
|
|
117
155
|
## Features
|
|
118
156
|
|
|
119
|
-
**Response Envelope**
|
|
157
|
+
**Response Envelope** All responses include `_meta` with item counts, truncation info, and drill-in hints. Large results auto-truncated at 8KB (configurable via `CTXO_RESPONSE_LIMIT`).
|
|
120
158
|
|
|
121
|
-
**Intent Filtering**
|
|
159
|
+
**Intent Filtering** 4 tools accept `intent` parameter for keyword-based result filtering. `get_blast_radius(symbolId, intent: "test")` returns only test-related impacts.
|
|
122
160
|
|
|
123
|
-
**Tool Annotations**
|
|
161
|
+
**Tool Annotations** All tools declare `readOnlyHint: true`, `idempotentHint: true`, `openWorldHint: false` for safe auto-approval in agent frameworks.
|
|
124
162
|
|
|
125
|
-
**Privacy Masking**
|
|
163
|
+
**Privacy Masking** AWS keys, GCP service accounts, Azure connection strings, JWTs, private IPs, env secrets automatically redacted. Extensible via `.ctxo/masking.json`.
|
|
126
164
|
|
|
127
|
-
**Debug Mode**
|
|
165
|
+
**Debug Mode** `DEBUG=ctxo:*` for all debug output, or `DEBUG=ctxo:git,ctxo:storage` for specific namespaces.
|
|
128
166
|
|
|
129
167
|
**Per-tool savings vs manual approach:**
|
|
130
168
|
|
|
@@ -180,7 +218,7 @@ See [Agentic AI Integration Guide](docs/agentic-ai-integration.md) for LangChain
|
|
|
180
218
|
|
|
181
219
|
## Index Visualizer
|
|
182
220
|
|
|
183
|
-
Ctxo ships with an interactive visualizer that renders your codebase index as a dependency graph. Explore symbols, edges, layers, and PageRank scores visually
|
|
221
|
+
Ctxo ships with an interactive visualizer that renders your codebase index as a dependency graph. Explore symbols, edges, layers, and PageRank scores visually deployed automatically to GitHub Pages on every push.
|
|
184
222
|
|
|
185
223
|

|
|
186
224
|
|
|
@@ -190,7 +228,7 @@ Ctxo ships with an interactive visualizer that renders your codebase index as a
|
|
|
190
228
|
|
|
191
229
|
<img src="docs/img/mcp-server.png" alt="Ctxo MCP Server Architecture" width="400">
|
|
192
230
|
|
|
193
|
-
Ctxo builds a **committed JSON index** (`.ctxo/index/`) that captures symbols, dependency edges, git history, and co-change data. The MCP server reads this index to answer queries
|
|
231
|
+
Ctxo builds a **committed JSON index** (`.ctxo/index/`) that captures symbols, dependency edges, git history, and co-change data. The MCP server reads this index to answer queries no runtime parsing, no external services.
|
|
194
232
|
|
|
195
233
|
```
|
|
196
234
|
.ctxo/
|
|
@@ -205,7 +243,8 @@ Ctxo builds a **committed JSON index** (`.ctxo/index/`) that captures symbols, d
|
|
|
205
243
|
* [npm](https://www.npmjs.com/package/ctxo-mcp)
|
|
206
244
|
* [Changelog](CHANGELOG.md)
|
|
207
245
|
* [LLM Reference](llms-full.txt)
|
|
208
|
-
* [Validation Runbook](docs/runbook/mcp-validation/mcp-validation.md)
|
|
246
|
+
* [MCP Validation Runbook](docs/runbook/mcp-validation/mcp-validation.md)
|
|
247
|
+
* [CLI Validation Runbook](docs/runbook/cli-validation/cli-validation.md)
|
|
209
248
|
* [Architecture](docs/artifacts/architecture.md)
|
|
210
249
|
* [PRD](docs/artifacts/prd.md)
|
|
211
250
|
|
|
@@ -68,15 +68,29 @@ var SqliteStorageAdapter = class {
|
|
|
68
68
|
}
|
|
69
69
|
async initEmpty() {
|
|
70
70
|
const SQL = await initSqlJs();
|
|
71
|
-
this.
|
|
71
|
+
if (existsSync(this.dbPath)) {
|
|
72
|
+
try {
|
|
73
|
+
const buffer = readFileSync(this.dbPath);
|
|
74
|
+
this.db = new SQL.Database(buffer);
|
|
75
|
+
} catch {
|
|
76
|
+
this.db = new SQL.Database();
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
this.db = new SQL.Database();
|
|
80
|
+
}
|
|
72
81
|
this.createTables();
|
|
82
|
+
this.clearIndexTables();
|
|
73
83
|
}
|
|
74
|
-
|
|
84
|
+
/** Returns the underlying sql.js Database instance. Throws if not initialized. */
|
|
85
|
+
getDb() {
|
|
75
86
|
if (!this.db) {
|
|
76
87
|
throw new Error("SqliteStorageAdapter not initialized. Call init() first.");
|
|
77
88
|
}
|
|
78
89
|
return this.db;
|
|
79
90
|
}
|
|
91
|
+
database() {
|
|
92
|
+
return this.getDb();
|
|
93
|
+
}
|
|
80
94
|
verifyIntegrity() {
|
|
81
95
|
const db = this.database();
|
|
82
96
|
const result = db.exec("PRAGMA integrity_check");
|
|
@@ -121,6 +135,12 @@ var SqliteStorageAdapter = class {
|
|
|
121
135
|
db.run("CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_symbol)");
|
|
122
136
|
db.run("CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_symbol)");
|
|
123
137
|
}
|
|
138
|
+
clearIndexTables() {
|
|
139
|
+
const db = this.database();
|
|
140
|
+
db.run("DELETE FROM edges");
|
|
141
|
+
db.run("DELETE FROM symbols");
|
|
142
|
+
db.run("DELETE FROM files");
|
|
143
|
+
}
|
|
124
144
|
rebuildFromJson() {
|
|
125
145
|
const reader = new JsonIndexReader(this.ctxoRoot);
|
|
126
146
|
const indices = reader.readAll();
|
|
@@ -371,12 +391,12 @@ var SimpleGitAdapter = class {
|
|
|
371
391
|
}
|
|
372
392
|
async getCommitHistory(filePath, maxCount) {
|
|
373
393
|
try {
|
|
374
|
-
const
|
|
394
|
+
const log4 = await this.git.log({
|
|
375
395
|
file: filePath,
|
|
376
396
|
"--follow": null,
|
|
377
397
|
...maxCount ? { maxCount } : {}
|
|
378
398
|
});
|
|
379
|
-
return
|
|
399
|
+
return log4.all.map((entry) => ({
|
|
380
400
|
hash: entry.hash,
|
|
381
401
|
message: entry.message,
|
|
382
402
|
date: entry.date,
|
|
@@ -389,9 +409,9 @@ var SimpleGitAdapter = class {
|
|
|
389
409
|
}
|
|
390
410
|
async getBatchHistory(maxCount = 20) {
|
|
391
411
|
try {
|
|
392
|
-
const
|
|
412
|
+
const log4 = await this.git.log({ maxCount: maxCount * 50, "--name-only": null });
|
|
393
413
|
const result = /* @__PURE__ */ new Map();
|
|
394
|
-
for (const entry of
|
|
414
|
+
for (const entry of log4.all) {
|
|
395
415
|
const record = {
|
|
396
416
|
hash: entry.hash,
|
|
397
417
|
message: entry.message,
|
|
@@ -428,10 +448,10 @@ var SimpleGitAdapter = class {
|
|
|
428
448
|
}
|
|
429
449
|
async getFileChurn(filePath) {
|
|
430
450
|
try {
|
|
431
|
-
const
|
|
451
|
+
const log4 = await this.git.log({ file: filePath, "--follow": null });
|
|
432
452
|
return {
|
|
433
453
|
filePath,
|
|
434
|
-
commitCount:
|
|
454
|
+
commitCount: log4.total
|
|
435
455
|
};
|
|
436
456
|
} catch (err) {
|
|
437
457
|
log2.error(` Failed to get churn for ${filePath}: ${err.message}`);
|
|
@@ -440,6 +460,126 @@ var SimpleGitAdapter = class {
|
|
|
440
460
|
}
|
|
441
461
|
};
|
|
442
462
|
|
|
463
|
+
// src/adapters/stats/session-recorder-adapter.ts
|
|
464
|
+
var log3 = createLogger("ctxo:stats");
|
|
465
|
+
var SessionRecorderAdapter = class {
|
|
466
|
+
db;
|
|
467
|
+
onWrite;
|
|
468
|
+
tableCreated = false;
|
|
469
|
+
constructor(db, onWrite) {
|
|
470
|
+
this.db = db;
|
|
471
|
+
this.onWrite = onWrite ?? null;
|
|
472
|
+
}
|
|
473
|
+
ensureTable() {
|
|
474
|
+
if (this.tableCreated) return;
|
|
475
|
+
this.db.run(`
|
|
476
|
+
CREATE TABLE IF NOT EXISTS session_events (
|
|
477
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
478
|
+
timestamp TEXT DEFAULT (datetime('now')),
|
|
479
|
+
tool TEXT NOT NULL,
|
|
480
|
+
symbol_id TEXT,
|
|
481
|
+
detail_level TEXT,
|
|
482
|
+
response_tokens INTEGER,
|
|
483
|
+
response_bytes INTEGER,
|
|
484
|
+
latency_ms REAL,
|
|
485
|
+
truncated BOOLEAN DEFAULT 0
|
|
486
|
+
)
|
|
487
|
+
`);
|
|
488
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_session_timestamp ON session_events(timestamp)");
|
|
489
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_session_tool ON session_events(tool)");
|
|
490
|
+
this.tableCreated = true;
|
|
491
|
+
}
|
|
492
|
+
record(event) {
|
|
493
|
+
try {
|
|
494
|
+
this.ensureTable();
|
|
495
|
+
this.db.run(
|
|
496
|
+
`INSERT INTO session_events (tool, symbol_id, detail_level, response_tokens, response_bytes, latency_ms, truncated)
|
|
497
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
498
|
+
[
|
|
499
|
+
event.tool,
|
|
500
|
+
event.symbolId,
|
|
501
|
+
event.detailLevel,
|
|
502
|
+
event.responseTokens,
|
|
503
|
+
event.responseBytes,
|
|
504
|
+
event.latencyMs,
|
|
505
|
+
event.truncated ? 1 : 0
|
|
506
|
+
]
|
|
507
|
+
);
|
|
508
|
+
this.flushToDisk();
|
|
509
|
+
} catch (err) {
|
|
510
|
+
log3.error(`Failed to record session event: ${err.message}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
queryStats(days) {
|
|
514
|
+
this.ensureTable();
|
|
515
|
+
const whereClause = days != null && days > 0 ? `WHERE timestamp >= datetime('now', '-${Math.floor(days)} days')` : "";
|
|
516
|
+
const summaryResult = this.db.exec(
|
|
517
|
+
`SELECT COUNT(*) as total_calls, COALESCE(SUM(response_tokens), 0) as total_tokens
|
|
518
|
+
FROM session_events ${whereClause}`
|
|
519
|
+
);
|
|
520
|
+
const summaryRow = summaryResult[0]?.values[0];
|
|
521
|
+
const totalCalls = Number(summaryRow?.[0] ?? 0);
|
|
522
|
+
const totalTokensServed = Number(summaryRow?.[1] ?? 0);
|
|
523
|
+
const toolsResult = this.db.exec(
|
|
524
|
+
`SELECT tool, COUNT(*) as calls, CAST(AVG(response_tokens) AS INTEGER) as avg_tokens
|
|
525
|
+
FROM session_events ${whereClause}
|
|
526
|
+
GROUP BY tool ORDER BY calls DESC LIMIT 5`
|
|
527
|
+
);
|
|
528
|
+
const topTools = (toolsResult[0]?.values ?? []).map((row) => ({
|
|
529
|
+
tool: String(row[0]),
|
|
530
|
+
calls: Number(row[1]),
|
|
531
|
+
avgTokens: Number(row[2])
|
|
532
|
+
}));
|
|
533
|
+
const symbolWherePrefix = whereClause ? whereClause + " AND" : "WHERE";
|
|
534
|
+
const symbolsResult = this.db.exec(
|
|
535
|
+
`SELECT symbol_id, COUNT(*) as queries
|
|
536
|
+
FROM session_events ${symbolWherePrefix} symbol_id IS NOT NULL
|
|
537
|
+
GROUP BY symbol_id ORDER BY queries DESC LIMIT 5`
|
|
538
|
+
);
|
|
539
|
+
const topSymbols = (symbolsResult[0]?.values ?? []).map((row) => {
|
|
540
|
+
const symbolId = String(row[0]);
|
|
541
|
+
const parts = symbolId.split("::");
|
|
542
|
+
const name = parts.length >= 2 ? parts[1] : symbolId;
|
|
543
|
+
return {
|
|
544
|
+
symbolId,
|
|
545
|
+
name,
|
|
546
|
+
queries: Number(row[1])
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
const levelResult = this.db.exec(
|
|
550
|
+
`SELECT detail_level, COUNT(*) as count
|
|
551
|
+
FROM session_events ${symbolWherePrefix} detail_level IS NOT NULL
|
|
552
|
+
GROUP BY detail_level ORDER BY detail_level`
|
|
553
|
+
);
|
|
554
|
+
const levelRows = levelResult[0]?.values ?? [];
|
|
555
|
+
const levelTotal = levelRows.reduce((sum, row) => sum + Number(row[1]), 0);
|
|
556
|
+
const detailLevelDistribution = levelRows.map((row) => ({
|
|
557
|
+
level: String(row[0]),
|
|
558
|
+
count: Number(row[1]),
|
|
559
|
+
percentage: levelTotal > 0 ? Math.round(Number(row[1]) / levelTotal * 100) : 0
|
|
560
|
+
}));
|
|
561
|
+
return {
|
|
562
|
+
totalCalls,
|
|
563
|
+
totalTokensServed,
|
|
564
|
+
topTools,
|
|
565
|
+
topSymbols,
|
|
566
|
+
detailLevelDistribution
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
clear() {
|
|
570
|
+
this.ensureTable();
|
|
571
|
+
this.db.run("DELETE FROM session_events");
|
|
572
|
+
this.flushToDisk();
|
|
573
|
+
}
|
|
574
|
+
flushToDisk() {
|
|
575
|
+
try {
|
|
576
|
+
this.onWrite?.();
|
|
577
|
+
} catch (err) {
|
|
578
|
+
log3.error(`Failed to flush stats to disk: ${err.message}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
443
583
|
// src/core/why-context/revert-detector.ts
|
|
444
584
|
var REVERT_QUOTED_PATTERN = /^Revert "(.+)"$/;
|
|
445
585
|
var REVERT_PREFIX_PATTERN = /^revert:\s*(.+)$/i;
|
|
@@ -556,6 +696,7 @@ export {
|
|
|
556
696
|
RevertDetector,
|
|
557
697
|
SimpleGitAdapter,
|
|
558
698
|
aggregateCoChanges,
|
|
559
|
-
loadCoChangeMap
|
|
699
|
+
loadCoChangeMap,
|
|
700
|
+
SessionRecorderAdapter
|
|
560
701
|
};
|
|
561
|
-
//# sourceMappingURL=chunk-
|
|
702
|
+
//# sourceMappingURL=chunk-WZKXGKKI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/logger.ts","../src/adapters/storage/sqlite-storage-adapter.ts","../src/adapters/git/simple-git-adapter.ts","../src/adapters/stats/session-recorder-adapter.ts","../src/core/why-context/revert-detector.ts","../src/core/co-change/co-change-analyzer.ts"],"sourcesContent":["/**\n * Structured logger for Ctxo — all output goes to stderr (stdout reserved for MCP JSON-RPC).\n *\n * Supports debug mode via DEBUG environment variable:\n * DEBUG=ctxo:* — all debug output\n * DEBUG=ctxo:git — only git adapter debug\n * DEBUG=ctxo:index — only indexing debug\n * DEBUG=ctxo:mcp — only MCP handler debug\n * DEBUG=ctxo:storage — only storage adapter debug\n * DEBUG=ctxo:masking — only masking pipeline debug\n *\n * Always outputs: info, warn, error (regardless of DEBUG).\n * Only outputs debug when matching namespace is enabled.\n */\n\nfunction isDebugEnabled(namespace: string): boolean {\n const debugEnv = process.env['DEBUG'] ?? '';\n if (!debugEnv) return false;\n const patterns = debugEnv.split(',').map(p => p.trim());\n return patterns.some(p => {\n if (p === '*' || p === 'ctxo:*') return true;\n if (p === namespace) return true;\n if (p.endsWith(':*') && namespace.startsWith(p.slice(0, -1))) return true;\n return false;\n });\n}\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\nexport function createLogger(namespace: string): Logger {\n const prefix = `[${namespace}]`;\n\n return {\n debug(message: string, ...args: unknown[]) {\n if (isDebugEnabled(namespace)) {\n console.error(`${prefix} DEBUG ${message}`, ...args);\n }\n },\n info(message: string, ...args: unknown[]) {\n console.error(`${prefix} ${message}`, ...args);\n },\n warn(message: string, ...args: unknown[]) {\n console.error(`${prefix} WARN ${message}`, ...args);\n },\n error(message: string, ...args: unknown[]) {\n console.error(`${prefix} ERROR ${message}`, ...args);\n },\n };\n}\n","import initSqlJs, { type Database } from 'sql.js';\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport type { FileIndex, GraphEdge, SymbolNode } from '../../core/types.js';\nimport type { IStoragePort } from '../../ports/i-storage-port.js';\nimport { JsonIndexReader } from './json-index-reader.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:storage');\n\nexport class SqliteStorageAdapter implements IStoragePort {\n private db: Database | undefined;\n private readonly dbPath: string;\n private readonly ctxoRoot: string;\n\n constructor(ctxoRoot: string) {\n this.ctxoRoot = ctxoRoot;\n this.dbPath = join(ctxoRoot, '.cache', 'symbols.db');\n }\n\n async init(): Promise<void> {\n const SQL = await initSqlJs();\n\n if (existsSync(this.dbPath)) {\n try {\n const buffer = readFileSync(this.dbPath);\n this.db = new SQL.Database(buffer);\n this.verifyIntegrity();\n } catch {\n log.warn('Corrupt DB detected, rebuilding from JSON index');\n this.db = new SQL.Database();\n this.createTables();\n }\n } else {\n this.db = new SQL.Database();\n this.createTables();\n }\n\n // Always rebuild from JSON — it is the source of truth\n this.rebuildFromJson();\n }\n\n async initEmpty(): Promise<void> {\n const SQL = await initSqlJs();\n if (existsSync(this.dbPath)) {\n try {\n const buffer = readFileSync(this.dbPath);\n this.db = new SQL.Database(buffer);\n } catch {\n this.db = new SQL.Database();\n }\n } else {\n this.db = new SQL.Database();\n }\n this.createTables();\n this.clearIndexTables();\n }\n\n /** Returns the underlying sql.js Database instance. Throws if not initialized. */\n getDb(): Database {\n if (!this.db) {\n throw new Error('SqliteStorageAdapter not initialized. Call init() first.');\n }\n return this.db;\n }\n\n private database(): Database {\n return this.getDb();\n }\n\n private verifyIntegrity(): void {\n const db = this.database();\n const result = db.exec('PRAGMA integrity_check');\n const firstRow = result[0]?.values[0];\n if (!firstRow || firstRow[0] !== 'ok') {\n throw new Error('SQLite integrity check failed');\n }\n\n const tables = db.exec(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('symbols', 'edges', 'files')\",\n );\n if (!tables[0] || tables[0].values.length < 3) {\n throw new Error('Missing required tables');\n }\n }\n\n private createTables(): void {\n const db = this.database();\n db.run(`\n CREATE TABLE IF NOT EXISTS files (\n file_path TEXT PRIMARY KEY,\n last_modified INTEGER NOT NULL\n )\n `);\n db.run(`\n CREATE TABLE IF NOT EXISTS symbols (\n symbol_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n kind TEXT NOT NULL,\n file_path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL\n )\n `);\n db.run(`\n CREATE TABLE IF NOT EXISTS edges (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n from_symbol TEXT NOT NULL,\n to_symbol TEXT NOT NULL,\n kind TEXT NOT NULL\n )\n `);\n db.run('CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path)');\n db.run('CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_symbol)');\n db.run('CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_symbol)');\n }\n\n private clearIndexTables(): void {\n const db = this.database();\n db.run('DELETE FROM edges');\n db.run('DELETE FROM symbols');\n db.run('DELETE FROM files');\n }\n\n private rebuildFromJson(): void {\n const reader = new JsonIndexReader(this.ctxoRoot);\n const indices = reader.readAll();\n\n if (indices.length > 0) {\n this.bulkWrite(indices);\n }\n }\n\n writeSymbolFile(fileIndex: FileIndex): void {\n const db = this.database();\n\n db.run('BEGIN TRANSACTION');\n try {\n this.deleteFileData(db, fileIndex.file);\n\n db.run(\n 'INSERT INTO files (file_path, last_modified) VALUES (?, ?)',\n [fileIndex.file, fileIndex.lastModified],\n );\n\n for (const sym of fileIndex.symbols) {\n db.run(\n 'INSERT OR REPLACE INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)',\n [sym.symbolId, sym.name, sym.kind, fileIndex.file, sym.startLine, sym.endLine],\n );\n }\n\n for (const edge of fileIndex.edges) {\n db.run(\n 'INSERT INTO edges (from_symbol, to_symbol, kind) VALUES (?, ?, ?)',\n [edge.from, edge.to, edge.kind],\n );\n }\n\n db.run('COMMIT');\n this.persistIfNeeded();\n } catch (err) {\n db.run('ROLLBACK');\n throw err;\n }\n }\n\n /**\n * Read symbol file from SQLite cache.\n * NOTE: SQLite only caches symbols + edges (graph topology).\n * intent, antiPatterns, and complexity live in committed JSON index only.\n * Use JsonIndexReader for full FileIndex data.\n */\n readSymbolFile(relativePath: string): FileIndex | undefined {\n const db = this.database();\n\n const fileResult = db.exec(\n 'SELECT file_path, last_modified FROM files WHERE file_path = ?',\n [relativePath],\n );\n if (!fileResult[0] || fileResult[0].values.length === 0) {\n return undefined;\n }\n\n const [filePath, lastModified] = fileResult[0].values[0] as [string, number];\n\n const symbols = this.getSymbolsForFile(db, filePath);\n const edges = this.getEdgesForFile(db, filePath);\n\n return {\n file: filePath,\n lastModified,\n symbols,\n edges,\n // Not stored in SQLite — use JsonIndexReader for these fields\n intent: [],\n antiPatterns: [],\n };\n }\n\n listIndexedFiles(): string[] {\n const db = this.database();\n const result = db.exec('SELECT file_path FROM files ORDER BY file_path');\n if (!result[0]) return [];\n return result[0].values.map((row) => row[0] as string);\n }\n\n deleteSymbolFile(relativePath: string): void {\n const db = this.database();\n this.deleteFileData(db, relativePath);\n }\n\n getSymbolById(symbolId: string): SymbolNode | undefined {\n const db = this.database();\n const result = db.exec(\n 'SELECT symbol_id, name, kind, start_line, end_line FROM symbols WHERE symbol_id = ?',\n [symbolId],\n );\n if (!result[0] || result[0].values.length === 0) {\n return undefined;\n }\n\n const [sid, name, kind, startLine, endLine] = result[0].values[0] as [string, string, string, number, number];\n return { symbolId: sid, name, kind: kind as SymbolNode['kind'], startLine, endLine };\n }\n\n getEdgesFrom(symbolId: string): GraphEdge[] {\n const db = this.database();\n const result = db.exec(\n 'SELECT from_symbol, to_symbol, kind FROM edges WHERE from_symbol = ?',\n [symbolId],\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n from: row[0] as string,\n to: row[1] as string,\n kind: row[2] as GraphEdge['kind'],\n }));\n }\n\n getEdgesTo(symbolId: string): GraphEdge[] {\n const db = this.database();\n const result = db.exec(\n 'SELECT from_symbol, to_symbol, kind FROM edges WHERE to_symbol = ?',\n [symbolId],\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n from: row[0] as string,\n to: row[1] as string,\n kind: row[2] as GraphEdge['kind'],\n }));\n }\n\n getAllSymbols(): SymbolNode[] {\n const db = this.database();\n const result = db.exec(\n 'SELECT symbol_id, name, kind, start_line, end_line FROM symbols',\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n symbolId: row[0] as string,\n name: row[1] as string,\n kind: row[2] as SymbolNode['kind'],\n startLine: row[3] as number,\n endLine: row[4] as number,\n }));\n }\n\n getAllEdges(): GraphEdge[] {\n const db = this.database();\n const result = db.exec(\n 'SELECT from_symbol, to_symbol, kind FROM edges',\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n from: row[0] as string,\n to: row[1] as string,\n kind: row[2] as GraphEdge['kind'],\n }));\n }\n\n bulkWrite(indices: FileIndex[]): void {\n const db = this.database();\n\n db.run('BEGIN TRANSACTION');\n try {\n for (const fileIndex of indices) {\n this.deleteFileData(db, fileIndex.file);\n\n db.run(\n 'INSERT INTO files (file_path, last_modified) VALUES (?, ?)',\n [fileIndex.file, fileIndex.lastModified],\n );\n\n for (const sym of fileIndex.symbols) {\n db.run(\n 'INSERT OR REPLACE INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)',\n [sym.symbolId, sym.name, sym.kind, fileIndex.file, sym.startLine, sym.endLine],\n );\n }\n\n for (const edge of fileIndex.edges) {\n db.run(\n 'INSERT INTO edges (from_symbol, to_symbol, kind) VALUES (?, ?, ?)',\n [edge.from, edge.to, edge.kind],\n );\n }\n }\n\n db.run('COMMIT');\n this.persistIfNeeded();\n } catch (err) {\n db.run('ROLLBACK');\n throw err;\n }\n }\n\n persist(): void {\n const db = this.database();\n const data = db.export();\n const buffer = Buffer.from(data);\n mkdirSync(dirname(this.dbPath), { recursive: true });\n writeFileSync(this.dbPath, buffer);\n }\n\n close(): void {\n if (this.db) {\n this.persist();\n this.db.close();\n this.db = undefined;\n }\n }\n\n private persistIfNeeded(): void {\n try {\n this.persist();\n } catch (err) {\n log.error(`Failed to persist DB: ${(err as Error).message}`);\n }\n }\n\n private deleteFileData(db: Database, filePath: string): void {\n // Delete edges originating from this file's symbols\n const symResult = db.exec(\n 'SELECT symbol_id FROM symbols WHERE file_path = ?',\n [filePath],\n );\n if (symResult[0]) {\n for (const row of symResult[0].values) {\n db.run('DELETE FROM edges WHERE from_symbol = ?', [row[0]]);\n }\n }\n db.run('DELETE FROM symbols WHERE file_path = ?', [filePath]);\n db.run('DELETE FROM files WHERE file_path = ?', [filePath]);\n }\n\n private getSymbolsForFile(db: Database, filePath: string): SymbolNode[] {\n const result = db.exec(\n 'SELECT symbol_id, name, kind, start_line, end_line FROM symbols WHERE file_path = ? ORDER BY start_line',\n [filePath],\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n symbolId: row[0] as string,\n name: row[1] as string,\n kind: row[2] as SymbolNode['kind'],\n startLine: row[3] as number,\n endLine: row[4] as number,\n }));\n }\n\n private getEdgesForFile(db: Database, filePath: string): GraphEdge[] {\n const result = db.exec(\n `SELECT e.from_symbol, e.to_symbol, e.kind FROM edges e\n INNER JOIN symbols s ON e.from_symbol = s.symbol_id\n WHERE s.file_path = ?`,\n [filePath],\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n from: row[0] as string,\n to: row[1] as string,\n kind: row[2] as GraphEdge['kind'],\n }));\n }\n}\n","import { simpleGit, type SimpleGit } from 'simple-git';\nimport type { IGitPort } from '../../ports/i-git-port.js';\nimport type { CommitRecord, ChurnData } from '../../core/types.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:git');\n\nexport class SimpleGitAdapter implements IGitPort {\n private readonly git: SimpleGit;\n\n constructor(projectRoot: string) {\n this.git = simpleGit(projectRoot);\n log.debug('Initialized with root: %s', projectRoot);\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await this.git.version();\n return true;\n } catch {\n return false;\n }\n }\n\n async getCommitHistory(filePath: string, maxCount?: number): Promise<CommitRecord[]> {\n try {\n const log = await this.git.log({\n file: filePath,\n '--follow': null,\n ...(maxCount ? { maxCount } : {}),\n });\n\n return log.all.map((entry) => ({\n hash: entry.hash,\n message: entry.message,\n date: entry.date,\n author: entry.author_name,\n }));\n } catch (err) {\n log.error(` Failed to get history for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async getBatchHistory(maxCount = 20): Promise<Map<string, CommitRecord[]>> {\n try {\n const log = await this.git.log({ maxCount: maxCount * 50, '--name-only': null });\n const result = new Map<string, CommitRecord[]>();\n\n for (const entry of log.all) {\n const record: CommitRecord = {\n hash: entry.hash,\n message: entry.message,\n date: entry.date,\n author: entry.author_name,\n };\n\n const diff = entry.diff;\n const files: string[] = diff?.files?.map((f: { file: string }) => f.file.replace(/\\\\/g, '/')) ?? [];\n\n for (const file of files) {\n let list = result.get(file);\n if (!list) { list = []; result.set(file, list); }\n if (list.length < maxCount) {\n list.push(record);\n }\n }\n }\n\n return result;\n } catch (err) {\n log.error(` Batch history failed: ${(err as Error).message}`);\n return new Map();\n }\n }\n\n async getChangedFiles(since: string): Promise<string[]> {\n try {\n const diff = await this.git.diffSummary([since]);\n return diff.files.map((f) => f.file.replace(/\\\\/g, '/'));\n } catch (err) {\n log.error(` Failed to get changed files since ${since}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async getFileChurn(filePath: string): Promise<ChurnData> {\n try {\n const log = await this.git.log({ file: filePath, '--follow': null });\n\n return {\n filePath,\n commitCount: log.total,\n };\n } catch (err) {\n log.error(` Failed to get churn for ${filePath}: ${(err as Error).message}`);\n return { filePath, commitCount: 0 };\n }\n }\n}\n","import type { Database } from 'sql.js';\nimport type {\n ISessionRecorderPort,\n SessionEvent,\n AggregatedStats,\n ToolStats,\n SymbolStats,\n DetailLevelStats,\n} from '../../ports/i-session-recorder-port.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:stats');\n\nexport class SessionRecorderAdapter implements ISessionRecorderPort {\n private readonly db: Database;\n private readonly onWrite: (() => void) | null;\n private tableCreated = false;\n\n constructor(db: Database, onWrite?: () => void) {\n this.db = db;\n this.onWrite = onWrite ?? null;\n }\n\n private ensureTable(): void {\n if (this.tableCreated) return;\n this.db.run(`\n CREATE TABLE IF NOT EXISTS session_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n timestamp TEXT DEFAULT (datetime('now')),\n tool TEXT NOT NULL,\n symbol_id TEXT,\n detail_level TEXT,\n response_tokens INTEGER,\n response_bytes INTEGER,\n latency_ms REAL,\n truncated BOOLEAN DEFAULT 0\n )\n `);\n this.db.run('CREATE INDEX IF NOT EXISTS idx_session_timestamp ON session_events(timestamp)');\n this.db.run('CREATE INDEX IF NOT EXISTS idx_session_tool ON session_events(tool)');\n this.tableCreated = true;\n }\n\n record(event: SessionEvent): void {\n try {\n this.ensureTable();\n this.db.run(\n `INSERT INTO session_events (tool, symbol_id, detail_level, response_tokens, response_bytes, latency_ms, truncated)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [\n event.tool,\n event.symbolId,\n event.detailLevel,\n event.responseTokens,\n event.responseBytes,\n event.latencyMs,\n event.truncated ? 1 : 0,\n ],\n );\n this.flushToDisk();\n } catch (err) {\n log.error(`Failed to record session event: ${(err as Error).message}`);\n }\n }\n\n queryStats(days?: number): AggregatedStats {\n this.ensureTable();\n\n const whereClause = days != null && days > 0\n ? `WHERE timestamp >= datetime('now', '-${Math.floor(days)} days')`\n : '';\n\n // Total calls and tokens\n const summaryResult = this.db.exec(\n `SELECT COUNT(*) as total_calls, COALESCE(SUM(response_tokens), 0) as total_tokens\n FROM session_events ${whereClause}`,\n );\n const summaryRow = summaryResult[0]?.values[0];\n const totalCalls = Number(summaryRow?.[0] ?? 0);\n const totalTokensServed = Number(summaryRow?.[1] ?? 0);\n\n // Top tools\n const toolsResult = this.db.exec(\n `SELECT tool, COUNT(*) as calls, CAST(AVG(response_tokens) AS INTEGER) as avg_tokens\n FROM session_events ${whereClause}\n GROUP BY tool ORDER BY calls DESC LIMIT 5`,\n );\n const topTools: ToolStats[] = (toolsResult[0]?.values ?? []).map((row) => ({\n tool: String(row[0]),\n calls: Number(row[1]),\n avgTokens: Number(row[2]),\n }));\n\n // Top symbols (exclude nulls)\n const symbolWherePrefix = whereClause ? whereClause + ' AND' : 'WHERE';\n const symbolsResult = this.db.exec(\n `SELECT symbol_id, COUNT(*) as queries\n FROM session_events ${symbolWherePrefix} symbol_id IS NOT NULL\n GROUP BY symbol_id ORDER BY queries DESC LIMIT 5`,\n );\n const topSymbols: SymbolStats[] = (symbolsResult[0]?.values ?? []).map((row) => {\n const symbolId = String(row[0]);\n const parts = symbolId.split('::');\n const name = parts.length >= 2 ? parts[1]! : symbolId;\n return {\n symbolId,\n name,\n queries: Number(row[1]),\n };\n });\n\n // Detail level distribution\n const levelResult = this.db.exec(\n `SELECT detail_level, COUNT(*) as count\n FROM session_events ${symbolWherePrefix} detail_level IS NOT NULL\n GROUP BY detail_level ORDER BY detail_level`,\n );\n const levelRows = levelResult[0]?.values ?? [];\n const levelTotal = levelRows.reduce((sum, row) => sum + Number(row[1]), 0);\n const detailLevelDistribution: DetailLevelStats[] = levelRows.map((row) => ({\n level: String(row[0]),\n count: Number(row[1]),\n percentage: levelTotal > 0 ? Math.round((Number(row[1]) / levelTotal) * 100) : 0,\n }));\n\n return {\n totalCalls,\n totalTokensServed,\n topTools,\n topSymbols,\n detailLevelDistribution,\n };\n }\n\n clear(): void {\n this.ensureTable();\n this.db.run('DELETE FROM session_events');\n this.flushToDisk();\n }\n\n private flushToDisk(): void {\n try {\n this.onWrite?.();\n } catch (err) {\n log.error(`Failed to flush stats to disk: ${(err as Error).message}`);\n }\n }\n}\n","import type { CommitRecord, AntiPattern } from '../types.js';\n\n// Explicit revert patterns\nconst REVERT_QUOTED_PATTERN = /^Revert \"(.+)\"$/;\nconst REVERT_PREFIX_PATTERN = /^revert:\\s*(.+)$/i;\n\n// Undo/rollback patterns\nconst UNDO_PATTERN = /^undo[:\\s]/i;\nconst ROLLBACK_PATTERN = /^rollback[:\\s]/i;\n\n// Indirect revert indicators (keywords in commit message body)\nconst INDIRECT_KEYWORDS = [\n /\\brevert(?:s|ed|ing)?\\b/i,\n /\\broll(?:s|ed|ing)?\\s*back\\b/i,\n /\\bundo(?:es|ne|ing)?\\b/i,\n /\\bbacked?\\s*out\\b/i,\n /\\bremov(?:e|es|ed|ing)\\s+(?:broken|buggy|faulty)\\b/i,\n];\n\nexport class RevertDetector {\n detect(commits: readonly CommitRecord[]): AntiPattern[] {\n const antiPatterns: AntiPattern[] = [];\n\n for (const commit of commits) {\n if (!commit.message) continue;\n\n if (this.isRevert(commit.message)) {\n antiPatterns.push({\n hash: commit.hash,\n message: commit.message,\n date: commit.date,\n });\n }\n }\n\n return antiPatterns;\n }\n\n private isRevert(message: string): boolean {\n // Explicit patterns (high confidence)\n if (REVERT_QUOTED_PATTERN.test(message)) return true;\n if (REVERT_PREFIX_PATTERN.test(message)) return true;\n if (UNDO_PATTERN.test(message)) return true;\n if (ROLLBACK_PATTERN.test(message)) return true;\n\n // Indirect indicators (keyword search in full message)\n for (const pattern of INDIRECT_KEYWORDS) {\n if (pattern.test(message)) return true;\n }\n\n return false;\n }\n}\n","import type { CoChangeEntry, CoChangeMatrix, FileIndex } from '../types.js';\n\nconst MIN_SHARED_COMMITS = 2;\nconst MIN_FREQUENCY = 0.1;\n\nexport function aggregateCoChanges(indices: readonly FileIndex[]): CoChangeMatrix {\n // Step 1: Build commit → files map\n const commitToFiles = new Map<string, Set<string>>();\n const fileCommitCounts = new Map<string, number>();\n\n for (const idx of indices) {\n const file = idx.file;\n let count = 0;\n for (const intent of idx.intent) {\n count++;\n let files = commitToFiles.get(intent.hash);\n if (!files) {\n files = new Set();\n commitToFiles.set(intent.hash, files);\n }\n files.add(file);\n }\n if (count > 0) {\n fileCommitCounts.set(file, count);\n }\n }\n\n // Step 2: Count shared commits per file pair\n const pairCounts = new Map<string, number>();\n for (const files of commitToFiles.values()) {\n if (files.size < 2) continue;\n const sorted = [...files].sort();\n for (let i = 0; i < sorted.length; i++) {\n for (let j = i + 1; j < sorted.length; j++) {\n const key = `${sorted[i]}|${sorted[j]}`;\n pairCounts.set(key, (pairCounts.get(key) ?? 0) + 1);\n }\n }\n }\n\n // Step 3: Calculate frequency and filter\n const entries: CoChangeEntry[] = [];\n for (const [key, sharedCommits] of pairCounts) {\n if (sharedCommits < MIN_SHARED_COMMITS) continue;\n\n const [file1, file2] = key.split('|') as [string, string];\n const count1 = fileCommitCounts.get(file1) ?? 0;\n const count2 = fileCommitCounts.get(file2) ?? 0;\n const minCount = Math.min(count1, count2);\n if (minCount === 0) continue;\n\n const frequency = Math.round((sharedCommits / minCount) * 1000) / 1000;\n if (frequency < MIN_FREQUENCY) continue;\n\n entries.push({ file1, file2, sharedCommits, frequency });\n }\n\n entries.sort((a, b) => b.frequency - a.frequency || b.sharedCommits - a.sharedCommits);\n\n return {\n version: 1,\n timestamp: Math.floor(Date.now() / 1000),\n entries,\n };\n}\n\nexport function loadCoChangeMap(matrix: CoChangeMatrix): Map<string, CoChangeEntry[]> {\n const map = new Map<string, CoChangeEntry[]>();\n for (const entry of matrix.entries) {\n let list1 = map.get(entry.file1);\n if (!list1) { list1 = []; map.set(entry.file1, list1); }\n list1.push(entry);\n\n let list2 = map.get(entry.file2);\n if (!list2) { list2 = []; map.set(entry.file2, list2); }\n list2.push(entry);\n }\n return map;\n}\n"],"mappings":"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","names":["log","log"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
RevertDetector,
|
|
4
|
+
SessionRecorderAdapter,
|
|
4
5
|
SimpleGitAdapter,
|
|
5
6
|
SqliteStorageAdapter,
|
|
6
7
|
aggregateCoChanges
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-WZKXGKKI.js";
|
|
8
9
|
import {
|
|
9
10
|
JsonIndexReader,
|
|
10
11
|
SYMBOL_KINDS
|
|
@@ -1781,6 +1782,159 @@ var WatchCommand = class {
|
|
|
1781
1782
|
}
|
|
1782
1783
|
};
|
|
1783
1784
|
|
|
1785
|
+
// src/cli/stats-command.ts
|
|
1786
|
+
import { join as join10 } from "path";
|
|
1787
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1788
|
+
import initSqlJs from "sql.js";
|
|
1789
|
+
var StatsCommand = class {
|
|
1790
|
+
ctxoRoot;
|
|
1791
|
+
constructor(projectRoot) {
|
|
1792
|
+
this.ctxoRoot = join10(projectRoot, ".ctxo");
|
|
1793
|
+
}
|
|
1794
|
+
async run(options) {
|
|
1795
|
+
if (!this.isStatsEnabled()) {
|
|
1796
|
+
console.error("[ctxo] Stats collection is disabled in .ctxo/config.yaml");
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
const dbPath = join10(this.ctxoRoot, ".cache", "symbols.db");
|
|
1800
|
+
if (!existsSync6(dbPath)) {
|
|
1801
|
+
if (options.json) {
|
|
1802
|
+
process.stdout.write(JSON.stringify(this.emptyReport(options.days)) + "\n");
|
|
1803
|
+
} else {
|
|
1804
|
+
console.error("[ctxo] No usage data yet. Start using Ctxo MCP tools to collect stats.");
|
|
1805
|
+
}
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
const SQL = await initSqlJs();
|
|
1809
|
+
const buffer = readFileSync5(dbPath);
|
|
1810
|
+
const db = new SQL.Database(buffer);
|
|
1811
|
+
const recorder = new SessionRecorderAdapter(db);
|
|
1812
|
+
try {
|
|
1813
|
+
if (options.clear) {
|
|
1814
|
+
recorder.clear();
|
|
1815
|
+
const data = db.export();
|
|
1816
|
+
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
1817
|
+
writeFileSync5(dbPath, Buffer.from(data));
|
|
1818
|
+
console.error("[ctxo] Session data cleared.");
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (options.days != null) {
|
|
1822
|
+
if (!Number.isInteger(options.days) || options.days <= 0) {
|
|
1823
|
+
console.error("[ctxo] --days must be a positive integer");
|
|
1824
|
+
process.exit(1);
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
const stats = recorder.queryStats(options.days);
|
|
1829
|
+
if (stats.totalCalls === 0) {
|
|
1830
|
+
if (options.json) {
|
|
1831
|
+
process.stdout.write(JSON.stringify(this.emptyReport(options.days)) + "\n");
|
|
1832
|
+
} else {
|
|
1833
|
+
console.error("[ctxo] No usage data yet. Start using Ctxo MCP tools to collect stats.");
|
|
1834
|
+
}
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if (options.json) {
|
|
1838
|
+
process.stdout.write(JSON.stringify(this.buildReport(stats, options.days), null, 2) + "\n");
|
|
1839
|
+
} else {
|
|
1840
|
+
this.printHumanReadable(stats, options.days);
|
|
1841
|
+
}
|
|
1842
|
+
} finally {
|
|
1843
|
+
db.close();
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
isStatsEnabled() {
|
|
1847
|
+
const configPath = join10(this.ctxoRoot, "config.yaml");
|
|
1848
|
+
if (!existsSync6(configPath)) return true;
|
|
1849
|
+
try {
|
|
1850
|
+
const raw = readFileSync5(configPath, "utf-8");
|
|
1851
|
+
const match = raw.match(/stats:\s*\n\s*enabled:\s*(true|false)/);
|
|
1852
|
+
if (match) return match[1] !== "false";
|
|
1853
|
+
return true;
|
|
1854
|
+
} catch {
|
|
1855
|
+
return true;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
buildReport(stats, days) {
|
|
1859
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1860
|
+
const from = days != null ? new Date(Date.now() - days * 864e5).toISOString() : null;
|
|
1861
|
+
return {
|
|
1862
|
+
timeRange: { from, to: now, daysFilter: days ?? null },
|
|
1863
|
+
summary: {
|
|
1864
|
+
totalCalls: stats.totalCalls,
|
|
1865
|
+
totalTokensServed: stats.totalTokensServed
|
|
1866
|
+
},
|
|
1867
|
+
topTools: stats.topTools,
|
|
1868
|
+
topSymbols: stats.topSymbols,
|
|
1869
|
+
detailLevelDistribution: stats.detailLevelDistribution.map((d) => ({
|
|
1870
|
+
level: d.level,
|
|
1871
|
+
count: d.count,
|
|
1872
|
+
percentage: d.percentage
|
|
1873
|
+
}))
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
emptyReport(days) {
|
|
1877
|
+
return this.buildReport({
|
|
1878
|
+
totalCalls: 0,
|
|
1879
|
+
totalTokensServed: 0,
|
|
1880
|
+
topTools: [],
|
|
1881
|
+
topSymbols: [],
|
|
1882
|
+
detailLevelDistribution: []
|
|
1883
|
+
}, days);
|
|
1884
|
+
}
|
|
1885
|
+
printHumanReadable(stats, days) {
|
|
1886
|
+
const header = days != null ? `last ${days} days` : "all time";
|
|
1887
|
+
console.error("");
|
|
1888
|
+
console.error(` Usage Summary (${header})`);
|
|
1889
|
+
console.error(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1890
|
+
console.error(` Total tool calls: ${this.formatNumber(stats.totalCalls)}`);
|
|
1891
|
+
console.error(` Total tokens served: ${this.formatTokens(stats.totalTokensServed)}`);
|
|
1892
|
+
if (stats.topTools.length > 0) {
|
|
1893
|
+
console.error("");
|
|
1894
|
+
console.error(" Top Tools");
|
|
1895
|
+
console.error(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1896
|
+
for (const t of stats.topTools) {
|
|
1897
|
+
const name = t.tool.padEnd(24);
|
|
1898
|
+
const calls = `${this.formatNumber(t.calls)} calls`.padEnd(14);
|
|
1899
|
+
console.error(` ${name}${calls}avg ${this.formatNumber(t.avgTokens)} tokens`);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
if (stats.topSymbols.length > 0) {
|
|
1903
|
+
console.error("");
|
|
1904
|
+
console.error(" Top Queried Symbols");
|
|
1905
|
+
console.error(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1906
|
+
for (const s of stats.topSymbols) {
|
|
1907
|
+
const name = s.name.padEnd(32);
|
|
1908
|
+
console.error(` ${name}${this.formatNumber(s.queries)} queries`);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (stats.detailLevelDistribution.length > 0) {
|
|
1912
|
+
console.error("");
|
|
1913
|
+
console.error(" Detail Level Distribution");
|
|
1914
|
+
console.error(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1915
|
+
for (const d of stats.detailLevelDistribution) {
|
|
1916
|
+
const bar = this.renderBar(d.percentage);
|
|
1917
|
+
const pct = `${d.percentage}%`.padStart(4);
|
|
1918
|
+
console.error(` ${d.level}: ${bar} ${pct}`);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
console.error("");
|
|
1922
|
+
}
|
|
1923
|
+
renderBar(percentage) {
|
|
1924
|
+
const filled = Math.round(percentage / 10);
|
|
1925
|
+
const empty = 10 - filled;
|
|
1926
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
1927
|
+
}
|
|
1928
|
+
formatNumber(n) {
|
|
1929
|
+
return n.toLocaleString("en-US");
|
|
1930
|
+
}
|
|
1931
|
+
formatTokens(n) {
|
|
1932
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1933
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
1934
|
+
return String(n);
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
|
|
1784
1938
|
// src/cli/cli-router.ts
|
|
1785
1939
|
var CliRouter = class {
|
|
1786
1940
|
projectRoot;
|
|
@@ -1829,6 +1983,14 @@ var CliRouter = class {
|
|
|
1829
1983
|
case "init":
|
|
1830
1984
|
new InitCommand(this.projectRoot).run();
|
|
1831
1985
|
break;
|
|
1986
|
+
case "stats": {
|
|
1987
|
+
const daysIdx = args.indexOf("--days");
|
|
1988
|
+
const daysArg = daysIdx !== -1 ? Number(args[daysIdx + 1]) : void 0;
|
|
1989
|
+
const jsonArg = args.includes("--json");
|
|
1990
|
+
const clearArg = args.includes("--clear");
|
|
1991
|
+
await new StatsCommand(this.projectRoot).run({ days: daysArg, json: jsonArg, clear: clearArg });
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1832
1994
|
default:
|
|
1833
1995
|
console.error(`[ctxo] Unknown command: "${command}". Run "ctxo --help" for usage.`);
|
|
1834
1996
|
process.exit(1);
|
|
@@ -1847,6 +2009,7 @@ Usage:
|
|
|
1847
2009
|
ctxo verify-index CI gate: fail if index is stale
|
|
1848
2010
|
ctxo status Show index manifest
|
|
1849
2011
|
ctxo init Install git hooks
|
|
2012
|
+
ctxo stats Show usage statistics (--json, --days N, --clear)
|
|
1850
2013
|
ctxo --help Show this help message
|
|
1851
2014
|
`.trim());
|
|
1852
2015
|
}
|
|
@@ -1854,4 +2017,4 @@ Usage:
|
|
|
1854
2017
|
export {
|
|
1855
2018
|
CliRouter
|
|
1856
2019
|
};
|
|
1857
|
-
//# sourceMappingURL=cli-router-
|
|
2020
|
+
//# sourceMappingURL=cli-router-H557TJAM.js.map
|