@psnext/lscg 0.1.2 → 0.1.4
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 +69 -3
- package/dist/src/cli.js +68 -13
- package/dist/src/graph/repository.d.ts +4 -1
- package/dist/src/graph/repository.js +106 -6
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +2 -0
- package/dist/src/mcp/server.js +2 -2
- package/dist/src/scanner/discover.js +0 -2
- package/dist/src/scanner/packagePlugin.d.ts +3 -0
- package/dist/src/scanner/packagePlugin.js +168 -0
- package/dist/src/scanner/plugins.d.ts +86 -0
- package/dist/src/scanner/plugins.js +503 -0
- package/dist/src/storage/connection.d.ts +6 -0
- package/dist/src/storage/connection.js +133 -0
- package/dist/src/storage/context-queries.d.ts +20 -0
- package/dist/src/storage/context-queries.js +137 -0
- package/dist/src/storage/database.d.ts +12 -71
- package/dist/src/storage/database.js +12 -562
- package/dist/src/storage/graph-writes.d.ts +17 -0
- package/dist/src/storage/graph-writes.js +126 -0
- package/dist/src/storage/plugin-contributions.d.ts +15 -0
- package/dist/src/storage/plugin-contributions.js +28 -0
- package/dist/src/storage/plugin-graph.d.ts +6 -0
- package/dist/src/storage/plugin-graph.js +81 -0
- package/dist/src/storage/queries.d.ts +25 -0
- package/dist/src/storage/queries.js +129 -0
- package/dist/src/storage/row-decoders.d.ts +8 -0
- package/dist/src/storage/row-decoders.js +37 -0
- package/dist/src/storage/schema.d.ts +2 -2
- package/dist/src/storage/schema.js +13 -2
- package/dist/src/storage/traversal-queries.d.ts +15 -0
- package/dist/src/storage/traversal-queries.js +87 -0
- package/dist/src/types.d.ts +10 -4
- package/dist/src/view/index.d.ts +1 -1
- package/dist/src/view/index.js +2 -2
- package/dist/src/view/model.d.ts +2 -0
- package/dist/src/view/render.d.ts +5 -2
- package/dist/src/view/render.js +81 -13
- package/dist/src/view/templates/icons/call.svg +13 -0
- package/dist/src/view/templates/icons/export.svg +1 -0
- package/dist/src/view/templates/icons/file.svg +9 -0
- package/dist/src/view/templates/icons/import.svg +1 -0
- package/dist/src/view/templates/icons/package.svg +1 -0
- package/dist/src/view/templates/icons/symbol.svg +7 -0
- package/dist/src/view/templates/icons/user.svg +15 -0
- package/dist/src/view/templates/interactive.css +17 -11
- package/dist/src/view/templates/interactive.html +236 -52
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { parseMetadata, parsePoint, contextReferenceFromRow } from './row-decoders.js';
|
|
3
|
+
import { clampLimit } from './queries.js';
|
|
4
|
+
export function selectContextCandidates(db, repositoryId, term, { kind, file, limit = 20 } = {}) {
|
|
5
|
+
const safeLimit = clampLimit(limit, 100);
|
|
6
|
+
const normalized = term.trim().toLocaleLowerCase();
|
|
7
|
+
if (!normalized)
|
|
8
|
+
return [];
|
|
9
|
+
const rows = [];
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
const stages = [
|
|
12
|
+
{ match: 'qualified', condition: "(lower(f.path || ':' || COALESCE(n.name, '')) = ? OR lower(f.path || '::' || COALESCE(n.name, '')) = ?)", value: normalized },
|
|
13
|
+
{ match: 'exact', condition: 'lower(n.name) = ?', value: normalized },
|
|
14
|
+
{ match: 'prefix', condition: 'lower(n.name) LIKE ?', value: `${normalized}%` },
|
|
15
|
+
{ match: 'substring', condition: 'lower(n.name) LIKE ?', value: `%${normalized}%` }
|
|
16
|
+
];
|
|
17
|
+
for (let stage = 0; stage < stages.length; stage += 1) {
|
|
18
|
+
const entry = stages[stage];
|
|
19
|
+
if (!entry)
|
|
20
|
+
continue;
|
|
21
|
+
const predicates = [`n.repository_id = ?`, entry.condition];
|
|
22
|
+
const params = [repositoryId, entry.value, entry.value];
|
|
23
|
+
if (!kind)
|
|
24
|
+
predicates.push("n.kind IN ('symbol', 'file')");
|
|
25
|
+
if (entry.match !== 'qualified')
|
|
26
|
+
params.pop();
|
|
27
|
+
if (kind) {
|
|
28
|
+
predicates.push('n.kind = ?');
|
|
29
|
+
params.push(kind);
|
|
30
|
+
}
|
|
31
|
+
if (file) {
|
|
32
|
+
predicates.push('f.path = ?');
|
|
33
|
+
params.push(file);
|
|
34
|
+
}
|
|
35
|
+
const selected = db.prepare(`
|
|
36
|
+
SELECT n.id, n.kind, n.type, n.name, n.file_id AS fileId,
|
|
37
|
+
n.start_point AS startPoint, n.end_point AS endPoint, n.source_hash AS sourceHash,
|
|
38
|
+
n.parser, n.parser_version AS parserVersion, n.metadata_json AS metadataJson,
|
|
39
|
+
f.path AS path
|
|
40
|
+
FROM nodes n LEFT JOIN files f ON f.id = n.file_id
|
|
41
|
+
WHERE ${predicates.join(' AND ')}
|
|
42
|
+
ORDER BY lower(COALESCE(f.path, '')), n.kind, lower(COALESCE(n.name, '')), n.id
|
|
43
|
+
LIMIT ?
|
|
44
|
+
`).all(...params, safeLimit);
|
|
45
|
+
for (const row of selected) {
|
|
46
|
+
const id = String(row.id);
|
|
47
|
+
if (seen.has(id))
|
|
48
|
+
continue;
|
|
49
|
+
seen.add(id);
|
|
50
|
+
rows.push({
|
|
51
|
+
...contextReferenceFromRow(row),
|
|
52
|
+
rank: stage,
|
|
53
|
+
match: entry.match,
|
|
54
|
+
stage
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// A unique exact/qualified match is authoritative. Prefix and substring are
|
|
58
|
+
// only consulted when no higher-priority stage produced a result.
|
|
59
|
+
if (rows.length > 0)
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
return rows.map(({ stage: _stage, ...candidate }) => candidate);
|
|
63
|
+
}
|
|
64
|
+
export function selectContextRelationships(db, repositoryId, anchorId, limit = 50, maxDepth = 1) {
|
|
65
|
+
const safeLimit = clampLimit(limit, 500);
|
|
66
|
+
const safeDepth = Math.max(1, Math.min(Number(maxDepth) || 1, 5));
|
|
67
|
+
const rows = [];
|
|
68
|
+
const seenNodes = new Set([anchorId]);
|
|
69
|
+
const seenEdges = new Set();
|
|
70
|
+
let frontier = [anchorId];
|
|
71
|
+
const kinds = ['calls', 'imports', 'exports'];
|
|
72
|
+
for (let depth = 1; depth <= safeDepth && frontier.length > 0; depth += 1) {
|
|
73
|
+
const next = new Set();
|
|
74
|
+
for (const kind of kinds) {
|
|
75
|
+
const remaining = safeLimit - rows.filter((row) => row.edgeKind === kind).length;
|
|
76
|
+
if (remaining <= 0)
|
|
77
|
+
continue;
|
|
78
|
+
const placeholders = frontier.map(() => '?').join(', ');
|
|
79
|
+
const selected = db.prepare(`
|
|
80
|
+
SELECT e.id AS edgeId, e.kind AS edgeKind, e.confidence, e.metadata_json AS edgeMetadata,
|
|
81
|
+
s.id AS sourceId, s.kind AS sourceKind, s.type AS sourceType, s.name AS sourceName,
|
|
82
|
+
s.start_point AS sourceStartPoint, s.end_point AS sourceEndPoint, s.source_hash AS sourceHash,
|
|
83
|
+
s.parser AS sourceParser, s.parser_version AS sourceParserVersion, s.metadata_json AS sourceMetadata,
|
|
84
|
+
sf.path AS sourcePath,
|
|
85
|
+
t.id AS targetId, t.kind AS targetKind, t.type AS targetType, t.name AS targetName,
|
|
86
|
+
t.start_point AS targetStartPoint, t.end_point AS targetEndPoint, t.source_hash AS targetHash,
|
|
87
|
+
t.parser AS targetParser, t.parser_version AS targetParserVersion, t.metadata_json AS targetMetadata,
|
|
88
|
+
tf.path AS targetPath
|
|
89
|
+
FROM edges e
|
|
90
|
+
JOIN nodes s ON s.id = e.source_id AND s.repository_id = e.repository_id
|
|
91
|
+
JOIN nodes t ON t.id = e.target_id AND t.repository_id = e.repository_id
|
|
92
|
+
LEFT JOIN files sf ON sf.id = s.file_id
|
|
93
|
+
LEFT JOIN files tf ON tf.id = t.file_id
|
|
94
|
+
WHERE e.repository_id = ? AND e.kind = ?
|
|
95
|
+
AND (e.source_id IN (${placeholders}) OR e.target_id IN (${placeholders}))
|
|
96
|
+
ORDER BY COALESCE(sf.path, tf.path), e.id
|
|
97
|
+
LIMIT ?
|
|
98
|
+
`).all(repositoryId, kind, ...frontier, ...frontier, remaining);
|
|
99
|
+
for (const row of selected) {
|
|
100
|
+
const edgeId = String(row.edgeId);
|
|
101
|
+
if (seenEdges.has(edgeId))
|
|
102
|
+
continue;
|
|
103
|
+
seenEdges.add(edgeId);
|
|
104
|
+
rows.push({ ...row, depth });
|
|
105
|
+
const sourceId = String(row.sourceId);
|
|
106
|
+
const targetId = String(row.targetId);
|
|
107
|
+
if (seenNodes.has(sourceId))
|
|
108
|
+
next.add(targetId);
|
|
109
|
+
if (seenNodes.has(targetId))
|
|
110
|
+
next.add(sourceId);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const nodeId of next)
|
|
114
|
+
seenNodes.add(nodeId);
|
|
115
|
+
frontier = [...next].filter((nodeId) => nodeId !== anchorId);
|
|
116
|
+
}
|
|
117
|
+
return rows.map((row) => ({
|
|
118
|
+
edgeId: String(row.edgeId),
|
|
119
|
+
edgeKind: row.edgeKind,
|
|
120
|
+
source: contextReferenceFromRow({
|
|
121
|
+
id: row.sourceId, kind: row.sourceKind, type: row.sourceType, name: row.sourceName,
|
|
122
|
+
startPoint: row.sourceStartPoint, endPoint: row.sourceEndPoint, sourceHash: row.sourceHash,
|
|
123
|
+
parser: row.sourceParser, parserVersion: row.sourceParserVersion, metadataJson: row.sourceMetadata,
|
|
124
|
+
path: row.sourcePath
|
|
125
|
+
}),
|
|
126
|
+
target: contextReferenceFromRow({
|
|
127
|
+
id: row.targetId, kind: row.targetKind, type: row.targetType, name: row.targetName,
|
|
128
|
+
startPoint: row.targetStartPoint, endPoint: row.targetEndPoint, sourceHash: row.targetHash,
|
|
129
|
+
parser: row.targetParser, parserVersion: row.targetParserVersion, metadataJson: row.targetMetadata,
|
|
130
|
+
path: row.targetPath
|
|
131
|
+
}),
|
|
132
|
+
confidence: Number(row.confidence ?? 0),
|
|
133
|
+
metadata: parseMetadata(row.edgeMetadata),
|
|
134
|
+
depth: row.depth
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=context-queries.js.map
|
|
@@ -1,72 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}): void;
|
|
14
|
-
export declare function getStatus(db: DatabaseSync, repositoryId: string): StatusResult;
|
|
15
|
-
export declare function selectNodes(db: DatabaseSync, repositoryId: string, { kind, limit }?: {
|
|
16
|
-
kind?: string | undefined;
|
|
17
|
-
limit?: number | undefined;
|
|
18
|
-
}): GraphNodeRow[];
|
|
19
|
-
export declare function selectNodeTextRows(db: DatabaseSync, repositoryId: string, { kind, term, limit }?: {
|
|
20
|
-
kind?: string | undefined;
|
|
21
|
-
term?: string | undefined;
|
|
22
|
-
limit?: number | undefined;
|
|
23
|
-
}): GraphNodeTextRow[];
|
|
24
|
-
export declare function selectAllNodes(db: DatabaseSync, repositoryId: string, { kind }?: {
|
|
25
|
-
kind?: string | undefined;
|
|
26
|
-
}): GraphNodeRow[];
|
|
27
|
-
export declare function selectEdges(db: DatabaseSync, repositoryId: string, { kind, limit }?: {
|
|
28
|
-
kind?: string | undefined;
|
|
29
|
-
limit?: number | undefined;
|
|
30
|
-
}): GraphEdgeRow[];
|
|
31
|
-
export declare function selectAllEdges(db: DatabaseSync, repositoryId: string, { kind }?: {
|
|
32
|
-
kind?: string | undefined;
|
|
33
|
-
}): GraphEdgeRow[];
|
|
34
|
-
export interface ContextNodeRow extends ContextSourceReference {
|
|
35
|
-
fileId: string | null;
|
|
36
|
-
}
|
|
37
|
-
/** Candidate lookup used by context. Stages are queried independently so a unique
|
|
38
|
-
* higher-priority match never gets merged with lower-priority fuzzy matches. */
|
|
39
|
-
export declare function selectContextCandidates(db: DatabaseSync, repositoryId: string, term: string, { kind, file, limit }?: {
|
|
40
|
-
kind?: string | undefined;
|
|
41
|
-
file?: string | undefined;
|
|
42
|
-
limit?: number | undefined;
|
|
43
|
-
}): ContextCandidate[];
|
|
44
|
-
export declare function selectContextRelationships(db: DatabaseSync, repositoryId: string, anchorId: string, limit?: number, maxDepth?: number): Array<{
|
|
45
|
-
edgeId: string;
|
|
46
|
-
edgeKind: 'imports' | 'exports' | 'calls';
|
|
47
|
-
source: ContextNodeRow;
|
|
48
|
-
target: ContextNodeRow;
|
|
49
|
-
confidence: number;
|
|
50
|
-
metadata: Record<string, unknown>;
|
|
51
|
-
depth: number;
|
|
52
|
-
}>;
|
|
53
|
-
export declare function reconcileDeletedFiles(db: DatabaseSync, repositoryId: string, discoveredPaths: string[]): void;
|
|
54
|
-
export declare function selectFileInventory(db: DatabaseSync, repositoryId: string): Array<{
|
|
55
|
-
path: string;
|
|
56
|
-
size: number;
|
|
57
|
-
mtimeMs: number;
|
|
58
|
-
}>;
|
|
59
|
-
export declare function selectCallGraph(db: DatabaseSync, repositoryId: string, term: string, { kind, depth, limit }?: {
|
|
60
|
-
kind?: string | undefined;
|
|
61
|
-
depth?: number | undefined;
|
|
62
|
-
limit?: number | undefined;
|
|
63
|
-
}): CallGraphMatch[];
|
|
64
|
-
export declare function selectNeighbors(db: DatabaseSync, repositoryId: string, nodeId: string, { depth, limit }?: {
|
|
65
|
-
depth?: number | undefined;
|
|
66
|
-
limit?: number | undefined;
|
|
67
|
-
}): NeighborRow[];
|
|
68
|
-
export declare function runSelect(db: DatabaseSync, sql: string, { limit }?: {
|
|
69
|
-
limit?: number | undefined;
|
|
70
|
-
}): Array<Record<string, unknown>>;
|
|
71
|
-
export declare function attachDatabase(db: DatabaseSync, alias: string, databasePath: string): void;
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility facade for the storage API. Keep consumers importing this module
|
|
3
|
+
* while the implementation stays split into focused modules small enough for
|
|
4
|
+
* Tree-sitter and easier to maintain.
|
|
5
|
+
*/
|
|
6
|
+
export * from './connection.js';
|
|
7
|
+
export * from './plugin-contributions.js';
|
|
8
|
+
export * from './graph-writes.js';
|
|
9
|
+
export * from './plugin-graph.js';
|
|
10
|
+
export * from './queries.js';
|
|
11
|
+
export * from './context-queries.js';
|
|
12
|
+
export * from './traversal-queries.js';
|
|
72
13
|
//# sourceMappingURL=database.d.ts.map
|