@fodx/codelens 1.0.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.
Files changed (98) hide show
  1. package/README.md +212 -0
  2. package/adapters/pi/codelens.extension.ts +280 -0
  3. package/adapters/pi/extension.json +10 -0
  4. package/build/src/cli.js +265 -0
  5. package/build/src/cli.js.map +1 -0
  6. package/build/src/context/schema.sql +23 -0
  7. package/build/src/context/store.js +91 -0
  8. package/build/src/context/store.js.map +1 -0
  9. package/build/src/db/db.js +65 -0
  10. package/build/src/db/db.js.map +1 -0
  11. package/build/src/db/migrations.js +88 -0
  12. package/build/src/db/migrations.js.map +1 -0
  13. package/build/src/db/schema.js +11 -0
  14. package/build/src/db/schema.js.map +1 -0
  15. package/build/src/db/schema.sql +111 -0
  16. package/build/src/git/scope.js +68 -0
  17. package/build/src/git/scope.js.map +1 -0
  18. package/build/src/graph/edges.js +76 -0
  19. package/build/src/graph/edges.js.map +1 -0
  20. package/build/src/graph/grammars.js +57 -0
  21. package/build/src/graph/grammars.js.map +1 -0
  22. package/build/src/graph/query.js +52 -0
  23. package/build/src/graph/query.js.map +1 -0
  24. package/build/src/graph/resolve.js +68 -0
  25. package/build/src/graph/resolve.js.map +1 -0
  26. package/build/src/graph/symbols.js +84 -0
  27. package/build/src/graph/symbols.js.map +1 -0
  28. package/build/src/graph/tests.js +73 -0
  29. package/build/src/graph/tests.js.map +1 -0
  30. package/build/src/index/autoprune.js +29 -0
  31. package/build/src/index/autoprune.js.map +1 -0
  32. package/build/src/index/deny.js +40 -0
  33. package/build/src/index/deny.js.map +1 -0
  34. package/build/src/index/freshness.js +60 -0
  35. package/build/src/index/freshness.js.map +1 -0
  36. package/build/src/index/fts.js +125 -0
  37. package/build/src/index/fts.js.map +1 -0
  38. package/build/src/index/identity.js +21 -0
  39. package/build/src/index/identity.js.map +1 -0
  40. package/build/src/index/indexer.js +32 -0
  41. package/build/src/index/indexer.js.map +1 -0
  42. package/build/src/index/manager.js +48 -0
  43. package/build/src/index/manager.js.map +1 -0
  44. package/build/src/index/queue.js +47 -0
  45. package/build/src/index/queue.js.map +1 -0
  46. package/build/src/index/recovery.js +51 -0
  47. package/build/src/index/recovery.js.map +1 -0
  48. package/build/src/index/reindex.js +70 -0
  49. package/build/src/index/reindex.js.map +1 -0
  50. package/build/src/index/scanner.js +147 -0
  51. package/build/src/index/scanner.js.map +1 -0
  52. package/build/src/index/ttl.js +87 -0
  53. package/build/src/index/ttl.js.map +1 -0
  54. package/build/src/index/watcher.js +74 -0
  55. package/build/src/index/watcher.js.map +1 -0
  56. package/build/src/installer/agents.js +440 -0
  57. package/build/src/installer/agents.js.map +1 -0
  58. package/build/src/obs/doctor.js +53 -0
  59. package/build/src/obs/doctor.js.map +1 -0
  60. package/build/src/obs/stats.js +28 -0
  61. package/build/src/obs/stats.js.map +1 -0
  62. package/build/src/obs/usage.js +136 -0
  63. package/build/src/obs/usage.js.map +1 -0
  64. package/build/src/search/rank.js +70 -0
  65. package/build/src/search/rank.js.map +1 -0
  66. package/build/src/search/snippet.js +66 -0
  67. package/build/src/search/snippet.js.map +1 -0
  68. package/build/src/server.js +126 -0
  69. package/build/src/server.js.map +1 -0
  70. package/build/src/tools/current.js +32 -0
  71. package/build/src/tools/current.js.map +1 -0
  72. package/build/src/tools/expand.js +54 -0
  73. package/build/src/tools/expand.js.map +1 -0
  74. package/build/src/tools/prune.js +46 -0
  75. package/build/src/tools/prune.js.map +1 -0
  76. package/build/src/tools/refresh.js +14 -0
  77. package/build/src/tools/refresh.js.map +1 -0
  78. package/build/src/tools/registry.js +176 -0
  79. package/build/src/tools/registry.js.map +1 -0
  80. package/build/src/tools/related.js +28 -0
  81. package/build/src/tools/related.js.map +1 -0
  82. package/build/src/tools/save.js +15 -0
  83. package/build/src/tools/save.js.map +1 -0
  84. package/build/src/tools/search.js +124 -0
  85. package/build/src/tools/search.js.map +1 -0
  86. package/build/src/upgrade.js +74 -0
  87. package/build/src/upgrade.js.map +1 -0
  88. package/build/src/util/hash.js +15 -0
  89. package/build/src/util/hash.js.map +1 -0
  90. package/build/src/util/paths.js +67 -0
  91. package/build/src/util/paths.js.map +1 -0
  92. package/build/src/version.js +27 -0
  93. package/build/src/version.js.map +1 -0
  94. package/docs/agent-guide.md +47 -0
  95. package/docs/codelens-preview.png +0 -0
  96. package/docs/routing.md +59 -0
  97. package/docs/tools.md +53 -0
  98. package/package.json +103 -0
