ctxo-mcp 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/dist/chunk-54ETLIQX.js +145 -0
- package/dist/chunk-54ETLIQX.js.map +1 -0
- package/dist/chunk-P7JUSY3I.js +410 -0
- package/dist/chunk-P7JUSY3I.js.map +1 -0
- package/dist/cli-router-PIWHLS5F.js +1118 -0
- package/dist/cli-router-PIWHLS5F.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +786 -0
- package/dist/index.js.map +1 -0
- package/dist/json-index-reader-PNLPAS42.js +8 -0
- package/dist/json-index-reader-PNLPAS42.js.map +1 -0
- package/dist/staleness-detector-5AN223FM.js +39 -0
- package/dist/staleness-detector-5AN223FM.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alper Hankendi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/adapters/storage/json-index-reader.ts
|
|
4
|
+
import { readFileSync, readdirSync, existsSync, realpathSync } from "fs";
|
|
5
|
+
import { join, relative } from "path";
|
|
6
|
+
|
|
7
|
+
// src/core/types.ts
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
var SYMBOL_KINDS = [
|
|
10
|
+
"function",
|
|
11
|
+
"class",
|
|
12
|
+
"interface",
|
|
13
|
+
"method",
|
|
14
|
+
"variable",
|
|
15
|
+
"type"
|
|
16
|
+
];
|
|
17
|
+
var SymbolKindSchema = z.enum(SYMBOL_KINDS);
|
|
18
|
+
var EDGE_KINDS = [
|
|
19
|
+
"imports",
|
|
20
|
+
"calls",
|
|
21
|
+
"extends",
|
|
22
|
+
"implements",
|
|
23
|
+
"uses"
|
|
24
|
+
];
|
|
25
|
+
var EdgeKindSchema = z.enum(EDGE_KINDS);
|
|
26
|
+
var DetailLevelSchema = z.union([
|
|
27
|
+
z.literal(1),
|
|
28
|
+
z.literal(2),
|
|
29
|
+
z.literal(3),
|
|
30
|
+
z.literal(4)
|
|
31
|
+
]);
|
|
32
|
+
var SymbolIdSchema = z.string().min(1).refine(
|
|
33
|
+
(value) => {
|
|
34
|
+
const parts = value.split("::");
|
|
35
|
+
if (parts.length !== 3) return false;
|
|
36
|
+
const [file, name, kind] = parts;
|
|
37
|
+
if (!file || !name || !kind) return false;
|
|
38
|
+
return SymbolKindSchema.safeParse(kind).success;
|
|
39
|
+
},
|
|
40
|
+
{ message: 'Symbol ID must match format "<file>::<name>::<kind>"' }
|
|
41
|
+
);
|
|
42
|
+
var SymbolNodeSchema = z.object({
|
|
43
|
+
symbolId: SymbolIdSchema,
|
|
44
|
+
name: z.string().min(1),
|
|
45
|
+
kind: SymbolKindSchema,
|
|
46
|
+
startLine: z.number().int().nonnegative(),
|
|
47
|
+
endLine: z.number().int().nonnegative()
|
|
48
|
+
}).refine((node) => node.endLine >= node.startLine, {
|
|
49
|
+
message: "endLine must be >= startLine"
|
|
50
|
+
});
|
|
51
|
+
var GraphEdgeSchema = z.object({
|
|
52
|
+
from: SymbolIdSchema,
|
|
53
|
+
to: SymbolIdSchema,
|
|
54
|
+
kind: EdgeKindSchema
|
|
55
|
+
});
|
|
56
|
+
var CommitIntentSchema = z.object({
|
|
57
|
+
hash: z.string().min(1),
|
|
58
|
+
message: z.string(),
|
|
59
|
+
date: z.string().min(1),
|
|
60
|
+
kind: z.literal("commit")
|
|
61
|
+
});
|
|
62
|
+
var AntiPatternSchema = z.object({
|
|
63
|
+
hash: z.string().min(1),
|
|
64
|
+
message: z.string(),
|
|
65
|
+
date: z.string().min(1)
|
|
66
|
+
});
|
|
67
|
+
var ComplexityMetricsSchema = z.object({
|
|
68
|
+
symbolId: z.string().min(1),
|
|
69
|
+
cyclomatic: z.number().nonnegative()
|
|
70
|
+
});
|
|
71
|
+
var FileIndexSchema = z.object({
|
|
72
|
+
file: z.string().min(1),
|
|
73
|
+
lastModified: z.number().nonnegative(),
|
|
74
|
+
contentHash: z.string().optional(),
|
|
75
|
+
symbols: z.array(SymbolNodeSchema),
|
|
76
|
+
edges: z.array(GraphEdgeSchema),
|
|
77
|
+
complexity: z.array(ComplexityMetricsSchema).optional(),
|
|
78
|
+
intent: z.array(CommitIntentSchema),
|
|
79
|
+
antiPatterns: z.array(AntiPatternSchema)
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// src/adapters/storage/json-index-reader.ts
|
|
83
|
+
var JsonIndexReader = class {
|
|
84
|
+
indexDir;
|
|
85
|
+
constructor(ctxoRoot) {
|
|
86
|
+
this.indexDir = join(ctxoRoot, "index");
|
|
87
|
+
}
|
|
88
|
+
readAll() {
|
|
89
|
+
if (!existsSync(this.indexDir)) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const jsonFiles = this.collectJsonFiles(this.indexDir);
|
|
93
|
+
const results = [];
|
|
94
|
+
for (const filePath of jsonFiles) {
|
|
95
|
+
const parsed = this.readSingle(filePath);
|
|
96
|
+
if (parsed) {
|
|
97
|
+
results.push(parsed);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
readSingle(absolutePath) {
|
|
103
|
+
try {
|
|
104
|
+
const raw = readFileSync(absolutePath, "utf-8");
|
|
105
|
+
const data = JSON.parse(raw);
|
|
106
|
+
const result = FileIndexSchema.safeParse(data);
|
|
107
|
+
if (!result.success) {
|
|
108
|
+
const rel = relative(this.indexDir, absolutePath);
|
|
109
|
+
console.error(
|
|
110
|
+
`[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`
|
|
111
|
+
);
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
return result.data;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
const rel = relative(this.indexDir, absolutePath);
|
|
117
|
+
console.error(
|
|
118
|
+
`[ctxo:json-reader] Failed to read ${rel}: ${err.message}`
|
|
119
|
+
);
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
collectJsonFiles(dir, visited = /* @__PURE__ */ new Set()) {
|
|
124
|
+
const realDir = realpathSync(dir);
|
|
125
|
+
if (visited.has(realDir)) return [];
|
|
126
|
+
visited.add(realDir);
|
|
127
|
+
const files = [];
|
|
128
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
129
|
+
if (entry.isSymbolicLink()) continue;
|
|
130
|
+
const fullPath = join(dir, entry.name);
|
|
131
|
+
if (entry.isDirectory()) {
|
|
132
|
+
files.push(...this.collectJsonFiles(fullPath, visited));
|
|
133
|
+
} else if (entry.name.endsWith(".json")) {
|
|
134
|
+
files.push(fullPath);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return files;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
DetailLevelSchema,
|
|
143
|
+
JsonIndexReader
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=chunk-54ETLIQX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/storage/json-index-reader.ts","../src/core/types.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync, realpathSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport { FileIndexSchema, type FileIndex } from '../../core/types.js';\n\nexport class JsonIndexReader {\n private readonly indexDir: string;\n\n constructor(ctxoRoot: string) {\n this.indexDir = join(ctxoRoot, 'index');\n }\n\n readAll(): FileIndex[] {\n if (!existsSync(this.indexDir)) {\n return [];\n }\n\n const jsonFiles = this.collectJsonFiles(this.indexDir);\n const results: FileIndex[] = [];\n\n for (const filePath of jsonFiles) {\n const parsed = this.readSingle(filePath);\n if (parsed) {\n results.push(parsed);\n }\n }\n\n return results;\n }\n\n readSingle(absolutePath: string): FileIndex | undefined {\n try {\n const raw = readFileSync(absolutePath, 'utf-8');\n const data: unknown = JSON.parse(raw);\n const result = FileIndexSchema.safeParse(data);\n\n if (!result.success) {\n const rel = relative(this.indexDir, absolutePath);\n console.error(\n `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`,\n );\n return undefined;\n }\n\n return result.data;\n } catch (err) {\n const rel = relative(this.indexDir, absolutePath);\n console.error(\n `[ctxo:json-reader] Failed to read ${rel}: ${(err as Error).message}`,\n );\n return undefined;\n }\n }\n\n private collectJsonFiles(dir: string, visited: Set<string> = new Set()): string[] {\n const realDir = realpathSync(dir);\n if (visited.has(realDir)) return []; // Guard against symlink loops\n visited.add(realDir);\n\n const files: string[] = [];\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.isSymbolicLink()) continue; // Skip symlinks entirely\n\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...this.collectJsonFiles(fullPath, visited));\n } else if (entry.name.endsWith('.json')) {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n}\n","import { z } from 'zod';\n\n// ── Symbol Kinds ────────────────────────────────────────────────\n\nexport const SYMBOL_KINDS = [\n 'function',\n 'class',\n 'interface',\n 'method',\n 'variable',\n 'type',\n] as const;\n\nexport const SymbolKindSchema = z.enum(SYMBOL_KINDS);\nexport type SymbolKind = z.infer<typeof SymbolKindSchema>;\n\n// ── Edge Kinds ──────────────────────────────────────────────────\n\nexport const EDGE_KINDS = [\n 'imports',\n 'calls',\n 'extends',\n 'implements',\n 'uses',\n] as const;\n\nexport const EdgeKindSchema = z.enum(EDGE_KINDS);\nexport type EdgeKind = z.infer<typeof EdgeKindSchema>;\n\n// ── Detail Levels ───────────────────────────────────────────────\n\nexport const DETAIL_LEVELS = [1, 2, 3, 4] as const;\n\nexport const DetailLevelSchema = z.union([\n z.literal(1),\n z.literal(2),\n z.literal(3),\n z.literal(4),\n]);\nexport type DetailLevel = z.infer<typeof DetailLevelSchema>;\n\n// ── Symbol ID ───────────────────────────────────────────────────\n\n/**\n * Format: \"<relativeFile>::<name>::<kind>\"\n * Example: \"src/foo.ts::myFn::function\"\n */\nexport const SymbolIdSchema = z\n .string()\n .min(1)\n .refine(\n (value) => {\n const parts = value.split('::');\n if (parts.length !== 3) return false;\n const [file, name, kind] = parts;\n if (!file || !name || !kind) return false;\n return SymbolKindSchema.safeParse(kind).success;\n },\n { message: 'Symbol ID must match format \"<file>::<name>::<kind>\"' },\n );\nexport type SymbolId = z.infer<typeof SymbolIdSchema>;\n\n// ── Symbol Node ─────────────────────────────────────────────────\n\nexport const SymbolNodeSchema = z\n .object({\n symbolId: SymbolIdSchema,\n name: z.string().min(1),\n kind: SymbolKindSchema,\n startLine: z.number().int().nonnegative(),\n endLine: z.number().int().nonnegative(),\n })\n .refine((node) => node.endLine >= node.startLine, {\n message: 'endLine must be >= startLine',\n });\nexport type SymbolNode = z.infer<typeof SymbolNodeSchema>;\n\n// ── Graph Edge ──────────────────────────────────────────────────\n\nexport const GraphEdgeSchema = z.object({\n from: SymbolIdSchema,\n to: SymbolIdSchema,\n kind: EdgeKindSchema,\n});\nexport type GraphEdge = z.infer<typeof GraphEdgeSchema>;\n\n// ── Commit Intent ───────────────────────────────────────────────\n\nexport const CommitIntentSchema = z.object({\n hash: z.string().min(1),\n message: z.string(),\n date: z.string().min(1),\n kind: z.literal('commit'),\n});\nexport type CommitIntent = z.infer<typeof CommitIntentSchema>;\n\n// ── Anti-Pattern ────────────────────────────────────────────────\n\nexport const AntiPatternSchema = z.object({\n hash: z.string().min(1),\n message: z.string(),\n date: z.string().min(1),\n});\nexport type AntiPattern = z.infer<typeof AntiPatternSchema>;\n\n// ── File Index ──────────────────────────────────────────────────\n\nexport const ComplexityMetricsSchema = z.object({\n symbolId: z.string().min(1),\n cyclomatic: z.number().nonnegative(),\n});\n\nexport const FileIndexSchema = z.object({\n file: z.string().min(1),\n lastModified: z.number().nonnegative(),\n contentHash: z.string().optional(),\n symbols: z.array(SymbolNodeSchema),\n edges: z.array(GraphEdgeSchema),\n complexity: z.array(ComplexityMetricsSchema).optional(),\n intent: z.array(CommitIntentSchema),\n antiPatterns: z.array(AntiPatternSchema),\n});\nexport type FileIndex = z.infer<typeof FileIndexSchema>;\n\n// ── Logic-Slice Result ──────────────────────────────────────────\n\nexport interface LogicSliceResult {\n readonly root: SymbolNode;\n readonly dependencies: readonly SymbolNode[];\n readonly edges: readonly GraphEdge[];\n}\n\n// ── Formatted Slice ─────────────────────────────────────────────\n\nexport interface TruncationInfo {\n readonly truncated: true;\n readonly reason: 'token_budget_exceeded';\n}\n\nexport interface FormattedSlice {\n readonly root: SymbolNode;\n readonly dependencies: readonly SymbolNode[];\n readonly edges: readonly GraphEdge[];\n readonly level: DetailLevel;\n readonly levelDescription?: string;\n readonly truncation?: TruncationInfo;\n}\n\n// ── Complexity & Churn ──────────────────────────────────────────\n\nexport interface ComplexityMetrics {\n readonly symbolId: string;\n readonly cyclomatic: number;\n}\n\nexport interface ChurnData {\n readonly filePath: string;\n readonly commitCount: number;\n}\n\nexport const SCORE_BANDS = ['low', 'medium', 'high'] as const;\nexport type ScoreBand = (typeof SCORE_BANDS)[number];\n\nexport interface ChangeIntelligenceScore {\n readonly symbolId: string;\n readonly complexity: number;\n readonly churn: number;\n readonly composite: number;\n readonly band: ScoreBand;\n}\n\n// ── Why-Context Result ──────────────────────────────────────────\n\nexport interface WhyContextResult {\n readonly commitHistory: readonly CommitIntent[];\n readonly antiPatternWarnings: readonly AntiPattern[];\n readonly changeIntelligence?: ChangeIntelligenceScore;\n}\n\n// ── Commit Record (from git adapter) ────────────────────────────\n\nexport interface CommitRecord {\n readonly hash: string;\n readonly message: string;\n readonly date: string;\n readonly author: string;\n}\n\n// ── Blame Line (from git adapter) ───────────────────────────────\n\nexport interface BlameLine {\n readonly hash: string;\n readonly lineNumber: number;\n readonly author: string;\n readonly date: string;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
JsonIndexReader
|
|
4
|
+
} from "./chunk-54ETLIQX.js";
|
|
5
|
+
|
|
6
|
+
// src/adapters/storage/sqlite-storage-adapter.ts
|
|
7
|
+
import initSqlJs from "sql.js";
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
|
+
var SqliteStorageAdapter = class {
|
|
11
|
+
db;
|
|
12
|
+
dbPath;
|
|
13
|
+
ctxoRoot;
|
|
14
|
+
constructor(ctxoRoot) {
|
|
15
|
+
this.ctxoRoot = ctxoRoot;
|
|
16
|
+
this.dbPath = join(ctxoRoot, ".cache", "symbols.db");
|
|
17
|
+
}
|
|
18
|
+
async init() {
|
|
19
|
+
const SQL = await initSqlJs();
|
|
20
|
+
if (existsSync(this.dbPath)) {
|
|
21
|
+
try {
|
|
22
|
+
const buffer = readFileSync(this.dbPath);
|
|
23
|
+
this.db = new SQL.Database(buffer);
|
|
24
|
+
this.verifyIntegrity();
|
|
25
|
+
} catch {
|
|
26
|
+
console.error("[ctxo:sqlite] Corrupt DB detected, rebuilding from JSON index");
|
|
27
|
+
this.db = new SQL.Database();
|
|
28
|
+
this.createTables();
|
|
29
|
+
this.rebuildFromJson();
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
this.db = new SQL.Database();
|
|
33
|
+
this.createTables();
|
|
34
|
+
this.rebuildFromJson();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async initEmpty() {
|
|
38
|
+
const SQL = await initSqlJs();
|
|
39
|
+
this.db = new SQL.Database();
|
|
40
|
+
this.createTables();
|
|
41
|
+
}
|
|
42
|
+
database() {
|
|
43
|
+
if (!this.db) {
|
|
44
|
+
throw new Error("SqliteStorageAdapter not initialized. Call init() first.");
|
|
45
|
+
}
|
|
46
|
+
return this.db;
|
|
47
|
+
}
|
|
48
|
+
verifyIntegrity() {
|
|
49
|
+
const db = this.database();
|
|
50
|
+
const result = db.exec("PRAGMA integrity_check");
|
|
51
|
+
const firstRow = result[0]?.values[0];
|
|
52
|
+
if (!firstRow || firstRow[0] !== "ok") {
|
|
53
|
+
throw new Error("SQLite integrity check failed");
|
|
54
|
+
}
|
|
55
|
+
const tables = db.exec(
|
|
56
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('symbols', 'edges', 'files')"
|
|
57
|
+
);
|
|
58
|
+
if (!tables[0] || tables[0].values.length < 3) {
|
|
59
|
+
throw new Error("Missing required tables");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
createTables() {
|
|
63
|
+
const db = this.database();
|
|
64
|
+
db.run(`
|
|
65
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
66
|
+
file_path TEXT PRIMARY KEY,
|
|
67
|
+
last_modified INTEGER NOT NULL
|
|
68
|
+
)
|
|
69
|
+
`);
|
|
70
|
+
db.run(`
|
|
71
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
72
|
+
symbol_id TEXT PRIMARY KEY,
|
|
73
|
+
name TEXT NOT NULL,
|
|
74
|
+
kind TEXT NOT NULL,
|
|
75
|
+
file_path TEXT NOT NULL,
|
|
76
|
+
start_line INTEGER NOT NULL,
|
|
77
|
+
end_line INTEGER NOT NULL
|
|
78
|
+
)
|
|
79
|
+
`);
|
|
80
|
+
db.run(`
|
|
81
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
82
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
83
|
+
from_symbol TEXT NOT NULL,
|
|
84
|
+
to_symbol TEXT NOT NULL,
|
|
85
|
+
kind TEXT NOT NULL
|
|
86
|
+
)
|
|
87
|
+
`);
|
|
88
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path)");
|
|
89
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_symbol)");
|
|
90
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_symbol)");
|
|
91
|
+
}
|
|
92
|
+
rebuildFromJson() {
|
|
93
|
+
const reader = new JsonIndexReader(this.ctxoRoot);
|
|
94
|
+
const indices = reader.readAll();
|
|
95
|
+
if (indices.length > 0) {
|
|
96
|
+
this.bulkWrite(indices);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
writeSymbolFile(fileIndex) {
|
|
100
|
+
const db = this.database();
|
|
101
|
+
db.run("BEGIN TRANSACTION");
|
|
102
|
+
try {
|
|
103
|
+
this.deleteFileData(db, fileIndex.file);
|
|
104
|
+
db.run(
|
|
105
|
+
"INSERT INTO files (file_path, last_modified) VALUES (?, ?)",
|
|
106
|
+
[fileIndex.file, fileIndex.lastModified]
|
|
107
|
+
);
|
|
108
|
+
for (const sym of fileIndex.symbols) {
|
|
109
|
+
db.run(
|
|
110
|
+
"INSERT INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)",
|
|
111
|
+
[sym.symbolId, sym.name, sym.kind, fileIndex.file, sym.startLine, sym.endLine]
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
for (const edge of fileIndex.edges) {
|
|
115
|
+
db.run(
|
|
116
|
+
"INSERT INTO edges (from_symbol, to_symbol, kind) VALUES (?, ?, ?)",
|
|
117
|
+
[edge.from, edge.to, edge.kind]
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
db.run("COMMIT");
|
|
121
|
+
this.persistIfNeeded();
|
|
122
|
+
} catch (err) {
|
|
123
|
+
db.run("ROLLBACK");
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Read symbol file from SQLite cache.
|
|
129
|
+
* NOTE: SQLite only caches symbols + edges (graph topology).
|
|
130
|
+
* intent, antiPatterns, and complexity live in committed JSON index only.
|
|
131
|
+
* Use JsonIndexReader for full FileIndex data.
|
|
132
|
+
*/
|
|
133
|
+
readSymbolFile(relativePath) {
|
|
134
|
+
const db = this.database();
|
|
135
|
+
const fileResult = db.exec(
|
|
136
|
+
"SELECT file_path, last_modified FROM files WHERE file_path = ?",
|
|
137
|
+
[relativePath]
|
|
138
|
+
);
|
|
139
|
+
if (!fileResult[0] || fileResult[0].values.length === 0) {
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
const [filePath, lastModified] = fileResult[0].values[0];
|
|
143
|
+
const symbols = this.getSymbolsForFile(db, filePath);
|
|
144
|
+
const edges = this.getEdgesForFile(db, filePath);
|
|
145
|
+
return {
|
|
146
|
+
file: filePath,
|
|
147
|
+
lastModified,
|
|
148
|
+
symbols,
|
|
149
|
+
edges,
|
|
150
|
+
// Not stored in SQLite — use JsonIndexReader for these fields
|
|
151
|
+
intent: [],
|
|
152
|
+
antiPatterns: []
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
listIndexedFiles() {
|
|
156
|
+
const db = this.database();
|
|
157
|
+
const result = db.exec("SELECT file_path FROM files ORDER BY file_path");
|
|
158
|
+
if (!result[0]) return [];
|
|
159
|
+
return result[0].values.map((row) => row[0]);
|
|
160
|
+
}
|
|
161
|
+
deleteSymbolFile(relativePath) {
|
|
162
|
+
const db = this.database();
|
|
163
|
+
this.deleteFileData(db, relativePath);
|
|
164
|
+
}
|
|
165
|
+
getSymbolById(symbolId) {
|
|
166
|
+
const db = this.database();
|
|
167
|
+
const result = db.exec(
|
|
168
|
+
"SELECT symbol_id, name, kind, start_line, end_line FROM symbols WHERE symbol_id = ?",
|
|
169
|
+
[symbolId]
|
|
170
|
+
);
|
|
171
|
+
if (!result[0] || result[0].values.length === 0) {
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
const [sid, name, kind, startLine, endLine] = result[0].values[0];
|
|
175
|
+
return { symbolId: sid, name, kind, startLine, endLine };
|
|
176
|
+
}
|
|
177
|
+
getEdgesFrom(symbolId) {
|
|
178
|
+
const db = this.database();
|
|
179
|
+
const result = db.exec(
|
|
180
|
+
"SELECT from_symbol, to_symbol, kind FROM edges WHERE from_symbol = ?",
|
|
181
|
+
[symbolId]
|
|
182
|
+
);
|
|
183
|
+
if (!result[0]) return [];
|
|
184
|
+
return result[0].values.map((row) => ({
|
|
185
|
+
from: row[0],
|
|
186
|
+
to: row[1],
|
|
187
|
+
kind: row[2]
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
getEdgesTo(symbolId) {
|
|
191
|
+
const db = this.database();
|
|
192
|
+
const result = db.exec(
|
|
193
|
+
"SELECT from_symbol, to_symbol, kind FROM edges WHERE to_symbol = ?",
|
|
194
|
+
[symbolId]
|
|
195
|
+
);
|
|
196
|
+
if (!result[0]) return [];
|
|
197
|
+
return result[0].values.map((row) => ({
|
|
198
|
+
from: row[0],
|
|
199
|
+
to: row[1],
|
|
200
|
+
kind: row[2]
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
getAllSymbols() {
|
|
204
|
+
const db = this.database();
|
|
205
|
+
const result = db.exec(
|
|
206
|
+
"SELECT symbol_id, name, kind, start_line, end_line FROM symbols"
|
|
207
|
+
);
|
|
208
|
+
if (!result[0]) return [];
|
|
209
|
+
return result[0].values.map((row) => ({
|
|
210
|
+
symbolId: row[0],
|
|
211
|
+
name: row[1],
|
|
212
|
+
kind: row[2],
|
|
213
|
+
startLine: row[3],
|
|
214
|
+
endLine: row[4]
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
getAllEdges() {
|
|
218
|
+
const db = this.database();
|
|
219
|
+
const result = db.exec(
|
|
220
|
+
"SELECT from_symbol, to_symbol, kind FROM edges"
|
|
221
|
+
);
|
|
222
|
+
if (!result[0]) return [];
|
|
223
|
+
return result[0].values.map((row) => ({
|
|
224
|
+
from: row[0],
|
|
225
|
+
to: row[1],
|
|
226
|
+
kind: row[2]
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
bulkWrite(indices) {
|
|
230
|
+
const db = this.database();
|
|
231
|
+
db.run("BEGIN TRANSACTION");
|
|
232
|
+
try {
|
|
233
|
+
for (const fileIndex of indices) {
|
|
234
|
+
this.deleteFileData(db, fileIndex.file);
|
|
235
|
+
db.run(
|
|
236
|
+
"INSERT INTO files (file_path, last_modified) VALUES (?, ?)",
|
|
237
|
+
[fileIndex.file, fileIndex.lastModified]
|
|
238
|
+
);
|
|
239
|
+
for (const sym of fileIndex.symbols) {
|
|
240
|
+
db.run(
|
|
241
|
+
"INSERT INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)",
|
|
242
|
+
[sym.symbolId, sym.name, sym.kind, fileIndex.file, sym.startLine, sym.endLine]
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
for (const edge of fileIndex.edges) {
|
|
246
|
+
db.run(
|
|
247
|
+
"INSERT INTO edges (from_symbol, to_symbol, kind) VALUES (?, ?, ?)",
|
|
248
|
+
[edge.from, edge.to, edge.kind]
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
db.run("COMMIT");
|
|
253
|
+
this.persistIfNeeded();
|
|
254
|
+
} catch (err) {
|
|
255
|
+
db.run("ROLLBACK");
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
persist() {
|
|
260
|
+
const db = this.database();
|
|
261
|
+
const data = db.export();
|
|
262
|
+
const buffer = Buffer.from(data);
|
|
263
|
+
mkdirSync(dirname(this.dbPath), { recursive: true });
|
|
264
|
+
writeFileSync(this.dbPath, buffer);
|
|
265
|
+
}
|
|
266
|
+
close() {
|
|
267
|
+
if (this.db) {
|
|
268
|
+
this.persist();
|
|
269
|
+
this.db.close();
|
|
270
|
+
this.db = void 0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
persistIfNeeded() {
|
|
274
|
+
try {
|
|
275
|
+
this.persist();
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.error(`[ctxo:sqlite] Failed to persist DB: ${err.message}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
deleteFileData(db, filePath) {
|
|
281
|
+
const symResult = db.exec(
|
|
282
|
+
"SELECT symbol_id FROM symbols WHERE file_path = ?",
|
|
283
|
+
[filePath]
|
|
284
|
+
);
|
|
285
|
+
if (symResult[0]) {
|
|
286
|
+
for (const row of symResult[0].values) {
|
|
287
|
+
db.run("DELETE FROM edges WHERE from_symbol = ?", [row[0]]);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
db.run("DELETE FROM symbols WHERE file_path = ?", [filePath]);
|
|
291
|
+
db.run("DELETE FROM files WHERE file_path = ?", [filePath]);
|
|
292
|
+
}
|
|
293
|
+
getSymbolsForFile(db, filePath) {
|
|
294
|
+
const result = db.exec(
|
|
295
|
+
"SELECT symbol_id, name, kind, start_line, end_line FROM symbols WHERE file_path = ? ORDER BY start_line",
|
|
296
|
+
[filePath]
|
|
297
|
+
);
|
|
298
|
+
if (!result[0]) return [];
|
|
299
|
+
return result[0].values.map((row) => ({
|
|
300
|
+
symbolId: row[0],
|
|
301
|
+
name: row[1],
|
|
302
|
+
kind: row[2],
|
|
303
|
+
startLine: row[3],
|
|
304
|
+
endLine: row[4]
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
getEdgesForFile(db, filePath) {
|
|
308
|
+
const result = db.exec(
|
|
309
|
+
`SELECT e.from_symbol, e.to_symbol, e.kind FROM edges e
|
|
310
|
+
INNER JOIN symbols s ON e.from_symbol = s.symbol_id
|
|
311
|
+
WHERE s.file_path = ?`,
|
|
312
|
+
[filePath]
|
|
313
|
+
);
|
|
314
|
+
if (!result[0]) return [];
|
|
315
|
+
return result[0].values.map((row) => ({
|
|
316
|
+
from: row[0],
|
|
317
|
+
to: row[1],
|
|
318
|
+
kind: row[2]
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// src/adapters/git/simple-git-adapter.ts
|
|
324
|
+
import { simpleGit } from "simple-git";
|
|
325
|
+
var SimpleGitAdapter = class {
|
|
326
|
+
git;
|
|
327
|
+
constructor(projectRoot) {
|
|
328
|
+
this.git = simpleGit(projectRoot);
|
|
329
|
+
}
|
|
330
|
+
async isAvailable() {
|
|
331
|
+
try {
|
|
332
|
+
await this.git.version();
|
|
333
|
+
return true;
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async getCommitHistory(filePath) {
|
|
339
|
+
try {
|
|
340
|
+
const log = await this.git.log({ file: filePath, "--follow": null });
|
|
341
|
+
return log.all.map((entry) => ({
|
|
342
|
+
hash: entry.hash,
|
|
343
|
+
message: entry.message,
|
|
344
|
+
date: entry.date,
|
|
345
|
+
author: entry.author_name
|
|
346
|
+
}));
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error(`[ctxo:git] Failed to get history for ${filePath}: ${err.message}`);
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async getFileChurn(filePath) {
|
|
353
|
+
try {
|
|
354
|
+
const log = await this.git.log({ file: filePath, "--follow": null });
|
|
355
|
+
return {
|
|
356
|
+
filePath,
|
|
357
|
+
commitCount: log.total
|
|
358
|
+
};
|
|
359
|
+
} catch (err) {
|
|
360
|
+
console.error(`[ctxo:git] Failed to get churn for ${filePath}: ${err.message}`);
|
|
361
|
+
return { filePath, commitCount: 0 };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// src/core/why-context/revert-detector.ts
|
|
367
|
+
var REVERT_QUOTED_PATTERN = /^Revert "(.+)"$/;
|
|
368
|
+
var REVERT_PREFIX_PATTERN = /^revert:\s*(.+)$/i;
|
|
369
|
+
var UNDO_PATTERN = /^undo[:\s]/i;
|
|
370
|
+
var ROLLBACK_PATTERN = /^rollback[:\s]/i;
|
|
371
|
+
var INDIRECT_KEYWORDS = [
|
|
372
|
+
/\brevert(?:s|ed|ing)?\b/i,
|
|
373
|
+
/\broll(?:s|ed|ing)?\s*back\b/i,
|
|
374
|
+
/\bundo(?:es|ne|ing)?\b/i,
|
|
375
|
+
/\bbacked?\s*out\b/i,
|
|
376
|
+
/\bremov(?:e|es|ed|ing)\s+(?:broken|buggy|faulty)\b/i
|
|
377
|
+
];
|
|
378
|
+
var RevertDetector = class {
|
|
379
|
+
detect(commits) {
|
|
380
|
+
const antiPatterns = [];
|
|
381
|
+
for (const commit of commits) {
|
|
382
|
+
if (!commit.message) continue;
|
|
383
|
+
if (this.isRevert(commit.message)) {
|
|
384
|
+
antiPatterns.push({
|
|
385
|
+
hash: commit.hash,
|
|
386
|
+
message: commit.message,
|
|
387
|
+
date: commit.date
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return antiPatterns;
|
|
392
|
+
}
|
|
393
|
+
isRevert(message) {
|
|
394
|
+
if (REVERT_QUOTED_PATTERN.test(message)) return true;
|
|
395
|
+
if (REVERT_PREFIX_PATTERN.test(message)) return true;
|
|
396
|
+
if (UNDO_PATTERN.test(message)) return true;
|
|
397
|
+
if (ROLLBACK_PATTERN.test(message)) return true;
|
|
398
|
+
for (const pattern of INDIRECT_KEYWORDS) {
|
|
399
|
+
if (pattern.test(message)) return true;
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
export {
|
|
406
|
+
SqliteStorageAdapter,
|
|
407
|
+
RevertDetector,
|
|
408
|
+
SimpleGitAdapter
|
|
409
|
+
};
|
|
410
|
+
//# sourceMappingURL=chunk-P7JUSY3I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/storage/sqlite-storage-adapter.ts","../src/adapters/git/simple-git-adapter.ts","../src/core/why-context/revert-detector.ts"],"sourcesContent":["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';\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 console.error('[ctxo:sqlite] Corrupt DB detected, rebuilding from JSON index');\n this.db = new SQL.Database();\n this.createTables();\n this.rebuildFromJson();\n }\n } else {\n this.db = new SQL.Database();\n this.createTables();\n this.rebuildFromJson();\n }\n }\n\n async initEmpty(): Promise<void> {\n const SQL = await initSqlJs();\n this.db = new SQL.Database();\n this.createTables();\n }\n\n private database(): Database {\n if (!this.db) {\n throw new Error('SqliteStorageAdapter not initialized. Call init() first.');\n }\n return this.db;\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 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 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 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 console.error(`[ctxo:sqlite] 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';\n\nexport class SimpleGitAdapter implements IGitPort {\n private readonly git: SimpleGit;\n\n constructor(projectRoot: string) {\n this.git = simpleGit(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): Promise<CommitRecord[]> {\n try {\n const log = await this.git.log({ file: filePath, '--follow': null });\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 console.error(`[ctxo:git] Failed to get history for ${filePath}: ${(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 console.error(`[ctxo:git] Failed to get churn for ${filePath}: ${(err as Error).message}`);\n return { filePath, commitCount: 0 };\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"],"mappings":"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","names":[]}
|