@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.
Files changed (48) hide show
  1. package/README.md +69 -3
  2. package/dist/src/cli.js +68 -13
  3. package/dist/src/graph/repository.d.ts +4 -1
  4. package/dist/src/graph/repository.js +106 -6
  5. package/dist/src/index.d.ts +3 -0
  6. package/dist/src/index.js +2 -0
  7. package/dist/src/mcp/server.js +2 -2
  8. package/dist/src/scanner/discover.js +0 -2
  9. package/dist/src/scanner/packagePlugin.d.ts +3 -0
  10. package/dist/src/scanner/packagePlugin.js +168 -0
  11. package/dist/src/scanner/plugins.d.ts +86 -0
  12. package/dist/src/scanner/plugins.js +503 -0
  13. package/dist/src/storage/connection.d.ts +6 -0
  14. package/dist/src/storage/connection.js +133 -0
  15. package/dist/src/storage/context-queries.d.ts +20 -0
  16. package/dist/src/storage/context-queries.js +137 -0
  17. package/dist/src/storage/database.d.ts +12 -71
  18. package/dist/src/storage/database.js +12 -562
  19. package/dist/src/storage/graph-writes.d.ts +17 -0
  20. package/dist/src/storage/graph-writes.js +126 -0
  21. package/dist/src/storage/plugin-contributions.d.ts +15 -0
  22. package/dist/src/storage/plugin-contributions.js +28 -0
  23. package/dist/src/storage/plugin-graph.d.ts +6 -0
  24. package/dist/src/storage/plugin-graph.js +81 -0
  25. package/dist/src/storage/queries.d.ts +25 -0
  26. package/dist/src/storage/queries.js +129 -0
  27. package/dist/src/storage/row-decoders.d.ts +8 -0
  28. package/dist/src/storage/row-decoders.js +37 -0
  29. package/dist/src/storage/schema.d.ts +2 -2
  30. package/dist/src/storage/schema.js +13 -2
  31. package/dist/src/storage/traversal-queries.d.ts +15 -0
  32. package/dist/src/storage/traversal-queries.js +87 -0
  33. package/dist/src/types.d.ts +10 -4
  34. package/dist/src/view/index.d.ts +1 -1
  35. package/dist/src/view/index.js +2 -2
  36. package/dist/src/view/model.d.ts +2 -0
  37. package/dist/src/view/render.d.ts +5 -2
  38. package/dist/src/view/render.js +81 -13
  39. package/dist/src/view/templates/icons/call.svg +13 -0
  40. package/dist/src/view/templates/icons/export.svg +1 -0
  41. package/dist/src/view/templates/icons/file.svg +9 -0
  42. package/dist/src/view/templates/icons/import.svg +1 -0
  43. package/dist/src/view/templates/icons/package.svg +1 -0
  44. package/dist/src/view/templates/icons/symbol.svg +7 -0
  45. package/dist/src/view/templates/icons/user.svg +15 -0
  46. package/dist/src/view/templates/interactive.css +17 -11
  47. package/dist/src/view/templates/interactive.html +236 -52
  48. 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
- import { DatabaseSync } from 'node:sqlite';
2
- import type { FileAttribution, FileRecord, GraphEdge, GraphEdgeRow, GraphNode, GraphNodeRow, GraphNodeTextRow, NeighborRow, CallGraphMatch, RepositoryRecord, StatusResult, ContextCandidate, ContextSourceReference } from '../types.js';
3
- export declare function openGraphDatabase(databasePath: string): DatabaseSync;
4
- export declare function openReadOnlyGraphDatabase(databasePath: string): DatabaseSync;
5
- export declare function closeDatabase(db: DatabaseSync): void;
6
- export declare function upsertRepository(db: DatabaseSync, repository: RepositoryRecord): void;
7
- export declare function replaceFileGraph(db: DatabaseSync, { repository, file, nodes, edges, attribution }: {
8
- repository: RepositoryRecord;
9
- file: FileRecord;
10
- nodes: GraphNode[];
11
- edges: GraphEdge[];
12
- attribution?: FileAttribution | null;
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