@@ -0,0 +1,111 @@
1
+ -- Context Expert — schema v1
2
+ -- All tables scoped by index_id (branch/worktree/head namespace).
3
+ -- Managed by src/db/migrations.ts; do not edit in place — add migrations.
4
+
5
+ CREATE TABLE IF NOT EXISTS schema_version (
6
+ version INTEGER PRIMARY KEY,
7
+ applied_at INTEGER NOT NULL
8
+ );
9
+
10
+ -- Index namespace: repo + worktree + branch + head
11
+ CREATE TABLE IF NOT EXISTS indexes (
12
+ id TEXT PRIMARY KEY,
13
+ repo_root TEXT NOT NULL,
14
+ worktree_path TEXT NOT NULL,
15
+ branch_name TEXT NOT NULL,
16
+ head_sha TEXT NOT NULL,
17
+ created_at INTEGER NOT NULL,
18
+ last_accessed_at INTEGER NOT NULL,
19
+ expires_at INTEGER, -- NULL = active, never expire
20
+ pinned INTEGER NOT NULL DEFAULT 0,
21
+ status TEXT NOT NULL DEFAULT 'active' -- active|stale|deleted
22
+ );
23
+
24
+ CREATE INDEX IF NOT EXISTS idx_indexes_branch ON indexes(branch_name);
25
+
26
+ -- File records within an index
27
+ CREATE TABLE IF NOT EXISTS files (
28
+ id TEXT PRIMARY KEY,
29
+ index_id TEXT NOT NULL,
30
+ path TEXT NOT NULL, -- POSIX repo-relative
31
+ language TEXT,
32
+ size INTEGER NOT NULL,
33
+ mtime_ms INTEGER NOT NULL,
34
+ content_hash TEXT, -- xxhash of content
35
+ git_blob_sha TEXT,
36
+ deleted INTEGER NOT NULL DEFAULT 0,
37
+ last_indexed_at INTEGER NOT NULL,
38
+ FOREIGN KEY (index_id) REFERENCES indexes(id) ON DELETE CASCADE
39
+ );
40
+ CREATE INDEX IF NOT EXISTS idx_files_index_path ON files(index_id, path);
41
+
42
+ -- Symbols (functions/classes/methods/types/exports/imports)
43
+ CREATE TABLE IF NOT EXISTS symbols (
44
+ id TEXT PRIMARY KEY,
45
+ index_id TEXT NOT NULL,
46
+ file_id TEXT NOT NULL,
47
+ path TEXT NOT NULL,
48
+ name TEXT NOT NULL,
49
+ kind TEXT NOT NULL, -- function|class|method|type|interface|constant|import|export
50
+ signature TEXT,
51
+ start_line INTEGER NOT NULL,
52
+ end_line INTEGER NOT NULL,
53
+ exported INTEGER NOT NULL DEFAULT 0,
54
+ doc TEXT,
55
+ FOREIGN KEY (index_id) REFERENCES indexes(id) ON DELETE CASCADE,
56
+ FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE CASCADE
57
+ );
58
+ CREATE INDEX IF NOT EXISTS idx_symbols_index_name ON symbols(index_id, name);
59
+ CREATE INDEX IF NOT EXISTS idx_symbols_index_file ON symbols(index_id, file_id);
60
+
61
+ -- Chunks (text slices for FTS + embedding)
62
+ CREATE TABLE IF NOT EXISTS chunks (
63
+ id TEXT PRIMARY KEY,
64
+ index_id TEXT NOT NULL,
65
+ file_id TEXT NOT NULL,
66
+ symbol_id TEXT,
67
+ path TEXT NOT NULL,
68
+ start_line INTEGER NOT NULL,
69
+ end_line INTEGER NOT NULL,
70
+ content TEXT NOT NULL,
71
+ content_hash TEXT,
72
+ content_type TEXT NOT NULL DEFAULT 'prose', -- prose|code
73
+ FOREIGN KEY (index_id) REFERENCES indexes(id) ON DELETE CASCADE,
74
+ FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE CASCADE
75
+ );
76
+ CREATE INDEX IF NOT EXISTS idx_chunks_index_file ON chunks(index_id, file_id);
77
+ CREATE INDEX IF NOT EXISTS idx_chunks_symbol ON chunks(symbol_id);
78
+
79
+ -- FTS5 virtual table over chunk content (content-less external content via rowid map)
80
+ CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
81
+ content,
82
+ path,
83
+ index_id UNINDEXED,
84
+ chunk_id UNINDEXED,
85
+ tokenize = 'porter'
86
+ );
87
+
88
+ -- Graph edges
89
+ CREATE TABLE IF NOT EXISTS edges (
90
+ id TEXT PRIMARY KEY,
91
+ index_id TEXT NOT NULL,
92
+ from_id TEXT,
93
+ to_id TEXT,
94
+ from_path TEXT,
95
+ to_path TEXT,
96
+ type TEXT NOT NULL, -- imports|imported_by|defines|exports|references|calls|tests|belongs_to
97
+ confidence REAL NOT NULL DEFAULT 1.0,
98
+ FOREIGN KEY (index_id) REFERENCES indexes(id) ON DELETE CASCADE
99
+ );
100
+ CREATE INDEX IF NOT EXISTS idx_edges_index_from ON edges(index_id, from_id);
101
+ CREATE INDEX IF NOT EXISTS idx_edges_index_to ON edges(index_id, to_id);
102
+ CREATE INDEX IF NOT EXISTS idx_edges_index_type ON edges(index_id, type);
103
+
104
+
105
+ -- Advisory write locks (cross-process, leased)
106
+ CREATE TABLE IF NOT EXISTS index_locks (
107
+ index_id TEXT NOT NULL,
108
+ owner TEXT NOT NULL,
109
+ expires_at INTEGER NOT NULL,
110
+ PRIMARY KEY (index_id)
111
+ );
@@ -0,0 +1,68 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { resolveReal, toPosix } from "../util/paths.js";
3
+ function git(cwd, args) {
4
+ const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
5
+ if (r.error || r.status !== 0)
6
+ return { ok: false, stdout: r.stdout ?? "" };
7
+ return { ok: true, stdout: r.stdout.trim() };
8
+ }
9
+ /** Detect the git scope for `cwd`. Returns null if not inside a git repo. */
10
+ export function detectScope(cwd) {
11
+ const root = git(cwd, ["rev-parse", "--show-toplevel"]);
12
+ if (!root.ok || root.stdout.length === 0)
13
+ return null;
14
+ const repoRoot = resolveReal(root.stdout);
15
+ // --show-toplevel returns the main repo for worktrees; use --show-cdup + pwd
16
+ // for the actual checkout path. git worktree list --porcelain would be heavier.
17
+ const commonDir = git(cwd, ["rev-parse", "--git-common-dir"]);
18
+ const gitDir = git(cwd, ["rev-parse", "--absolute-git-dir"]);
19
+ // worktreePath = the checkout root (where .git link points). For a linked
20
+ // worktree, --show-toplevel already returns the worktree path, not the main.
21
+ const worktreePath = resolveReal(root.stdout);
22
+ const head = git(cwd, ["rev-parse", "HEAD"]);
23
+ if (!head.ok || head.stdout.length === 0) {
24
+ // Fresh repo with no commits — treat as detached with empty head.
25
+ return {
26
+ repoRoot,
27
+ worktreePath,
28
+ branch: "DETACHED",
29
+ headSha: "",
30
+ dirtyFiles: listDirty(cwd),
31
+ detached: true,
32
+ };
33
+ }
34
+ const headSha = head.stdout;
35
+ const branchOut = git(cwd, ["branch", "--show-current"]);
36
+ const detached = !branchOut.ok || branchOut.stdout.length === 0;
37
+ const branch = detached ? "DETACHED" : branchOut.stdout;
38
+ // commonDir/gitDir unused for now but validated to keep git presence robust.
39
+ void commonDir;
40
+ void gitDir;
41
+ return {
42
+ repoRoot,
43
+ worktreePath,
44
+ branch,
45
+ headSha,
46
+ dirtyFiles: listDirty(cwd),
47
+ detached,
48
+ };
49
+ }
50
+ /** List repo-relative posix paths that are changed or untracked (not ignored). */
51
+ export function listDirty(cwd) {
52
+ const r = spawnSync("git", ["status", "--porcelain", "-z"], { cwd, encoding: "utf-8" });
53
+ if (r.error || r.status !== 0)
54
+ return [];
55
+ // -z separates records by NUL; each record is "XY path" (or "XY orig -> path" for renames).
56
+ const out = r.stdout ?? "";
57
+ const files = [];
58
+ for (const rec of out.split("\0")) {
59
+ if (rec.length === 0)
60
+ continue;
61
+ // status code is 2 chars, then a space, then path (possibly "orig -> path").
62
+ const path = rec.slice(3);
63
+ const finalPath = path.includes(" -> ") ? path.split(" -> ")[1] ?? path : path;
64
+ files.push(toPosix(finalPath));
65
+ }
66
+ return files;
67
+ }
68
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../../../src/git/scope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { makeParser } from "./grammars.js";
3
+ import { resolveImportFs, resolveImport } from "./resolve.js";
4
+ // Node types that carry an import spec, per grammar.
5
+ const IMPORT_SPEC_FIELDS = ["source", "module_name", "name", "path"];
6
+ function importSpec(node) {
7
+ // Try named fields first.
8
+ for (const f of IMPORT_SPEC_FIELDS) {
9
+ const child = node.childForFieldName?.(f);
10
+ if (child)
11
+ return stripQuotes(child.text);
12
+ }
13
+ // Walk for string literals.
14
+ for (const c of iterAll(node)) {
15
+ if (c.type === "string" || c.type === "string_fragment")
16
+ return stripQuotes(c.text);
17
+ }
18
+ return null;
19
+ }
20
+ function* iterAll(node) {
21
+ yield node;
22
+ for (const c of node.children)
23
+ yield* iterAll(c);
24
+ }
25
+ function stripQuotes(s) {
26
+ return s.replace(/^["'`]|["'`]$/g, "");
27
+ }
28
+ /**
29
+ * Extract edges for a single file. `knownFiles` (repo-relative POSIX set) is
30
+ * used to resolve imports to indexed files; falls back to the filesystem.
31
+ */
32
+ export function extractEdges(path, lang, source, repoRoot, knownFiles) {
33
+ const parser = makeParser(lang);
34
+ if (!parser)
35
+ return [];
36
+ let tree;
37
+ try {
38
+ tree = parser.parse(source);
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ const out = [];
44
+ const root = tree.rootNode;
45
+ // Walk for import statements.
46
+ for (const node of iterAll(root)) {
47
+ if (node.type === "import_statement" || node.type === "import_declaration" || node.type === "import_from_statement") {
48
+ const spec = importSpec(node);
49
+ if (!spec)
50
+ continue;
51
+ const target = resolveImportFs(repoRoot, path, spec) ?? resolveImport(path, spec, knownFiles);
52
+ out.push({
53
+ fromPath: path,
54
+ toPath: target,
55
+ fromSymbol: null,
56
+ toSymbol: null,
57
+ type: "imports",
58
+ confidence: target ? 0.9 : 0.0,
59
+ });
60
+ if (!target) {
61
+ // unresolved — keep no-edge policy (confidence 0 filtered on insert)
62
+ }
63
+ }
64
+ }
65
+ return out;
66
+ }
67
+ /** Insert extracted edges into the edges table (skips unresolved imports). */
68
+ export function insertEdges(db, indexId, edges) {
69
+ const stmt = db.prepare(`INSERT INTO edges (id, index_id, from_id, to_id, from_path, to_path, type, confidence) VALUES (?, ?, NULL, NULL, ?, ?, ?, ?)`);
70
+ for (const e of edges) {
71
+ if (e.type === "imports" && !e.toPath)
72
+ continue; // unresolved → no edge
73
+ stmt.run("edge_" + randomUUID(), indexId, e.fromPath, e.toPath, e.type, e.confidence);
74
+ }
75
+ }
76
+ //# sourceMappingURL=edges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edges.js","sourceRoot":"","sources":["../../../src/graph/edges.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import Parser from "tree-sitter";
2
+ import { createRequire } from "node:module";
3
+ const _require = createRequire(import.meta.url);
4
+ const GRAMMAR_LOADERS = {
5
+ typescript: () => _require("tree-sitter-typescript").typescript,
6
+ javascript: () => _require("tree-sitter-javascript"),
7
+ python: () => _require("tree-sitter-python"),
8
+ go: () => _require("tree-sitter-go"),
9
+ rust: () => _require("tree-sitter-rust"),
10
+ java: () => _require("tree-sitter-java"),
11
+ ruby: () => _require("tree-sitter-ruby"),
12
+ php: () => _require("tree-sitter-php").php,
13
+ c: () => _require("tree-sitter-c"),
14
+ cpp: () => _require("tree-sitter-cpp"),
15
+ };
16
+ const cache = new Map();
17
+ /** Get the tree-sitter Language for a language name, or null if unavailable. */
18
+ export function loadGrammar(lang) {
19
+ if (cache.has(lang))
20
+ return cache.get(lang) ?? null;
21
+ const loader = GRAMMAR_LOADERS[lang];
22
+ if (!loader) {
23
+ cache.set(lang, null);
24
+ return null;
25
+ }
26
+ try {
27
+ const grammar = loader();
28
+ cache.set(lang, grammar);
29
+ return grammar;
30
+ }
31
+ catch {
32
+ cache.set(lang, null);
33
+ return null;
34
+ }
35
+ }
36
+ /** Whether a language has a usable tree-sitter grammar. */
37
+ export function isSupported(lang) {
38
+ if (!lang)
39
+ return false;
40
+ return loadGrammar(lang) !== null;
41
+ }
42
+ /** Create a configured Parser for a language, or null if unavailable. */
43
+ export function makeParser(lang) {
44
+ const grammar = loadGrammar(lang);
45
+ if (!grammar)
46
+ return null;
47
+ try {
48
+ const parser = new Parser();
49
+ // setLanguage signature varies across bindings; the grammar object is the Language.
50
+ parser.setLanguage(grammar);
51
+ return parser;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ //# sourceMappingURL=grammars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grammars.js","sourceRoot":"","sources":["../../../src/graph/grammars.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAYhD,MAAM,eAAe,GAAmC;IACtD,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC,UAAsB;IAC3E,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAa;IAChE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAa;IACxD,EAAE,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAChD,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAa;IACpD,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAa;IACpD,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAa;IACpD,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,GAAe;IACtD,CAAC,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAa;IAC9C,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAa;CACnD,CAAC;AAEF,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEjD,gFAAgF;AAChF,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IACpD,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC;QACzB,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,IAA+B;IACzD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AACpC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QAC5B,oFAAoF;QACnF,MAA2D,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAClF,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,52 @@
1
+ const EDGE_TYPES_ALL = ["imports", "imported_by", "defines", "exports", "references", "calls", "tests", "belongs_to"];
2
+ const MAX_DEPTH = 3;
3
+ /** Recursive CTE neighbors of `startPath`, filtered by edge types, bounded by depth. */
4
+ export function neighbors(db, indexId, startPath, opts = {}) {
5
+ const types = opts.types ?? EDGE_TYPES_ALL;
6
+ const depth = Math.min(opts.depth ?? 2, MAX_DEPTH);
7
+ const direction = opts.direction ?? "both";
8
+ // Direction determines which edge column must equal the current node, and
9
+ // which end is the neighbor.
10
+ // out: match e.from_path = node → neighbor = e.to_path
11
+ // in: match e.to_path = node → neighbor = e.from_path
12
+ // both: either end matches → neighbor is the other end
13
+ const matchClause = direction === "out" ? "e.from_path = w.node" :
14
+ direction === "in" ? "e.to_path = w.node" :
15
+ "(e.from_path = w.node OR e.to_path = w.node)";
16
+ const neighborExpr = direction === "out" ? "e.to_path" :
17
+ direction === "in" ? "e.from_path" :
18
+ "CASE WHEN e.from_path = w.node THEN e.to_path ELSE e.from_path END";
19
+ const typePlaceholders = types.map(() => "?").join(",");
20
+ const seenGuard = `instr(w.seen, ',' || ${neighborExpr} || ',') = 0`;
21
+ const sql = `
22
+ WITH RECURSIVE walk(node, edge_type, hops, confidence, seen) AS (
23
+ SELECT ?, NULL, 0, 1.0, ',' || ? || ','
24
+ UNION
25
+ SELECT
26
+ ${neighborExpr},
27
+ e.type,
28
+ w.hops + 1,
29
+ e.confidence,
30
+ w.seen || ${neighborExpr} || ','
31
+ FROM edges e
32
+ JOIN walk w ON ${matchClause}
33
+ WHERE e.index_id = ?
34
+ AND e.type IN (${typePlaceholders})
35
+ AND w.hops < ?
36
+ AND ${seenGuard}
37
+ )
38
+ SELECT node AS path, edge_type AS edgeType, MIN(hops) AS hops, MAX(confidence) AS confidence
39
+ FROM walk
40
+ WHERE node != ? AND edge_type IS NOT NULL
41
+ GROUP BY node, edge_type
42
+ ORDER BY hops ASC, confidence DESC
43
+ `;
44
+ const rows = db.prepare(sql).all(startPath, startPath, indexId, ...types, depth, startPath);
45
+ return rows.map((r) => ({ path: r.path, edgeType: r.edgeType, hops: r.hops, confidence: r.confidence }));
46
+ }
47
+ /** Files that emit `tests` edges pointing at `sourcePath`. */
48
+ export function testsFor(db, indexId, sourcePath) {
49
+ const rows = db.prepare(`SELECT from_path AS path, confidence FROM edges WHERE index_id = ? AND type = 'tests' AND to_path = ?`).all(indexId, sourcePath);
50
+ return rows.map((r) => ({ path: r.path, edgeType: "tests", hops: 1, confidence: r.confidence }));
51
+ }
52
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/graph/query.ts"],"names":[],"mappings":"AAcA,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;AACtH,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,wFAAwF;AACxF,MAAM,UAAU,SAAS,CACvB,EAAqB,EACrB,OAAe,EACf,SAAiB,EACjB,OAAgF,EAAE;IAElF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,cAAc,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC;IAE3C,0EAA0E;IAC1E,6BAA6B;IAC7B,yDAAyD;IACzD,2DAA2D;IAC3D,yDAAyD;IACzD,MAAM,WAAW,GACf,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC;QAC9C,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;YAC3C,8CAA8C,CAAC;IACjD,MAAM,YAAY,GAChB,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QACnC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;YACpC,oEAAoE,CAAC;IAEvE,MAAM,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxD,MAAM,SAAS,GAAG,wBAAwB,YAAY,cAAc,CAAC;IAErE,MAAM,GAAG,GAAG;;;;;UAKJ,YAAY;;;;oBAIF,YAAY;;uBAET,WAAW;;yBAET,gBAAgB;;cAE3B,SAAS;;;;;;;GAOpB,CAAC;IAEF,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAC9B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,SAAS,CACsC,CAAC;IAE5E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAC3G,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,QAAQ,CAAC,EAAqB,EAAE,OAAe,EAAE,UAAkB;IACjF,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CACrB,uGAAuG,CACxG,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAA2C,CAAC;IACrE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AACnG,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { posix, join } from "node:path";
2
+ import { existsSync, statSync } from "node:fs";
3
+ /**
4
+ * Import resolution (Step 14).
5
+ *
6
+ * Resolves a relative import spec to a candidate repo-relative file path,
7
+ * trying extension substitution (incl. TS ESM `.js`→`.ts`), extension
8
+ * fallbacks, and index files. Returns null if unresolved (no edge — better to
9
+ * emit no edge than a wrong one).
10
+ */
11
+ const EXT_FALLBACKS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".d.ts", ".py", ".go", ".rs", ".java", ".rb", ".php", ".c", ".cpp"];
12
+ const INDEX_FILES = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.mjs", "index.py", "__init__.py"];
13
+ // TS ESM convention: `import x from "./foo.js"` resolves to `./foo.ts`.
14
+ const JS_TO_TS = {
15
+ ".js": [".ts", ".tsx", ".d.ts", ".mts", ".cts"],
16
+ ".jsx": [".tsx", ".d.ts"],
17
+ ".mjs": [".mts", ".ts"],
18
+ ".cjs": [".cts", ".ts"],
19
+ };
20
+ function isRelative(spec) {
21
+ return spec.startsWith("./") || spec.startsWith("../") || spec.startsWith(".");
22
+ }
23
+ /** Ordered candidate repo-relative paths to try for a resolved target. */
24
+ function* candidates(target) {
25
+ yield target;
26
+ // TS extension substitution: "./foo.js" → "./foo.ts" etc.
27
+ for (const [jsExt, tsExts] of Object.entries(JS_TO_TS)) {
28
+ if (target.endsWith(jsExt)) {
29
+ const stem = target.slice(0, -jsExt.length);
30
+ for (const ts of tsExts)
31
+ yield stem + ts;
32
+ }
33
+ }
34
+ // Append fallback extensions (only when target has no extension, to avoid
35
+ // producing "./foo.js.ts").
36
+ if (posix.extname(target) === "") {
37
+ for (const ext of EXT_FALLBACKS)
38
+ yield target + ext;
39
+ }
40
+ // Directory + index files.
41
+ for (const idx of INDEX_FILES)
42
+ yield posix.join(target, idx);
43
+ }
44
+ /** Resolve a relative import from `fromPath` (repo-relative POSIX) to a repo-relative target path. */
45
+ export function resolveImport(fromPath, spec, knownFiles) {
46
+ if (!isRelative(spec))
47
+ return null; // bare specifier (npm/builtin) — not a repo file
48
+ const base = posix.dirname(fromPath);
49
+ const target = posix.normalize(posix.join(base, spec));
50
+ for (const cand of candidates(target))
51
+ if (knownFiles.has(cand))
52
+ return cand;
53
+ return null;
54
+ }
55
+ /** Filesystem-backed resolution when knownFiles is incomplete. */
56
+ export function resolveImportFs(repoRoot, fromPath, spec) {
57
+ if (!isRelative(spec))
58
+ return null;
59
+ const base = posix.dirname(fromPath);
60
+ const target = posix.normalize(posix.join(base, spec));
61
+ for (const cand of candidates(target)) {
62
+ const abs = join(repoRoot, cand);
63
+ if (existsSync(abs) && statSync(abs).isFile())
64
+ return cand;
65
+ }
66
+ return null;
67
+ }
68
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/graph/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { makeParser } from "./grammars.js";
2
+ // Node types → symbol kind. Covers TS/JS/Python/Go/Rust/Java/Ruby/PHP/C/C++.
3
+ const SYMBOL_TYPES = {
4
+ function_declaration: "function",
5
+ function_definition: "function",
6
+ method_definition: "method",
7
+ method_declaration: "method",
8
+ function_signature: "function",
9
+ class_declaration: "class",
10
+ class_definition: "class",
11
+ interface_declaration: "interface",
12
+ interface_statement: "interface",
13
+ type_alias_declaration: "type",
14
+ type_definition: "type",
15
+ enum_declaration: "type",
16
+ enum_statement: "type",
17
+ lexical_declaration: "constant",
18
+ variable_declaration: "constant",
19
+ import_statement: "import",
20
+ import_declaration: "import",
21
+ import_from_statement: "import",
22
+ export_statement: "export",
23
+ export_declaration: "export",
24
+ };
25
+ // Node types that indicate an export (TS/JS).
26
+ const EXPORT_WRAPPERS = new Set(["export_statement", "export_declaration"]);
27
+ export function extractSymbols(path, lang, source) {
28
+ const parser = makeParser(lang);
29
+ if (!parser)
30
+ return [];
31
+ let tree;
32
+ try {
33
+ tree = parser.parse(source);
34
+ }
35
+ catch {
36
+ return [];
37
+ }
38
+ const out = [];
39
+ const root = tree.rootNode;
40
+ function nodeName(node) {
41
+ const nameNode = node.childForFieldName?.("name");
42
+ if (nameNode)
43
+ return nameNode.text;
44
+ for (const c of node.namedChildren) {
45
+ if (c.type === "identifier" || c.type === "property_identifier")
46
+ return c.text;
47
+ }
48
+ return null;
49
+ }
50
+ function isExported(node) {
51
+ if (EXPORT_WRAPPERS.has(node.type))
52
+ return true;
53
+ if (node.parent && EXPORT_WRAPPERS.has(node.parent.type))
54
+ return true;
55
+ const text = node.text;
56
+ const name = nodeName(node) ?? "";
57
+ const head = text.slice(0, text.indexOf(name));
58
+ return /\bexport\b/.test(head);
59
+ }
60
+ function walk(node) {
61
+ const kind = SYMBOL_TYPES[node.type];
62
+ if (kind && kind !== "import" && kind !== "export") {
63
+ const name = nodeName(node);
64
+ if (name) {
65
+ out.push({
66
+ name,
67
+ kind,
68
+ startLine: node.startPosition.row + 1,
69
+ endLine: node.endPosition.row + 1,
70
+ signature: firstLine(node.text),
71
+ exported: isExported(node),
72
+ });
73
+ }
74
+ }
75
+ for (const child of node.namedChildren)
76
+ walk(child);
77
+ }
78
+ walk(root);
79
+ return out;
80
+ }
81
+ function firstLine(text) {
82
+ return text.split("\n")[0] ?? text;
83
+ }
84
+ //# sourceMappingURL=symbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../../src/graph/symbols.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { posix, basename, dirname } from "node:path";
2
+ /**
3
+ * Test heuristics (Step 15).
4
+ *
5
+ * Infers test-to-source relationships from filename/path conventions:
6
+ * foo.test.ts → foo.ts
7
+ * foo.spec.ts → foo.ts
8
+ * __tests__/foo.test.ts → ../foo.ts (or same-dir foo.ts)
9
+ * tests/foo.ts → ../src/foo.ts (best-effort)
10
+ * foo_test.go → foo.go
11
+ * test_foo.py → foo.py
12
+ * Returns candidate target paths; only emitted as a `tests` edge if the
13
+ * target exists in the indexed file set (no wrong edges).
14
+ */
15
+ const TEST_SUFFIXES = [/\.test\.[a-z]+$/i, /\.spec\.[a-z]+$/i, /_test\.[a-z]+$/i, /\.test$/i, /\.spec$/i];
16
+ const TEST_PREFIXES = [/^test_/i];
17
+ const TEST_DIRS = ["__tests__", "tests", "test", "__test__"];
18
+ /** Is this path a test file by name? */
19
+ export function isTestFile(path) {
20
+ const base = basename(path);
21
+ if (TEST_SUFFIXES.some((re) => re.test(base)))
22
+ return true;
23
+ if (TEST_PREFIXES.some((re) => re.test(base)))
24
+ return true;
25
+ const dir = dirname(path);
26
+ return TEST_DIRS.some((d) => dir.split("/").includes(d));
27
+ }
28
+ /** Map a test file to candidate source file paths (repo-relative POSIX). */
29
+ export function inferTestTargets(testPath) {
30
+ const base = basename(testPath);
31
+ const dir = dirname(testPath);
32
+ const candidates = new Set();
33
+ // Strip test suffix: foo.test.ts → foo.ts
34
+ for (const re of TEST_SUFFIXES) {
35
+ if (re.test(base)) {
36
+ const stripped = base.replace(re, "");
37
+ // Re-add a likely source extension: foo.test.ts → foo.ts
38
+ const ext = extOf(base);
39
+ candidates.add(posix.join(dir, stripped + (ext ? "." + ext : "")));
40
+ candidates.add(posix.join(dir, stripped));
41
+ }
42
+ }
43
+ // Strip test prefix: test_foo.py → foo.py
44
+ for (const re of TEST_PREFIXES) {
45
+ if (re.test(base)) {
46
+ const stripped = base.replace(re, "");
47
+ candidates.add(posix.join(dir, stripped));
48
+ }
49
+ }
50
+ // __tests__/foo.test.ts → ../foo.ts and ../src/foo.ts
51
+ const segs = dir.split("/");
52
+ if (segs.includes("__tests__") || segs.includes("tests") || segs.includes("test")) {
53
+ const base2 = base.replace(/\.test\.|\.spec\.|_test\./, ".");
54
+ const stripped = base2.replace(/\.[a-z]+$/, "");
55
+ const ext = extOf(base);
56
+ const parent = segs.slice(0, -1).join("/");
57
+ candidates.add(posix.join(parent, stripped + (ext ? "." + ext : "")));
58
+ candidates.add(posix.join(parent, "src", stripped + (ext ? "." + ext : "")));
59
+ }
60
+ return [...candidates];
61
+ }
62
+ function extOf(name) {
63
+ const m = name.match(/\.([a-z0-9]+)$/i);
64
+ return m ? m[1] : "";
65
+ }
66
+ /**
67
+ * Given the set of indexed files, return the source paths this test file
68
+ * likely tests (only those present in the file set).
69
+ */
70
+ export function resolveTestTargets(testPath, knownFiles) {
71
+ return inferTestTargets(testPath).filter((p) => knownFiles.has(p));
72
+ }
73
+ //# sourceMappingURL=tests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tests.js","sourceRoot":"","sources":["../../../src/graph/tests.ts"],"names":[],"mappings":"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"}