@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,147 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { statSync, openSync, readSync, closeSync, readdirSync, readFileSync } from "node:fs";
3
+ import { join, posix } from "node:path";
4
+ import { resolveReal, toPosix } from "../util/paths.js";
5
+ import { shouldDeny } from "./deny.js";
6
+ import ignore from "ignore";
7
+ /**
8
+ * File scanner (Design Decisions #2, #3, #11).
9
+ *
10
+ * Honors .gitignore via `git ls-files` (includes untracked, excludes ignored).
11
+ * Applies heuristic deny (build/dist/out/coverage + generated patterns) even
12
+ * for untracked files. Skips files >5MB. Skips binary files (NUL in first 8KB).
13
+ * Returns POSIX repo-relative paths.
14
+ */
15
+ export const MAX_FILE_BYTES = 5 * 1024 * 1024; // 5MB
16
+ const EXT_LANG = {
17
+ ".ts": "typescript", ".tsx": "typescript", ".js": "javascript", ".jsx": "javascript",
18
+ ".mjs": "javascript", ".cjs": "javascript", ".py": "python", ".go": "go", ".rs": "rust",
19
+ ".java": "java", ".rb": "ruby", ".php": "php", ".c": "c", ".h": "c", ".cpp": "cpp",
20
+ ".cc": "cpp", ".hpp": "cpp", ".cs": "csharp", ".swift": "swift", ".kt": "kotlin",
21
+ ".scala": "scala", ".sh": "bash", ".md": "markdown", ".json": "json", ".yaml": "yaml",
22
+ ".yml": "yaml", ".toml": "toml", ".sql": "sql",
23
+ };
24
+ function inferLanguage(posixPath) {
25
+ const ext = posix.extname(posixPath).toLowerCase();
26
+ return EXT_LANG[ext] ?? null;
27
+ }
28
+ /** True if file starts with a NUL byte or non-UTF8 bytes in first 8KB (binary heuristic). */
29
+ export function isBinary(absPath) {
30
+ let fd = null;
31
+ try {
32
+ fd = openSync(absPath, "r");
33
+ const buf = Buffer.alloc(8192);
34
+ const n = readSync(fd, buf, 0, 8192, 0);
35
+ for (let i = 0; i < n; i++) {
36
+ const b = buf[i];
37
+ // NUL byte → binary. Allow common text bytes (incl. tab/cr/lf).
38
+ if (b === 0)
39
+ return true;
40
+ }
41
+ // Heuristic: if >10% of bytes are non-text control chars (excluding \t \n \r), treat as binary.
42
+ let controls = 0;
43
+ for (let i = 0; i < n; i++) {
44
+ const b = buf[i];
45
+ if (b < 9 || (b > 13 && b < 32))
46
+ controls++;
47
+ }
48
+ return n > 0 && controls / n > 0.1;
49
+ }
50
+ catch {
51
+ return true; // unreadable → treat as binary/skip
52
+ }
53
+ finally {
54
+ if (fd !== null)
55
+ try {
56
+ closeSync(fd);
57
+ }
58
+ catch { /* ignore */ }
59
+ }
60
+ }
61
+ /**
62
+ * Scan the repo at `repoRoot`. Uses `git ls-files` when inside a git repo
63
+ * (honors .gitignore: untracked included, ignored excluded). Falls back to a
64
+ * plain directory walk for non-git dirs.
65
+ */
66
+ export function scanFiles(repoRoot) {
67
+ const root = resolveReal(repoRoot);
68
+ const relPaths = gitLsFiles(root) ?? walkDir(root);
69
+ const out = [];
70
+ for (const rel of relPaths) {
71
+ const p = toPosix(rel);
72
+ if (shouldDeny(p))
73
+ continue;
74
+ const abs = join(root, p);
75
+ let st;
76
+ try {
77
+ const s = statSync(abs);
78
+ if (!s.isFile())
79
+ continue;
80
+ st = { size: s.size, mtimeMs: s.mtimeMs };
81
+ }
82
+ catch {
83
+ continue;
84
+ }
85
+ if (st.size > MAX_FILE_BYTES)
86
+ continue;
87
+ if (isBinary(abs))
88
+ continue;
89
+ out.push({ path: p, size: st.size, mtimeMs: st.mtimeMs, language: inferLanguage(p) });
90
+ }
91
+ return out;
92
+ }
93
+ /** `git ls-files --others --exclude-standard --cached -z` → untracked+tracked, ignored excluded. */
94
+ function gitLsFiles(root) {
95
+ const r = spawnSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
96
+ cwd: root,
97
+ encoding: "utf-8",
98
+ });
99
+ if (r.error || r.status !== 0)
100
+ return null;
101
+ return (r.stdout ?? "").split("\0").filter((s) => s.length > 0);
102
+ }
103
+ /** Plain directory walk fallback for non-git dirs. Honors nested .gitignore. */
104
+ function walkDir(root) {
105
+ const out = [];
106
+ // Each stack frame carries the cumulative .gitignore pattern lines from all
107
+ // ancestor dirs (gitignore patterns without a slash match in any subdir, so
108
+ // concatenating lines approximates git's nested semantics for the fallback).
109
+ const stack = [{ dir: root, patterns: [] }];
110
+ while (stack.length) {
111
+ const { dir: cur, patterns } = stack.pop();
112
+ let entries;
113
+ try {
114
+ entries = readdirSync(cur, { withFileTypes: true });
115
+ }
116
+ catch {
117
+ continue;
118
+ }
119
+ // Inherit ancestor patterns and add this dir's .gitignore lines (if present).
120
+ let dirPatterns = patterns;
121
+ let dirIg = ignore().add(patterns);
122
+ try {
123
+ const gi = readFileSync(posix.join(cur, ".gitignore"), "utf-8");
124
+ dirPatterns = [...patterns, ...gi.split(/\r?\n/).filter((l) => l.trim().length > 0)];
125
+ dirIg = ignore().add(dirPatterns);
126
+ }
127
+ catch { /* no .gitignore here */ }
128
+ for (const e of entries) {
129
+ if (e.name === ".gitignore" || e.name.startsWith(".git"))
130
+ continue;
131
+ const full = posix.join(cur, e.name);
132
+ const rel = toPosix(full.slice(root.length + 1));
133
+ if (e.isDirectory()) {
134
+ if (dirIg.ignores(rel + "/"))
135
+ continue;
136
+ stack.push({ dir: full, patterns: dirPatterns });
137
+ }
138
+ else if (e.isFile()) {
139
+ if (dirIg.ignores(rel))
140
+ continue;
141
+ out.push(rel);
142
+ }
143
+ }
144
+ }
145
+ return out;
146
+ }
147
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../../src/index/scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import { getActiveIndexId } from "./manager.js";
2
+ /**
3
+ * TTL pruner (Step 22).
4
+ *
5
+ * Deletes inactive indexes per retention policy. NEVER deletes:
6
+ * - the active index
7
+ * - pinned indexes
8
+ * - indexes with an active (non-expired) write lease
9
+ * - indexes accessed within the grace window
10
+ *
11
+ * Default retention:
12
+ * active index: never (expires_at NULL)
13
+ * inactive branch: 14 days since last_access
14
+ * detached: 3 days
15
+ * temp worktree: 48h
16
+ * Grace window: 1h (don't prune if accessed in the last hour even if expired).
17
+ */
18
+ export const RETENTION = {
19
+ inactiveBranchDays: 14,
20
+ detachedDays: 3,
21
+ worktreeHours: 48,
22
+ graceMs: 3600_000,
23
+ };
24
+ /** Compute an expires_at timestamp for an index row (ms since epoch). */
25
+ export function computeExpiry(row, _now = Date.now()) {
26
+ if (row.pinned)
27
+ return null;
28
+ // Explicit expires_at wins; NULL means "compute from last_accessed_at".
29
+ // The currently-active index is guarded in pruneIndexes, not here: status='active'
30
+ // just means "was active once", not "currently in use", so it must NOT bypass TTL
31
+ // or no index would ever expire (every index is created with status='active').
32
+ if (row.expires_at !== null)
33
+ return row.expires_at;
34
+ if (row.branch_name === "DETACHED")
35
+ return row.last_accessed_at + RETENTION.detachedDays * 86400_000;
36
+ if (row.worktree_path && row.worktree_path !== row.repo_root) {
37
+ return row.last_accessed_at + RETENTION.worktreeHours * 3600_000;
38
+ }
39
+ return row.last_accessed_at + RETENTION.inactiveBranchDays * 86400_000;
40
+ }
41
+ /** Prune expired inactive indexes. Respects never-delete guards. */
42
+ export function pruneIndexes(db, now = Date.now()) {
43
+ const active = getActiveIndexId();
44
+ // Exclude indexes with an active (non-expired) write lease via NOT EXISTS.
45
+ const rows = db.prepare(`SELECT i.id AS id, i.pinned AS pinned, i.status AS status, i.branch_name AS branch,
46
+ i.last_accessed_at AS last, i.expires_at AS expires_at, i.worktree_path AS wpath, i.repo_root AS root
47
+ FROM indexes i
48
+ WHERE NOT EXISTS (
49
+ SELECT 1 FROM index_locks l WHERE l.index_id = i.id AND l.expires_at > ?
50
+ )`).all(now);
51
+ const deleted = [];
52
+ let skipped = 0;
53
+ for (const r of rows) {
54
+ // Never-delete guards.
55
+ if (r.id === active) {
56
+ skipped++;
57
+ continue;
58
+ }
59
+ if (r.pinned) {
60
+ skipped++;
61
+ continue;
62
+ }
63
+ if (now - r.last < RETENTION.graceMs) {
64
+ skipped++;
65
+ continue;
66
+ }
67
+ const expiry = r.expires_at ?? computeExpiry({ pinned: r.pinned, status: r.status, branch_name: r.branch, head_sha: "", last_accessed_at: r.last, expires_at: r.expires_at, worktree_path: r.wpath }, now);
68
+ if (expiry === null || now < expiry) {
69
+ skipped++;
70
+ continue;
71
+ }
72
+ // Delete scoped rows across all tables, transactionally.
73
+ const tx = db.transaction(() => {
74
+ db.prepare("DELETE FROM chunks_fts WHERE index_id = ?").run(r.id);
75
+ db.prepare("DELETE FROM chunks WHERE index_id = ?").run(r.id);
76
+ db.prepare("DELETE FROM symbols WHERE index_id = ?").run(r.id);
77
+ db.prepare("DELETE FROM edges WHERE index_id = ?").run(r.id);
78
+ db.prepare("DELETE FROM files WHERE index_id = ?").run(r.id);
79
+ db.prepare("DELETE FROM index_locks WHERE index_id = ?").run(r.id);
80
+ db.prepare("DELETE FROM indexes WHERE id = ?").run(r.id);
81
+ });
82
+ tx();
83
+ deleted.push(r.id);
84
+ }
85
+ return { deletedIndexes: deleted, skipped, bytesFreed: 0 };
86
+ }
87
+ //# sourceMappingURL=ttl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ttl.js","sourceRoot":"","sources":["../../../src/index/ttl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { watch } from "node:fs";
2
+ import { resolveReal, toPosix } from "../util/paths.js";
3
+ import { relative, posix } from "node:path";
4
+ /**
5
+ * File watcher (Gap #6).
6
+ *
7
+ * Maintains a set of repo-relative POSIX paths that changed since the last
8
+ * consume(), via recursive fs.watch where supported. Used by ensureFreshIndex
9
+ * to skip re-scanning during quiet periods (the main per-query cost) while
10
+ * still catching edits promptly.
11
+ *
12
+ * Platform note: recursive fs.watch is supported on macOS and Windows. On
13
+ * Linux it falls back to a non-recursive watch of the repo root only — callers
14
+ * must not rely on completeness; ensureFreshIndex periodically does a full
15
+ * scan as a safety net (FULL_SCAN_INTERVAL_MS) so missed changes are caught.
16
+ *
17
+ * Always non-fatal: if watching fails (unsupported FS, permissions), the
18
+ * watcher stays empty and the tool falls back to full-scan-per-query (the
19
+ * original behavior).
20
+ */
21
+ export class FileWatcher {
22
+ repoRoot;
23
+ watcher = null;
24
+ dirty = new Set();
25
+ started = false;
26
+ constructor(repoRoot) {
27
+ this.repoRoot = resolveReal(repoRoot);
28
+ }
29
+ start() {
30
+ if (this.started)
31
+ return;
32
+ this.started = true;
33
+ try {
34
+ this.watcher = watch(this.repoRoot, { recursive: true }, (_event, filename) => {
35
+ if (!filename)
36
+ return;
37
+ const abs = posix.join(this.repoRoot, toPosix(filename));
38
+ try {
39
+ const rel = toPosix(relative(this.repoRoot, abs));
40
+ if (rel && !rel.startsWith(".."))
41
+ this.dirty.add(rel);
42
+ }
43
+ catch { /* ignore */ }
44
+ });
45
+ this.watcher.on("error", () => { });
46
+ }
47
+ catch {
48
+ this.watcher = null; // unsupported → degrade to full-scan
49
+ }
50
+ }
51
+ stop() {
52
+ try {
53
+ this.watcher?.close();
54
+ }
55
+ catch { /* ignore */ }
56
+ this.watcher = null;
57
+ this.started = false;
58
+ }
59
+ /** True if the watcher is active (may still be partial on Linux). */
60
+ get active() {
61
+ return this.watcher !== null;
62
+ }
63
+ /** Snapshot + clear the dirty set. Returns repo-relative POSIX paths. */
64
+ consume() {
65
+ const out = [...this.dirty];
66
+ this.dirty.clear();
67
+ return out;
68
+ }
69
+ /** Mark a path dirty manually (e.g. after an in-process index write). */
70
+ markDirty(rel) {
71
+ this.dirty.add(rel);
72
+ }
73
+ }
74
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/index/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAkB,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE5C;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,OAAO,WAAW;IACd,QAAQ,CAAS;IACjB,OAAO,GAAqB,IAAI,CAAC;IAChC,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3B,OAAO,GAAG,KAAK,CAAC;IAExB,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;gBAC5E,IAAI,CAAC,QAAQ;oBAAE,OAAO;gBACtB,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACzD,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;oBAClD,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;wBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACxD,CAAC;gBAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAmD,CAAC,CAAC,CAAC;QACtF,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,qCAAqC;QAC5D,CAAC;IACH,CAAC;IAED,IAAI;QACF,IAAI,CAAC;YAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,qEAAqE;IACrE,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC;IAC/B,CAAC;IAED,yEAAyE;IACzE,OAAO;QACL,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,yEAAyE;IACzE,SAAS,CAAC,GAAW;QACnB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;CACF"}