@psnext/lscg 0.1.5 → 0.1.6
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 +30 -6
- package/dist/bin/lscg.js +0 -0
- package/dist/src/cli.js +54 -9
- package/dist/src/explore/sigma-provider.d.ts +27 -0
- package/dist/src/explore/sigma-provider.js +87 -0
- package/dist/src/explore/sigma-render.d.ts +18 -0
- package/dist/src/explore/sigma-render.js +67 -0
- package/dist/src/graph/explore.d.ts +20 -0
- package/dist/src/graph/explore.js +200 -0
- package/dist/src/graph/repository.d.ts +7 -2
- package/dist/src/graph/repository.js +66 -20
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/mcp/server.js +5 -2
- package/dist/src/scanner/attributionPlugin.d.ts +5 -0
- package/dist/src/scanner/attributionPlugin.js +16 -0
- package/dist/src/scanner/discover.js +89 -16
- package/dist/src/scanner/plugins.d.ts +6 -2
- package/dist/src/scanner/plugins.js +8 -1
- package/dist/src/storage/connection.js +22 -0
- package/dist/src/storage/explore-queries.d.ts +52 -0
- package/dist/src/storage/explore-queries.js +184 -0
- package/dist/src/storage/graph-writes.d.ts +8 -2
- package/dist/src/storage/graph-writes.js +55 -33
- package/dist/src/storage/plugin-graph.js +3 -3
- package/dist/src/storage/queries.d.ts +4 -2
- package/dist/src/storage/queries.js +40 -31
- package/dist/src/storage/schema.d.ts +2 -2
- package/dist/src/storage/schema.js +5 -1
- package/dist/src/types.d.ts +10 -2
- package/dist/src/watch.d.ts +3 -0
- package/dist/src/watch.js +5 -2
- package/package.json +1 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { parseMetadata, parsePoint } from './row-decoders.js';
|
|
3
|
+
const EXPLORE_KINDS = ['contains', 'defines', 'calls', 'imports', 'exports'];
|
|
4
|
+
export function selectExploreAggregates(db, repositoryId, search) {
|
|
5
|
+
const predicates = ['f.repository_id = ?'];
|
|
6
|
+
const params = [repositoryId];
|
|
7
|
+
if (search?.trim()) {
|
|
8
|
+
predicates.push('(lower(f.path) LIKE ? OR lower(f.path) = ?)');
|
|
9
|
+
const normalized = search.trim().toLocaleLowerCase();
|
|
10
|
+
params.push(`%${normalized}%`, normalized);
|
|
11
|
+
}
|
|
12
|
+
const rows = db.prepare(`
|
|
13
|
+
SELECT f.id, f.path, f.language, f.repository_id AS repositoryId,
|
|
14
|
+
(SELECT count(*) FROM nodes n WHERE n.repository_id = f.repository_id AND n.file_id = f.id) AS childCount
|
|
15
|
+
FROM files f
|
|
16
|
+
WHERE ${predicates.join(' AND ')}
|
|
17
|
+
ORDER BY lower(f.path), f.id
|
|
18
|
+
`).all(...params);
|
|
19
|
+
return rows.map((row) => ({ ...row, id: aggregateId(repositoryId, row.id), childCount: Number(row.childCount) }));
|
|
20
|
+
}
|
|
21
|
+
export function selectExploreAggregateEdges(db, repositoryId, kinds = EXPLORE_KINDS) {
|
|
22
|
+
const safeKinds = kinds.filter((kind) => EXPLORE_KINDS.includes(kind));
|
|
23
|
+
if (safeKinds.length === 0)
|
|
24
|
+
return [];
|
|
25
|
+
const placeholders = safeKinds.map(() => '?').join(', ');
|
|
26
|
+
const rows = db.prepare(`
|
|
27
|
+
SELECT MIN(e.id) AS id, e.kind,
|
|
28
|
+
'aggregate:' || e.repository_id || ':' || sf.id AS sourceAggregateId,
|
|
29
|
+
'aggregate:' || e.repository_id || ':' || tf.id AS targetAggregateId,
|
|
30
|
+
AVG(e.confidence) AS confidence, count(*) AS count
|
|
31
|
+
FROM edges e
|
|
32
|
+
JOIN nodes s ON s.id = e.source_id AND s.repository_id = e.repository_id
|
|
33
|
+
JOIN nodes t ON t.id = e.target_id AND t.repository_id = e.repository_id
|
|
34
|
+
LEFT JOIN files sf ON sf.id = s.file_id
|
|
35
|
+
LEFT JOIN files tf ON tf.id = t.file_id
|
|
36
|
+
WHERE e.repository_id = ? AND e.kind IN (${placeholders}) AND sf.id IS NOT NULL AND tf.id IS NOT NULL AND sf.id <> tf.id
|
|
37
|
+
GROUP BY sf.id, tf.id, e.kind
|
|
38
|
+
ORDER BY sourceAggregateId, targetAggregateId, e.kind
|
|
39
|
+
`).all(repositoryId, ...safeKinds);
|
|
40
|
+
return rows.map((row) => ({
|
|
41
|
+
id: String(row.id), sourceId: String(row.sourceAggregateId), targetId: String(row.targetAggregateId),
|
|
42
|
+
sourceAggregateId: String(row.sourceAggregateId), targetAggregateId: String(row.targetAggregateId),
|
|
43
|
+
kind: row.kind, confidence: Number(row.confidence ?? 0), count: Number(row.count ?? 0), metadata: {}
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
export function selectExploreSourcesForSearch(db, repositoryId, term, limit = 200) {
|
|
47
|
+
const normalized = term.trim().toLocaleLowerCase();
|
|
48
|
+
if (!normalized)
|
|
49
|
+
return [];
|
|
50
|
+
const rows = db.prepare(`
|
|
51
|
+
SELECT n.id, n.kind, n.type, n.name, n.file_id AS fileId,
|
|
52
|
+
n.start_point AS startPoint, n.end_point AS endPoint, n.metadata_json AS metadataJson, f.path AS path
|
|
53
|
+
FROM nodes n LEFT JOIN files f ON f.id = n.file_id
|
|
54
|
+
WHERE n.repository_id = ? AND (
|
|
55
|
+
lower(COALESCE(n.name, '')) = ? OR
|
|
56
|
+
lower(COALESCE(f.path, '') || ':' || COALESCE(n.name, '')) = ? OR
|
|
57
|
+
lower(COALESCE(n.name, '')) LIKE ? OR
|
|
58
|
+
lower(COALESCE(n.name, '')) LIKE ?
|
|
59
|
+
)
|
|
60
|
+
ORDER BY CASE
|
|
61
|
+
WHEN lower(COALESCE(f.path, '') || ':' || COALESCE(n.name, '')) = ? THEN 0
|
|
62
|
+
WHEN lower(COALESCE(n.name, '')) = ? THEN 1
|
|
63
|
+
WHEN lower(COALESCE(n.name, '')) LIKE ? THEN 2 ELSE 3 END,
|
|
64
|
+
lower(COALESCE(f.path, '')), lower(COALESCE(n.name, '')), n.id
|
|
65
|
+
LIMIT ?
|
|
66
|
+
`).all(repositoryId, normalized, normalized, `${normalized}%`, `%${normalized}%`, normalized, normalized, `${normalized}%`, Math.max(1, Math.min(1000, limit)));
|
|
67
|
+
return rows.map(sourceFromRow);
|
|
68
|
+
}
|
|
69
|
+
export function selectExploreCandidates(db, repositoryId, term, { kind, file, fileAnchor = false, limit = 20 } = {}) {
|
|
70
|
+
const normalized = term.trim().toLocaleLowerCase();
|
|
71
|
+
if (!normalized)
|
|
72
|
+
return [];
|
|
73
|
+
const safeLimit = Math.max(1, Math.min(100, Math.trunc(limit)));
|
|
74
|
+
const explicitFiles = db.prepare('SELECT id, path FROM files WHERE repository_id = ? AND path = ? ORDER BY id LIMIT ?').all(repositoryId, term.trim(), safeLimit);
|
|
75
|
+
if (fileAnchor && explicitFiles.length > 0)
|
|
76
|
+
return explicitFiles.map((row, index) => {
|
|
77
|
+
const fileId = String(row.id);
|
|
78
|
+
const filePath = String(row.path);
|
|
79
|
+
return { id: aggregateId(repositoryId, fileId), kind: 'file', type: 'file', name: filePath, fileId, path: filePath, startPoint: { row: 0, column: 0 }, endPoint: { row: 0, column: 0 }, metadata: {}, rank: index, match: 'path', qualifiedName: filePath, aggregateId: aggregateId(repositoryId, fileId) };
|
|
80
|
+
});
|
|
81
|
+
const stages = [
|
|
82
|
+
{ match: 'exact', predicate: 'lower(COALESCE(n.name, \'\')) = ?', params: [normalized] },
|
|
83
|
+
{ match: 'qualified', predicate: "lower(COALESCE(f.path, '') || ':' || COALESCE(n.name, '')) = ?", params: [normalized] },
|
|
84
|
+
{ match: 'prefix', predicate: 'lower(COALESCE(n.name, \'\')) LIKE ?', params: [`${normalized}%`] },
|
|
85
|
+
{ match: 'substring', predicate: 'lower(COALESCE(n.name, \'\')) LIKE ?', params: [`%${normalized}%`] }
|
|
86
|
+
];
|
|
87
|
+
const result = [];
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
for (const stage of stages) {
|
|
90
|
+
const predicates = ['n.repository_id = ?', stage.predicate];
|
|
91
|
+
const params = [repositoryId, ...stage.params];
|
|
92
|
+
if (kind) {
|
|
93
|
+
predicates.push('n.kind = ?');
|
|
94
|
+
params.push(kind);
|
|
95
|
+
}
|
|
96
|
+
if (file) {
|
|
97
|
+
predicates.push('f.path = ?');
|
|
98
|
+
params.push(file);
|
|
99
|
+
}
|
|
100
|
+
const rows = db.prepare(`
|
|
101
|
+
SELECT n.id, n.kind, n.type, n.name, n.file_id AS fileId,
|
|
102
|
+
n.start_point AS startPoint, n.end_point AS endPoint, n.metadata_json AS metadataJson, f.path AS path
|
|
103
|
+
FROM nodes n LEFT JOIN files f ON f.id = n.file_id
|
|
104
|
+
WHERE ${predicates.join(' AND ')}
|
|
105
|
+
ORDER BY lower(COALESCE(f.path, '')), lower(COALESCE(n.name, '')), n.id LIMIT ?
|
|
106
|
+
`).all(...params, safeLimit);
|
|
107
|
+
for (const row of rows) {
|
|
108
|
+
const source = sourceFromRow(row);
|
|
109
|
+
if (seen.has(source.id))
|
|
110
|
+
continue;
|
|
111
|
+
seen.add(source.id);
|
|
112
|
+
result.push({ ...source, rank: result.length, match: stage.match, qualifiedName: qualifiedName(source), aggregateId: source.fileId ? aggregateId(repositoryId, source.fileId) : null });
|
|
113
|
+
}
|
|
114
|
+
if (result.length > 0)
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
// A path is an explicit file identity and resolves to its aggregate.
|
|
118
|
+
for (const row of explicitFiles) {
|
|
119
|
+
const fileId = String(row.id);
|
|
120
|
+
const id = aggregateId(repositoryId, fileId);
|
|
121
|
+
if (seen.has(id))
|
|
122
|
+
continue;
|
|
123
|
+
result.push({ id, kind: 'file', type: 'file', name: String(row.path), fileId, path: String(row.path), startPoint: { row: 0, column: 0 }, endPoint: { row: 0, column: 0 }, metadata: {}, rank: result.length, match: 'path', qualifiedName: String(row.path), aggregateId: id });
|
|
124
|
+
}
|
|
125
|
+
return result.slice(0, safeLimit);
|
|
126
|
+
}
|
|
127
|
+
export function selectExploreNode(db, repositoryId, id) {
|
|
128
|
+
const row = db.prepare(`
|
|
129
|
+
SELECT n.id, n.kind, n.type, n.name, n.file_id AS fileId,
|
|
130
|
+
n.start_point AS startPoint, n.end_point AS endPoint, n.metadata_json AS metadataJson, f.path AS path
|
|
131
|
+
FROM nodes n LEFT JOIN files f ON f.id = n.file_id WHERE n.repository_id = ? AND n.id = ?
|
|
132
|
+
`).get(repositoryId, id);
|
|
133
|
+
return row ? sourceFromRow(row) : null;
|
|
134
|
+
}
|
|
135
|
+
export function selectExploreTraversalEdges(db, repositoryId, frontier, direction, kinds, limit) {
|
|
136
|
+
if (frontier.length === 0 || limit <= 0)
|
|
137
|
+
return [];
|
|
138
|
+
const safeKinds = kinds.filter((kind) => EXPLORE_KINDS.includes(kind));
|
|
139
|
+
if (safeKinds.length === 0)
|
|
140
|
+
return [];
|
|
141
|
+
const nodePlaceholders = frontier.map(() => '?').join(', ');
|
|
142
|
+
const kindPlaceholders = safeKinds.map(() => '?').join(', ');
|
|
143
|
+
const adjacency = direction === 'incoming'
|
|
144
|
+
? `(e.target_id IN (${nodePlaceholders}))`
|
|
145
|
+
: direction === 'outgoing'
|
|
146
|
+
? `(e.source_id IN (${nodePlaceholders}))`
|
|
147
|
+
: `(e.source_id IN (${nodePlaceholders}) OR e.target_id IN (${nodePlaceholders}))`;
|
|
148
|
+
const nodeParams = direction === 'both' ? [...frontier, ...frontier] : [...frontier];
|
|
149
|
+
const rows = db.prepare(`
|
|
150
|
+
SELECT e.id, e.source_id AS sourceId, e.target_id AS targetId, e.kind, e.confidence, e.metadata_json AS metadataJson
|
|
151
|
+
FROM edges e
|
|
152
|
+
WHERE e.repository_id = ? AND e.kind IN (${kindPlaceholders}) AND ${adjacency}
|
|
153
|
+
ORDER BY e.kind, e.id
|
|
154
|
+
LIMIT ?
|
|
155
|
+
`).all(repositoryId, ...safeKinds, ...nodeParams, Math.max(1, limit));
|
|
156
|
+
return rows.map((row) => ({ id: String(row.id), sourceId: String(row.sourceId), targetId: String(row.targetId), kind: row.kind, confidence: Number(row.confidence ?? 0), metadata: parseMetadata(row.metadataJson) }));
|
|
157
|
+
}
|
|
158
|
+
export function selectExploreNodeRows(db, repositoryId, ids) {
|
|
159
|
+
if (ids.length === 0)
|
|
160
|
+
return [];
|
|
161
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
162
|
+
const rows = db.prepare(`
|
|
163
|
+
SELECT n.id, n.kind, n.type, n.name, n.file_id AS fileId,
|
|
164
|
+
n.start_point AS startPoint, n.end_point AS endPoint, n.metadata_json AS metadataJson, f.path AS path
|
|
165
|
+
FROM nodes n LEFT JOIN files f ON f.id = n.file_id
|
|
166
|
+
WHERE n.repository_id = ? AND n.id IN (${placeholders})
|
|
167
|
+
`).all(repositoryId, ...ids);
|
|
168
|
+
const byId = new Map(rows.map((row) => [String(row.id), sourceFromRow(row)]));
|
|
169
|
+
return ids.flatMap((id) => byId.get(id) ? [byId.get(id)] : []);
|
|
170
|
+
}
|
|
171
|
+
export function aggregateId(repositoryId, fileId) {
|
|
172
|
+
return `aggregate:${repositoryId}:${fileId}`;
|
|
173
|
+
}
|
|
174
|
+
function sourceFromRow(row) {
|
|
175
|
+
return {
|
|
176
|
+
id: String(row.id), kind: row.kind, type: String(row.type), name: row.name == null ? null : String(row.name),
|
|
177
|
+
fileId: row.fileId == null ? null : String(row.fileId), path: row.path == null ? null : String(row.path),
|
|
178
|
+
startPoint: parsePoint(row.startPoint), endPoint: parsePoint(row.endPoint), metadata: parseMetadata(row.metadataJson)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function qualifiedName(source) {
|
|
182
|
+
return source.name && source.path ? `${source.path}:${source.name}` : source.name;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=explore-queries.js.map
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import type { AttributionApplyResult, AttributionOutcome, FileRecord, GraphEdge, GraphNode, RepositoryRecord } from '../types.js';
|
|
3
3
|
export declare function upsertRepository(db: DatabaseSync, repository: RepositoryRecord): void;
|
|
4
|
-
export declare function replaceFileGraph(db: DatabaseSync, { repository, file, nodes, edges }: {
|
|
4
|
+
export declare function replaceFileGraph(db: DatabaseSync, { repository, file, nodes, edges, enrichment }: {
|
|
5
5
|
repository: RepositoryRecord;
|
|
6
6
|
file: FileRecord;
|
|
7
7
|
nodes: GraphNode[];
|
|
8
8
|
edges: GraphEdge[];
|
|
9
|
+
enrichment?: {
|
|
10
|
+
pluginName: string;
|
|
11
|
+
historyFingerprint: string | null;
|
|
12
|
+
};
|
|
9
13
|
}): void;
|
|
10
14
|
/**
|
|
11
15
|
* Applies optional attribution only when it still matches the structural file
|
|
12
16
|
* hash. Failed outcomes never mutate existing attribution.
|
|
13
17
|
*/
|
|
14
|
-
export declare function applyFileAttribution(db: DatabaseSync, { repository, fileId, sourceHash, outcome }: {
|
|
18
|
+
export declare function applyFileAttribution(db: DatabaseSync, { repository, fileId, sourceHash, outcome, pluginName, historyFingerprint }: {
|
|
15
19
|
repository: RepositoryRecord;
|
|
16
20
|
fileId: string;
|
|
17
21
|
sourceHash: string;
|
|
18
22
|
outcome: AttributionOutcome;
|
|
23
|
+
pluginName?: string;
|
|
24
|
+
historyFingerprint?: string | null;
|
|
19
25
|
}): AttributionApplyResult;
|
|
20
26
|
export declare function reconcileDeletedFiles(db: DatabaseSync, repositoryId: string, discoveredPaths: string[]): void;
|
|
21
27
|
export declare function recordFileScanFailure(db: DatabaseSync, repositoryId: string, filePath: string, diagnostic: string): void;
|
|
@@ -10,7 +10,7 @@ export function upsertRepository(db, repository) {
|
|
|
10
10
|
updated_at = datetime('now')
|
|
11
11
|
`).run(repository.id, repository.root, repository.name);
|
|
12
12
|
}
|
|
13
|
-
export function replaceFileGraph(db, { repository, file, nodes, edges }) {
|
|
13
|
+
export function replaceFileGraph(db, { repository, file, nodes, edges, enrichment }) {
|
|
14
14
|
upsertRepository(db, repository);
|
|
15
15
|
db.exec('BEGIN IMMEDIATE');
|
|
16
16
|
try {
|
|
@@ -42,14 +42,15 @@ export function replaceFileGraph(db, { repository, file, nodes, edges }) {
|
|
|
42
42
|
}
|
|
43
43
|
const insertEdge = db.prepare(`
|
|
44
44
|
INSERT INTO edges(
|
|
45
|
-
id, repository_id, file_id, source_id, target_id, kind, confidence, metadata_json
|
|
46
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
45
|
+
id, repository_id, file_id, source_id, target_id, kind, type, confidence, metadata_json
|
|
46
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
47
47
|
`);
|
|
48
48
|
for (const edge of edges) {
|
|
49
|
-
insertEdge.run(edge.id, repository.id, file.id, edge.sourceId, edge.targetId, edge.kind, edge.confidence ?? 1, JSON.stringify(edge.metadata ?? {}));
|
|
49
|
+
insertEdge.run(edge.id, repository.id, file.id, edge.sourceId, edge.targetId, edge.kind, edge.type ?? edge.kind, edge.confidence ?? 1, JSON.stringify(edge.metadata ?? {}));
|
|
50
50
|
}
|
|
51
51
|
restorePriorAttribution(db, file.id, new Set(nodes.map((node) => node.id)), priorAttribution);
|
|
52
|
-
|
|
52
|
+
if (enrichment)
|
|
53
|
+
queueFileEnrichment(db, repository.id, file.id, file.hash, enrichment.pluginName, enrichment.historyFingerprint);
|
|
53
54
|
db.exec('COMMIT');
|
|
54
55
|
}
|
|
55
56
|
catch (error) {
|
|
@@ -61,27 +62,34 @@ export function replaceFileGraph(db, { repository, file, nodes, edges }) {
|
|
|
61
62
|
* Applies optional attribution only when it still matches the structural file
|
|
62
63
|
* hash. Failed outcomes never mutate existing attribution.
|
|
63
64
|
*/
|
|
64
|
-
export function applyFileAttribution(db, { repository, fileId, sourceHash, outcome }) {
|
|
65
|
+
export function applyFileAttribution(db, { repository, fileId, sourceHash, outcome, pluginName = 'git-attribution-plugin', historyFingerprint = null }) {
|
|
65
66
|
db.exec('BEGIN IMMEDIATE');
|
|
66
67
|
try {
|
|
67
68
|
const current = db.prepare(`
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
SELECT f.hash AS sourceHash, e.source_hash AS enrichmentHash,
|
|
70
|
+
e.plugin_name AS pluginName, e.history_fingerprint AS historyFingerprint
|
|
71
|
+
FROM files f LEFT JOIN file_enrichment_state e ON e.file_id = f.id
|
|
72
|
+
WHERE f.id = ? AND f.repository_id = ?
|
|
73
|
+
`).get(fileId, repository.id);
|
|
74
|
+
if (current?.sourceHash === sourceHash && current.enrichmentHash === undefined) {
|
|
75
|
+
queueFileEnrichment(db, repository.id, fileId, sourceHash, pluginName, historyFingerprint);
|
|
76
|
+
}
|
|
77
|
+
const refreshed = current?.enrichmentHash === undefined
|
|
78
|
+
? { ...current, enrichmentHash: sourceHash, pluginName, historyFingerprint }
|
|
79
|
+
: current;
|
|
80
|
+
if (refreshed?.sourceHash !== sourceHash || refreshed.enrichmentHash !== sourceHash || refreshed.pluginName !== pluginName || refreshed.historyFingerprint !== historyFingerprint) {
|
|
73
81
|
db.exec('ROLLBACK');
|
|
74
82
|
return 'stale';
|
|
75
83
|
}
|
|
76
84
|
if (outcome.status === 'complete') {
|
|
77
85
|
replaceAttribution(db, repository, fileId, outcome.attribution);
|
|
78
|
-
setFileEnrichmentState(db, repository.id, fileId, 'complete', null);
|
|
86
|
+
setFileEnrichmentState(db, repository.id, fileId, 'complete', 'complete', null);
|
|
79
87
|
}
|
|
80
88
|
else if (outcome.status === 'unavailable') {
|
|
81
|
-
setFileEnrichmentState(db, repository.id, fileId, 'complete',
|
|
89
|
+
setFileEnrichmentState(db, repository.id, fileId, 'complete', 'unavailable', `unavailable:${outcome.reason}`);
|
|
82
90
|
}
|
|
83
91
|
else {
|
|
84
|
-
setFileEnrichmentState(db, repository.id, fileId, 'failed', outcome.diagnostic);
|
|
92
|
+
setFileEnrichmentState(db, repository.id, fileId, 'failed', 'failed', outcome.diagnostic);
|
|
85
93
|
}
|
|
86
94
|
db.exec('COMMIT');
|
|
87
95
|
return 'applied';
|
|
@@ -136,49 +144,63 @@ export function selectFileInventory(db, repositoryId) {
|
|
|
136
144
|
function captureFileAttribution(db, fileId) {
|
|
137
145
|
return {
|
|
138
146
|
lastModified: db.prepare(`
|
|
139
|
-
SELECT id AS nodeId, last_modified_user_id AS userId
|
|
147
|
+
SELECT id AS nodeId, last_modified_user_id AS userId,
|
|
148
|
+
kind || ':' || type || ':' || COALESCE(name, '') AS key
|
|
140
149
|
FROM nodes
|
|
141
150
|
WHERE file_id = ? AND last_modified_user_id IS NOT NULL
|
|
142
151
|
`).all(fileId),
|
|
143
152
|
edges: db.prepare(`
|
|
144
|
-
SELECT id, source_id AS sourceId,
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
SELECT e.id, e.source_id AS sourceId,
|
|
154
|
+
n.kind || ':' || n.type || ':' || COALESCE(n.name, '') AS sourceKey,
|
|
155
|
+
e.target_id AS targetId, e.confidence, e.metadata_json AS metadataJson
|
|
156
|
+
FROM edges e JOIN nodes n ON n.id = e.source_id
|
|
157
|
+
WHERE e.file_id = ? AND e.kind = 'attributed_to'
|
|
147
158
|
`).all(fileId)
|
|
148
159
|
};
|
|
149
160
|
}
|
|
150
161
|
function restorePriorAttribution(db, fileId, currentNodeIds, prior) {
|
|
162
|
+
const currentByKey = new Map(db.prepare(`
|
|
163
|
+
SELECT id, kind || ':' || type || ':' || COALESCE(name, '') AS key
|
|
164
|
+
FROM nodes WHERE file_id = ?
|
|
165
|
+
`).all(fileId).map((row) => [row.key, row.id]));
|
|
166
|
+
const mappedNodeId = (nodeId, key) => currentNodeIds.has(nodeId) ? nodeId : currentByKey.get(key);
|
|
151
167
|
const updateLastModified = db.prepare('UPDATE nodes SET last_modified_user_id = ? WHERE id = ? AND file_id = ?');
|
|
152
168
|
for (const entry of prior.lastModified) {
|
|
153
|
-
|
|
154
|
-
|
|
169
|
+
const currentId = mappedNodeId(entry.nodeId, entry.key);
|
|
170
|
+
if (currentId)
|
|
171
|
+
updateLastModified.run(entry.userId, currentId, fileId);
|
|
155
172
|
}
|
|
156
173
|
const insertEdge = db.prepare(`
|
|
157
|
-
INSERT OR IGNORE INTO edges(id, repository_id, file_id, source_id, target_id, kind, confidence, metadata_json)
|
|
158
|
-
SELECT ?, repository_id, ?, ?, ?, 'attributed_to', ?, ?
|
|
174
|
+
INSERT OR IGNORE INTO edges(id, repository_id, file_id, source_id, target_id, kind, type, confidence, metadata_json)
|
|
175
|
+
SELECT ?, repository_id, ?, ?, ?, 'attributed_to', 'attributed_to', ?, ?
|
|
159
176
|
FROM files WHERE id = ?
|
|
160
177
|
`);
|
|
161
178
|
for (const edge of prior.edges) {
|
|
162
|
-
|
|
163
|
-
|
|
179
|
+
const currentSourceId = mappedNodeId(edge.sourceId, edge.sourceKey);
|
|
180
|
+
if (currentSourceId) {
|
|
181
|
+
const edgeId = currentSourceId === edge.sourceId ? edge.id : hashParts([fileId, currentSourceId, edge.targetId, 'attributed_to']);
|
|
182
|
+
insertEdge.run(edgeId, fileId, currentSourceId, edge.targetId, edge.confidence, edge.metadataJson, fileId);
|
|
164
183
|
}
|
|
165
184
|
}
|
|
166
185
|
}
|
|
167
|
-
function queueFileEnrichment(db, repositoryId, fileId, sourceHash) {
|
|
186
|
+
function queueFileEnrichment(db, repositoryId, fileId, sourceHash, pluginName, historyFingerprint) {
|
|
168
187
|
db.prepare(`
|
|
169
188
|
INSERT INTO file_enrichment_state(
|
|
170
|
-
repository_id, file_id, source_hash, state, diagnostic, queued_at, updated_at, completed_at, failed_at
|
|
171
|
-
) VALUES (?, ?, ?, 'pending', NULL, datetime('now'), datetime('now'), NULL, NULL)
|
|
189
|
+
repository_id, file_id, plugin_name, source_hash, history_fingerprint, state, outcome, diagnostic, queued_at, updated_at, completed_at, failed_at
|
|
190
|
+
) VALUES (?, ?, ?, ?, ?, 'pending', NULL, NULL, datetime('now'), datetime('now'), NULL, NULL)
|
|
172
191
|
ON CONFLICT(file_id) DO UPDATE SET
|
|
173
192
|
repository_id = excluded.repository_id,
|
|
193
|
+
plugin_name = excluded.plugin_name,
|
|
174
194
|
source_hash = excluded.source_hash,
|
|
195
|
+
history_fingerprint = excluded.history_fingerprint,
|
|
175
196
|
state = 'pending',
|
|
197
|
+
outcome = NULL,
|
|
176
198
|
diagnostic = NULL,
|
|
177
199
|
queued_at = datetime('now'),
|
|
178
200
|
updated_at = datetime('now'),
|
|
179
201
|
completed_at = NULL,
|
|
180
202
|
failed_at = NULL
|
|
181
|
-
`).run(repositoryId, fileId, sourceHash);
|
|
203
|
+
`).run(repositoryId, fileId, pluginName, sourceHash, historyFingerprint);
|
|
182
204
|
}
|
|
183
205
|
function replaceAttribution(db, repository, fileId, attribution) {
|
|
184
206
|
upsertContributorNodes(db, repository, attribution.contributorEmails);
|
|
@@ -186,8 +208,8 @@ function replaceAttribution(db, repository, fileId, attribution) {
|
|
|
186
208
|
db.prepare('UPDATE nodes SET last_modified_user_id = NULL WHERE file_id = ?').run(fileId);
|
|
187
209
|
const updateLastModified = db.prepare('UPDATE nodes SET last_modified_user_id = ? WHERE id = ? AND file_id = ?');
|
|
188
210
|
const insertEdge = db.prepare(`
|
|
189
|
-
INSERT INTO edges(id, repository_id, file_id, source_id, target_id, kind, confidence, metadata_json)
|
|
190
|
-
VALUES (?, ?, ?, ?, ?, 'attributed_to', 1, ?)
|
|
211
|
+
INSERT INTO edges(id, repository_id, file_id, source_id, target_id, kind, type, confidence, metadata_json)
|
|
212
|
+
VALUES (?, ?, ?, ?, ?, 'attributed_to', 'attributed_to', 1, ?)
|
|
191
213
|
`);
|
|
192
214
|
for (const entry of attribution.nodeAttributions) {
|
|
193
215
|
const lastModifiedUserId = resolveUserId(repository, entry.lastModifiedEmail);
|
|
@@ -201,14 +223,14 @@ function replaceAttribution(db, repository, fileId, attribution) {
|
|
|
201
223
|
}
|
|
202
224
|
}
|
|
203
225
|
}
|
|
204
|
-
function setFileEnrichmentState(db, repositoryId, fileId, state, diagnostic) {
|
|
226
|
+
function setFileEnrichmentState(db, repositoryId, fileId, state, outcome, diagnostic) {
|
|
205
227
|
db.prepare(`
|
|
206
228
|
UPDATE file_enrichment_state
|
|
207
|
-
SET state = ?, diagnostic = ?, updated_at = datetime('now'),
|
|
229
|
+
SET state = ?, outcome = ?, diagnostic = ?, updated_at = datetime('now'),
|
|
208
230
|
completed_at = CASE WHEN ? = 'complete' THEN datetime('now') ELSE NULL END,
|
|
209
231
|
failed_at = CASE WHEN ? = 'failed' THEN datetime('now') ELSE NULL END
|
|
210
232
|
WHERE repository_id = ? AND file_id = ?
|
|
211
|
-
`).run(state, diagnostic, state, state, repositoryId, fileId);
|
|
233
|
+
`).run(state, outcome, diagnostic, state, state, repositoryId, fileId);
|
|
212
234
|
}
|
|
213
235
|
function upsertContributorNodes(db, repository, contributorEmails) {
|
|
214
236
|
const insertNode = db.prepare(`
|
|
@@ -13,8 +13,8 @@ export function reconcilePluginEdges(db, repository, edges, fileIdByPath, active
|
|
|
13
13
|
`).run(repository.id, plugin);
|
|
14
14
|
}
|
|
15
15
|
const insert = db.prepare(`
|
|
16
|
-
INSERT OR REPLACE INTO edges(id, repository_id, file_id, source_id, target_id, kind, confidence, metadata_json)
|
|
17
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
16
|
+
INSERT OR REPLACE INTO edges(id, repository_id, file_id, source_id, target_id, kind, type, confidence, metadata_json)
|
|
17
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
18
18
|
`);
|
|
19
19
|
for (const edge of edges) {
|
|
20
20
|
const plugin = typeof edge.metadata.plugin === 'string' ? edge.metadata.plugin : undefined;
|
|
@@ -29,7 +29,7 @@ export function reconcilePluginEdges(db, repository, edges, fileIdByPath, active
|
|
|
29
29
|
const requiredEndpoints = edge.sourceId === edge.targetId ? 1 : 2;
|
|
30
30
|
if (endpoints.count !== requiredEndpoints)
|
|
31
31
|
continue;
|
|
32
|
-
insert.run(edge.id, repository.id, fileId, edge.sourceId, edge.targetId, edge.kind, edge.confidence ?? 1, JSON.stringify(edge.metadata ?? {}));
|
|
32
|
+
insert.run(edge.id, repository.id, fileId, edge.sourceId, edge.targetId, edge.kind, edge.type ?? edge.kind, edge.confidence ?? 1, JSON.stringify(edge.metadata ?? {}));
|
|
33
33
|
inserted += 1;
|
|
34
34
|
}
|
|
35
35
|
db.exec('COMMIT');
|
|
@@ -19,12 +19,14 @@ export declare function selectAllNodeTextRows(db: DatabaseSync, repositoryId: st
|
|
|
19
19
|
export declare function selectAllNodes(db: DatabaseSync, repositoryId: string, { kind }?: {
|
|
20
20
|
kind?: string | undefined;
|
|
21
21
|
}): GraphNodeRow[];
|
|
22
|
-
export declare function selectEdges(db: DatabaseSync, repositoryId: string, { kind, limit }?: {
|
|
22
|
+
export declare function selectEdges(db: DatabaseSync, repositoryId: string, { kind, type, limit }?: {
|
|
23
23
|
kind?: string | undefined;
|
|
24
|
+
type?: string | undefined;
|
|
24
25
|
limit?: number | undefined;
|
|
25
26
|
}): GraphEdgeRow[];
|
|
26
|
-
export declare function selectAllEdges(db: DatabaseSync, repositoryId: string, { kind }?: {
|
|
27
|
+
export declare function selectAllEdges(db: DatabaseSync, repositoryId: string, { kind, type }?: {
|
|
27
28
|
kind?: string | undefined;
|
|
29
|
+
type?: string | undefined;
|
|
28
30
|
}): GraphEdgeRow[];
|
|
29
31
|
export declare function clampLimit(limit: number | undefined, max?: number): number;
|
|
30
32
|
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -13,8 +13,9 @@ export function selectFreshnessReport(db, repositoryId) {
|
|
|
13
13
|
}
|
|
14
14
|
export function selectFileEnrichmentState(db, repositoryId, fileId) {
|
|
15
15
|
const row = db.prepare(`
|
|
16
|
-
SELECT repository_id AS repositoryId, file_id AS fileId,
|
|
17
|
-
|
|
16
|
+
SELECT repository_id AS repositoryId, file_id AS fileId, plugin_name AS pluginName,
|
|
17
|
+
source_hash AS sourceHash, history_fingerprint AS historyFingerprint,
|
|
18
|
+
state, outcome, diagnostic, queued_at AS queuedAt, updated_at AS updatedAt,
|
|
18
19
|
completed_at AS completedAt, failed_at AS failedAt
|
|
19
20
|
FROM file_enrichment_state
|
|
20
21
|
WHERE repository_id = ? AND file_id = ?
|
|
@@ -26,8 +27,9 @@ export function selectFileEnrichmentStates(db, repositoryId, states = ['pending'
|
|
|
26
27
|
return [];
|
|
27
28
|
const placeholders = states.map(() => '?').join(', ');
|
|
28
29
|
return db.prepare(`
|
|
29
|
-
SELECT repository_id AS repositoryId, file_id AS fileId,
|
|
30
|
-
|
|
30
|
+
SELECT repository_id AS repositoryId, file_id AS fileId, plugin_name AS pluginName,
|
|
31
|
+
source_hash AS sourceHash, history_fingerprint AS historyFingerprint,
|
|
32
|
+
state, outcome, diagnostic, queued_at AS queuedAt, updated_at AS updatedAt,
|
|
31
33
|
completed_at AS completedAt, failed_at AS failedAt
|
|
32
34
|
FROM file_enrichment_state
|
|
33
35
|
WHERE repository_id = ? AND state IN (${placeholders})
|
|
@@ -39,8 +41,9 @@ export function selectEnrichmentWork(db, repositoryId, states = ['pending', 'fai
|
|
|
39
41
|
return [];
|
|
40
42
|
const placeholders = states.map(() => '?').join(', ');
|
|
41
43
|
return db.prepare(`
|
|
42
|
-
SELECT e.repository_id AS repositoryId, e.file_id AS fileId, e.
|
|
43
|
-
e.
|
|
44
|
+
SELECT e.repository_id AS repositoryId, e.file_id AS fileId, e.plugin_name AS pluginName,
|
|
45
|
+
e.source_hash AS sourceHash, e.history_fingerprint AS historyFingerprint,
|
|
46
|
+
e.state, e.outcome, e.diagnostic, e.queued_at AS queuedAt, e.updated_at AS updatedAt,
|
|
44
47
|
e.completed_at AS completedAt, e.failed_at AS failedAt, f.path, f.language
|
|
45
48
|
FROM file_enrichment_state e
|
|
46
49
|
JOIN files f ON f.id = e.file_id
|
|
@@ -51,11 +54,14 @@ export function selectEnrichmentWork(db, repositoryId, states = ['pending', 'fai
|
|
|
51
54
|
export function selectEnrichmentSummary(db, repositoryId) {
|
|
52
55
|
const states = selectFileEnrichmentStates(db, repositoryId, ['pending', 'complete', 'failed']);
|
|
53
56
|
const failedEntries = states.filter((entry) => entry.state === 'failed');
|
|
57
|
+
const unavailable = states.filter((entry) => entry.outcome === 'unavailable');
|
|
54
58
|
return {
|
|
55
|
-
state: failedEntries.length > 0 ? 'failed' : states.some((entry) => entry.state === 'pending') ? 'pending' : 'complete',
|
|
59
|
+
state: states.length === 0 ? 'disabled' : failedEntries.length > 0 ? 'failed' : states.some((entry) => entry.state === 'pending') ? 'pending' : 'complete',
|
|
56
60
|
pending: states.filter((entry) => entry.state === 'pending').length,
|
|
57
|
-
complete: states.filter((entry) => entry.state === 'complete').length,
|
|
61
|
+
complete: states.filter((entry) => entry.state === 'complete' && entry.outcome !== 'unavailable').length,
|
|
58
62
|
failed: failedEntries.length,
|
|
63
|
+
unavailable: unavailable.length,
|
|
64
|
+
notApplicable: unavailable.filter((entry) => entry.diagnostic === 'unavailable:not_applicable').length,
|
|
59
65
|
diagnostics: failedEntries.flatMap((entry) => entry.diagnostic ? [entry.diagnostic] : [])
|
|
60
66
|
};
|
|
61
67
|
}
|
|
@@ -153,40 +159,43 @@ export function selectAllNodes(db, repositoryId, { kind } = {}) {
|
|
|
153
159
|
ORDER BY kind, name IS NULL, name, id
|
|
154
160
|
`).all(repositoryId);
|
|
155
161
|
}
|
|
156
|
-
export function selectEdges(db, repositoryId, { kind, limit = 50 } = {}) {
|
|
162
|
+
export function selectEdges(db, repositoryId, { kind, type, limit = 50 } = {}) {
|
|
157
163
|
const safeLimit = clampLimit(limit);
|
|
164
|
+
const where = ['repository_id = ?'];
|
|
165
|
+
const parameters = [repositoryId];
|
|
158
166
|
if (kind) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
`).all(repositoryId, kind, safeLimit);
|
|
167
|
+
where.push('kind = ?');
|
|
168
|
+
parameters.push(kind);
|
|
169
|
+
}
|
|
170
|
+
if (type) {
|
|
171
|
+
where.push('type = ?');
|
|
172
|
+
parameters.push(type);
|
|
166
173
|
}
|
|
167
174
|
return db.prepare(`
|
|
168
|
-
SELECT id, kind, source_id AS sourceId, target_id AS targetId, file_id AS fileId, confidence, metadata_json AS metadataJson
|
|
175
|
+
SELECT id, kind, type, source_id AS sourceId, target_id AS targetId, file_id AS fileId, confidence, metadata_json AS metadataJson
|
|
169
176
|
FROM edges
|
|
170
|
-
WHERE
|
|
171
|
-
ORDER BY kind, id
|
|
177
|
+
WHERE ${where.join(' AND ')}
|
|
178
|
+
ORDER BY kind, type, id
|
|
172
179
|
LIMIT ?
|
|
173
|
-
`).all(
|
|
180
|
+
`).all(...parameters, safeLimit);
|
|
174
181
|
}
|
|
175
|
-
export function selectAllEdges(db, repositoryId, { kind } = {}) {
|
|
182
|
+
export function selectAllEdges(db, repositoryId, { kind, type } = {}) {
|
|
183
|
+
const where = ['repository_id = ?'];
|
|
184
|
+
const parameters = [repositoryId];
|
|
176
185
|
if (kind) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
186
|
+
where.push('kind = ?');
|
|
187
|
+
parameters.push(kind);
|
|
188
|
+
}
|
|
189
|
+
if (type) {
|
|
190
|
+
where.push('type = ?');
|
|
191
|
+
parameters.push(type);
|
|
183
192
|
}
|
|
184
193
|
return db.prepare(`
|
|
185
|
-
SELECT id, kind, source_id AS sourceId, target_id AS targetId, file_id AS fileId, confidence, metadata_json AS metadataJson
|
|
194
|
+
SELECT id, kind, type, source_id AS sourceId, target_id AS targetId, file_id AS fileId, confidence, metadata_json AS metadataJson
|
|
186
195
|
FROM edges
|
|
187
|
-
WHERE
|
|
188
|
-
ORDER BY kind, id
|
|
189
|
-
`).all(
|
|
196
|
+
WHERE ${where.join(' AND ')}
|
|
197
|
+
ORDER BY kind, type, id
|
|
198
|
+
`).all(...parameters);
|
|
190
199
|
}
|
|
191
200
|
export function clampLimit(limit, max = 500) {
|
|
192
201
|
const value = Number(limit) || 50;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SCHEMA_VERSION =
|
|
2
|
-
export declare const CREATE_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS schema_migrations (\n version INTEGER PRIMARY KEY,\n applied_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS repositories (\n id TEXT PRIMARY KEY,\n root TEXT NOT NULL,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS files (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n path TEXT NOT NULL,\n language TEXT NOT NULL,\n hash TEXT NOT NULL,\n size INTEGER NOT NULL,\n mtime_ms INTEGER NOT NULL,\n scanned_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE(repository_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n file_id TEXT REFERENCES files(id) ON DELETE CASCADE,\n kind TEXT NOT NULL,\n type TEXT NOT NULL,\n name TEXT,\n start_byte INTEGER NOT NULL,\n end_byte INTEGER NOT NULL,\n start_point TEXT NOT NULL,\n end_point TEXT NOT NULL,\n source_hash TEXT NOT NULL,\n parser TEXT NOT NULL,\n parser_version TEXT NOT NULL,\n metadata_json TEXT NOT NULL DEFAULT '{}',\n last_modified_user_id TEXT\n);\n\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n file_id TEXT REFERENCES files(id) ON DELETE CASCADE,\n source_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n target_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n kind TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 1.0,\n metadata_json TEXT NOT NULL DEFAULT '{}'\n);\n\nCREATE TABLE IF NOT EXISTS overlays (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n subject_id TEXT NOT NULL,\n subject_kind TEXT NOT NULL,\n operation TEXT NOT NULL,\n metadata_json TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_files_repo_path ON files(repository_id, path);\nCREATE INDEX IF NOT EXISTS idx_nodes_repo_kind ON nodes(repository_id, kind);\nCREATE INDEX IF NOT EXISTS idx_nodes_repo_name ON nodes(repository_id, name);\nCREATE INDEX IF NOT EXISTS idx_edges_repo_kind ON edges(repository_id, kind);\nCREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);\nCREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);\n\nCREATE TABLE IF NOT EXISTS plugin_contributions (\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n plugin_name TEXT NOT NULL,\n generation INTEGER NOT NULL,\n nodes_json TEXT NOT NULL,\n edges_json TEXT NOT NULL,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY(repository_id, plugin_name)\n);\nCREATE INDEX IF NOT EXISTS idx_plugin_contributions_repo ON plugin_contributions(repository_id);\n\nCREATE TABLE IF NOT EXISTS file_scan_failures (\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n path TEXT NOT NULL,\n diagnostic TEXT NOT NULL,\n failed_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY(repository_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS repository_scan_state (\n repository_id TEXT PRIMARY KEY REFERENCES repositories(id) ON DELETE CASCADE,\n state TEXT NOT NULL CHECK(state IN ('fresh', 'degraded', 'stale')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS file_enrichment_state (\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n file_id TEXT PRIMARY KEY REFERENCES files(id) ON DELETE CASCADE,\n source_hash TEXT NOT NULL,\n state TEXT NOT NULL CHECK(state IN ('pending', 'complete', 'failed')),\n diagnostic TEXT,\n queued_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n completed_at TEXT,\n failed_at TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_file_enrichment_state_repo_state ON file_enrichment_state(repository_id, state);\n\nCREATE TABLE IF NOT EXISTS repository_manifest_inventory (\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n path TEXT NOT NULL,\n kind TEXT NOT NULL CHECK(kind IN ('maven-pom', 'gradle-groovy', 'gradle-kotlin')),\n module_identity TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('observed', 'read_failed', 'traversal_incomplete', 'confirmed_deleted')),\n size INTEGER NOT NULL,\n mtime_ms INTEGER NOT NULL,\n source_hash TEXT,\n generation INTEGER NOT NULL,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY(repository_id, path)\n);\nCREATE INDEX IF NOT EXISTS idx_repository_manifest_inventory_repo_status\n ON repository_manifest_inventory(repository_id, status);\n";
|
|
1
|
+
export declare const SCHEMA_VERSION = 9;
|
|
2
|
+
export declare const CREATE_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS schema_migrations (\n version INTEGER PRIMARY KEY,\n applied_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS repositories (\n id TEXT PRIMARY KEY,\n root TEXT NOT NULL,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS files (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n path TEXT NOT NULL,\n language TEXT NOT NULL,\n hash TEXT NOT NULL,\n size INTEGER NOT NULL,\n mtime_ms INTEGER NOT NULL,\n scanned_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE(repository_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n file_id TEXT REFERENCES files(id) ON DELETE CASCADE,\n kind TEXT NOT NULL,\n type TEXT NOT NULL,\n name TEXT,\n start_byte INTEGER NOT NULL,\n end_byte INTEGER NOT NULL,\n start_point TEXT NOT NULL,\n end_point TEXT NOT NULL,\n source_hash TEXT NOT NULL,\n parser TEXT NOT NULL,\n parser_version TEXT NOT NULL,\n metadata_json TEXT NOT NULL DEFAULT '{}',\n last_modified_user_id TEXT\n);\n\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n file_id TEXT REFERENCES files(id) ON DELETE CASCADE,\n source_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n target_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n kind TEXT NOT NULL,\n type TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 1.0,\n metadata_json TEXT NOT NULL DEFAULT '{}'\n);\n\nCREATE TABLE IF NOT EXISTS overlays (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n subject_id TEXT NOT NULL,\n subject_kind TEXT NOT NULL,\n operation TEXT NOT NULL,\n metadata_json TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_files_repo_path ON files(repository_id, path);\nCREATE INDEX IF NOT EXISTS idx_nodes_repo_kind ON nodes(repository_id, kind);\nCREATE INDEX IF NOT EXISTS idx_nodes_repo_name ON nodes(repository_id, name);\nCREATE INDEX IF NOT EXISTS idx_edges_repo_kind ON edges(repository_id, kind);\nCREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);\nCREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);\n\nCREATE TABLE IF NOT EXISTS plugin_contributions (\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n plugin_name TEXT NOT NULL,\n generation INTEGER NOT NULL,\n nodes_json TEXT NOT NULL,\n edges_json TEXT NOT NULL,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY(repository_id, plugin_name)\n);\nCREATE INDEX IF NOT EXISTS idx_plugin_contributions_repo ON plugin_contributions(repository_id);\n\nCREATE TABLE IF NOT EXISTS file_scan_failures (\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n path TEXT NOT NULL,\n diagnostic TEXT NOT NULL,\n failed_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY(repository_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS repository_scan_state (\n repository_id TEXT PRIMARY KEY REFERENCES repositories(id) ON DELETE CASCADE,\n state TEXT NOT NULL CHECK(state IN ('fresh', 'degraded', 'stale')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS file_enrichment_state (\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n file_id TEXT PRIMARY KEY REFERENCES files(id) ON DELETE CASCADE,\n plugin_name TEXT NOT NULL DEFAULT 'git-attribution-plugin',\n source_hash TEXT NOT NULL,\n history_fingerprint TEXT,\n state TEXT NOT NULL CHECK(state IN ('pending', 'complete', 'failed')),\n outcome TEXT CHECK(outcome IN ('complete', 'unavailable', 'failed')),\n diagnostic TEXT,\n queued_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n completed_at TEXT,\n failed_at TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_file_enrichment_state_repo_state ON file_enrichment_state(repository_id, state);\n\nCREATE TABLE IF NOT EXISTS repository_manifest_inventory (\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n path TEXT NOT NULL,\n kind TEXT NOT NULL CHECK(kind IN ('maven-pom', 'gradle-groovy', 'gradle-kotlin')),\n module_identity TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('observed', 'read_failed', 'traversal_incomplete', 'confirmed_deleted')),\n size INTEGER NOT NULL,\n mtime_ms INTEGER NOT NULL,\n source_hash TEXT,\n generation INTEGER NOT NULL,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY(repository_id, path)\n);\nCREATE INDEX IF NOT EXISTS idx_repository_manifest_inventory_repo_status\n ON repository_manifest_inventory(repository_id, status);\n";
|
|
3
3
|
export declare const CREATE_NODES_TABLE_SQL = "\nCREATE TABLE nodes_new (\n id TEXT PRIMARY KEY,\n repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,\n file_id TEXT REFERENCES files(id) ON DELETE CASCADE,\n kind TEXT NOT NULL,\n type TEXT NOT NULL,\n name TEXT,\n start_byte INTEGER NOT NULL,\n end_byte INTEGER NOT NULL,\n start_point TEXT NOT NULL,\n end_point TEXT NOT NULL,\n source_hash TEXT NOT NULL,\n parser TEXT NOT NULL,\n parser_version TEXT NOT NULL,\n metadata_json TEXT NOT NULL DEFAULT '{}',\n last_modified_user_id TEXT\n);\n";
|
|
4
4
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const SCHEMA_VERSION =
|
|
1
|
+
export const SCHEMA_VERSION = 9;
|
|
2
2
|
export const CREATE_SCHEMA_SQL = `
|
|
3
3
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
4
4
|
version INTEGER PRIMARY KEY,
|
|
@@ -50,6 +50,7 @@ CREATE TABLE IF NOT EXISTS edges (
|
|
|
50
50
|
source_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
51
51
|
target_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
52
52
|
kind TEXT NOT NULL,
|
|
53
|
+
type TEXT NOT NULL,
|
|
53
54
|
confidence REAL NOT NULL DEFAULT 1.0,
|
|
54
55
|
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
55
56
|
);
|
|
@@ -99,8 +100,11 @@ CREATE TABLE IF NOT EXISTS repository_scan_state (
|
|
|
99
100
|
CREATE TABLE IF NOT EXISTS file_enrichment_state (
|
|
100
101
|
repository_id TEXT NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
|
|
101
102
|
file_id TEXT PRIMARY KEY REFERENCES files(id) ON DELETE CASCADE,
|
|
103
|
+
plugin_name TEXT NOT NULL DEFAULT 'git-attribution-plugin',
|
|
102
104
|
source_hash TEXT NOT NULL,
|
|
105
|
+
history_fingerprint TEXT,
|
|
103
106
|
state TEXT NOT NULL CHECK(state IN ('pending', 'complete', 'failed')),
|
|
107
|
+
outcome TEXT CHECK(outcome IN ('complete', 'unavailable', 'failed')),
|
|
104
108
|
diagnostic TEXT,
|
|
105
109
|
queued_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
106
110
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|