@opencodehub/cli 0.1.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.
- package/LICENSE +202 -0
- package/README.md +85 -0
- package/dist/agent-context.d.ts +54 -0
- package/dist/agent-context.d.ts.map +1 -0
- package/dist/agent-context.js +122 -0
- package/dist/agent-context.js.map +1 -0
- package/dist/cobol-proleap-setup.d.ts +77 -0
- package/dist/cobol-proleap-setup.d.ts.map +1 -0
- package/dist/cobol-proleap-setup.js +289 -0
- package/dist/cobol-proleap-setup.js.map +1 -0
- package/dist/commands/analyze.d.ts +234 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +1096 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/augment.d.ts +48 -0
- package/dist/commands/augment.d.ts.map +1 -0
- package/dist/commands/augment.js +249 -0
- package/dist/commands/augment.js.map +1 -0
- package/dist/commands/baseline.d.ts +68 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +110 -0
- package/dist/commands/baseline.js.map +1 -0
- package/dist/commands/bench.d.ts +54 -0
- package/dist/commands/bench.d.ts.map +1 -0
- package/dist/commands/bench.js +283 -0
- package/dist/commands/bench.js.map +1 -0
- package/dist/commands/ci-init.d.ts +37 -0
- package/dist/commands/ci-init.d.ts.map +1 -0
- package/dist/commands/ci-init.js +115 -0
- package/dist/commands/ci-init.js.map +1 -0
- package/dist/commands/clean.d.ts +13 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +38 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/code-pack.d.ts +105 -0
- package/dist/commands/code-pack.d.ts.map +1 -0
- package/dist/commands/code-pack.js +187 -0
- package/dist/commands/code-pack.js.map +1 -0
- package/dist/commands/context.d.ts +30 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +237 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/detect-changes.d.ts +26 -0
- package/dist/commands/detect-changes.d.ts.map +1 -0
- package/dist/commands/detect-changes.js +73 -0
- package/dist/commands/detect-changes.js.map +1 -0
- package/dist/commands/doctor.d.ts +52 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +472 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/find-enclosing-symbol.d.ts +67 -0
- package/dist/commands/find-enclosing-symbol.d.ts.map +1 -0
- package/dist/commands/find-enclosing-symbol.js +106 -0
- package/dist/commands/find-enclosing-symbol.js.map +1 -0
- package/dist/commands/group.d.ts +123 -0
- package/dist/commands/group.d.ts.map +1 -0
- package/dist/commands/group.js +448 -0
- package/dist/commands/group.js.map +1 -0
- package/dist/commands/impact.d.ts +23 -0
- package/dist/commands/impact.d.ts.map +1 -0
- package/dist/commands/impact.js +91 -0
- package/dist/commands/impact.js.map +1 -0
- package/dist/commands/index-repo.d.ts +39 -0
- package/dist/commands/index-repo.d.ts.map +1 -0
- package/dist/commands/index-repo.js +148 -0
- package/dist/commands/index-repo.js.map +1 -0
- package/dist/commands/ingest-sarif.d.ts +64 -0
- package/dist/commands/ingest-sarif.d.ts.map +1 -0
- package/dist/commands/ingest-sarif.js +381 -0
- package/dist/commands/ingest-sarif.js.map +1 -0
- package/dist/commands/init.d.ts +75 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +315 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +17 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +79 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/mcp.d.ts +8 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +28 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/open-store.d.ts +25 -0
- package/dist/commands/open-store.d.ts.map +1 -0
- package/dist/commands/open-store.js +47 -0
- package/dist/commands/open-store.js.map +1 -0
- package/dist/commands/pack.d.ts +35 -0
- package/dist/commands/pack.d.ts.map +1 -0
- package/dist/commands/pack.js +83 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/query.d.ts +85 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +309 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/scan.d.ts +81 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +407 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/setup.d.ts +178 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +370 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/sql.d.ts +19 -0
- package/dist/commands/sql.d.ts.map +1 -0
- package/dist/commands/sql.js +51 -0
- package/dist/commands/sql.js.map +1 -0
- package/dist/commands/status.d.ts +13 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +66 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/verdict-render.d.ts +33 -0
- package/dist/commands/verdict-render.d.ts.map +1 -0
- package/dist/commands/verdict-render.js +123 -0
- package/dist/commands/verdict-render.js.map +1 -0
- package/dist/commands/verdict.d.ts +61 -0
- package/dist/commands/verdict.d.ts.map +1 -0
- package/dist/commands/verdict.js +146 -0
- package/dist/commands/verdict.js.map +1 -0
- package/dist/commands/wiki.d.ts +26 -0
- package/dist/commands/wiki.d.ts.map +1 -0
- package/dist/commands/wiki.js +74 -0
- package/dist/commands/wiki.js.map +1 -0
- package/dist/editors/claude-code.d.ts +23 -0
- package/dist/editors/claude-code.d.ts.map +1 -0
- package/dist/editors/claude-code.js +58 -0
- package/dist/editors/claude-code.js.map +1 -0
- package/dist/editors/codex.d.ts +22 -0
- package/dist/editors/codex.d.ts.map +1 -0
- package/dist/editors/codex.js +59 -0
- package/dist/editors/codex.js.map +1 -0
- package/dist/editors/cursor.d.ts +13 -0
- package/dist/editors/cursor.d.ts.map +1 -0
- package/dist/editors/cursor.js +21 -0
- package/dist/editors/cursor.js.map +1 -0
- package/dist/editors/index.d.ts +12 -0
- package/dist/editors/index.d.ts.map +1 -0
- package/dist/editors/index.js +11 -0
- package/dist/editors/index.js.map +1 -0
- package/dist/editors/opencode.d.ts +23 -0
- package/dist/editors/opencode.d.ts.map +1 -0
- package/dist/editors/opencode.js +61 -0
- package/dist/editors/opencode.js.map +1 -0
- package/dist/editors/types.d.ts +33 -0
- package/dist/editors/types.d.ts.map +1 -0
- package/dist/editors/types.js +19 -0
- package/dist/editors/types.js.map +1 -0
- package/dist/editors/windows-wrap.d.ts +19 -0
- package/dist/editors/windows-wrap.d.ts.map +1 -0
- package/dist/editors/windows-wrap.js +28 -0
- package/dist/editors/windows-wrap.js.map +1 -0
- package/dist/editors/windsurf.d.ts +12 -0
- package/dist/editors/windsurf.d.ts.map +1 -0
- package/dist/editors/windsurf.js +21 -0
- package/dist/editors/windsurf.js.map +1 -0
- package/dist/embedder-downloader.d.ts +87 -0
- package/dist/embedder-downloader.d.ts.map +1 -0
- package/dist/embedder-downloader.js +261 -0
- package/dist/embedder-downloader.js.map +1 -0
- package/dist/fs-atomic.d.ts +22 -0
- package/dist/fs-atomic.d.ts.map +1 -0
- package/dist/fs-atomic.js +28 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/groups.d.ts +64 -0
- package/dist/groups.d.ts.map +1 -0
- package/dist/groups.js +172 -0
- package/dist/groups.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +703 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/is-indexed.d.ts +20 -0
- package/dist/lib/is-indexed.d.ts.map +1 -0
- package/dist/lib/is-indexed.js +35 -0
- package/dist/lib/is-indexed.js.map +1 -0
- package/dist/registry.d.ts +64 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +145 -0
- package/dist/registry.js.map +1 -0
- package/dist/scip-downloader.d.ts +138 -0
- package/dist/scip-downloader.d.ts.map +1 -0
- package/dist/scip-downloader.js +372 -0
- package/dist/scip-downloader.js.map +1 -0
- package/dist/scip-pins.d.ts +99 -0
- package/dist/scip-pins.d.ts.map +1 -0
- package/dist/scip-pins.js +195 -0
- package/dist/scip-pins.js.map +1 -0
- package/dist/skills-gen.d.ts +47 -0
- package/dist/skills-gen.d.ts.map +1 -0
- package/dist/skills-gen.js +292 -0
- package/dist/skills-gen.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `codehub doctor` — environment probe.
|
|
3
|
+
*
|
|
4
|
+
* Runs every `Check` registered in `CHECKS`, prints a table, and exits
|
|
5
|
+
* with a status code that maps to the worst result seen:
|
|
6
|
+
*
|
|
7
|
+
* 0 all ok
|
|
8
|
+
* 1 at least one warn (non-blocking)
|
|
9
|
+
* 2 at least one fail (blocking — something is broken)
|
|
10
|
+
*
|
|
11
|
+
* Checks are strictly diagnostic — they never auto-heal. Each one returns
|
|
12
|
+
* a `hint` string the user can copy-paste when things are off.
|
|
13
|
+
*/
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { access, readFile, stat } from "node:fs/promises";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { dirname, join, resolve } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import Table from "cli-table3";
|
|
21
|
+
/**
|
|
22
|
+
* Entry point invoked by `codehub doctor`. Prints the table and sets
|
|
23
|
+
* `process.exitCode`. Also returns the structured report so tests and
|
|
24
|
+
* acceptance scripts can assert the outcome without parsing stdout.
|
|
25
|
+
*/
|
|
26
|
+
export async function runDoctor(opts = {}) {
|
|
27
|
+
const checks = buildChecks(opts);
|
|
28
|
+
const rows = [];
|
|
29
|
+
for (const check of checks) {
|
|
30
|
+
const result = await check.run().catch((err) => ({
|
|
31
|
+
status: "fail",
|
|
32
|
+
message: `check threw: ${err instanceof Error ? err.message : String(err)}`,
|
|
33
|
+
hint: "report a bug against @opencodehub/cli — checks should never throw",
|
|
34
|
+
}));
|
|
35
|
+
rows.push({ name: check.name, result });
|
|
36
|
+
}
|
|
37
|
+
printTable(rows);
|
|
38
|
+
let exitCode = 0;
|
|
39
|
+
for (const { result } of rows) {
|
|
40
|
+
if (result.status === "fail") {
|
|
41
|
+
exitCode = 2;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (result.status === "warn" && exitCode === 0) {
|
|
45
|
+
exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
process.exitCode = exitCode;
|
|
49
|
+
return { rows, exitCode };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Ordered list of environment probes. Order matters only for the output
|
|
53
|
+
* table — runs are independent, but we surface foundational checks first
|
|
54
|
+
* (Node, pnpm, native bindings) so the user can fix a broken foundation
|
|
55
|
+
* before worrying about downstream extras.
|
|
56
|
+
*/
|
|
57
|
+
export function buildChecks(opts = {}) {
|
|
58
|
+
const home = opts.home ?? homedir();
|
|
59
|
+
const repoRoot = opts.repoRoot ?? guessRepoRoot();
|
|
60
|
+
const list = [nodeVersionCheck(), pnpmInstalledCheck()];
|
|
61
|
+
if (opts.skipNative !== true) {
|
|
62
|
+
list.push(treeSitterNativeCheck(repoRoot));
|
|
63
|
+
list.push(duckdbWorksCheck(repoRoot));
|
|
64
|
+
list.push(lbugWorksCheck(repoRoot));
|
|
65
|
+
}
|
|
66
|
+
list.push(binaryOnPathCheck("semgrep", "P1 scanner — install semgrep via `brew install semgrep` or `uv tool install semgrep`"));
|
|
67
|
+
list.push(binaryOnPathCheck("osv-scanner", "P1 scanner — install from https://github.com/google/osv-scanner"));
|
|
68
|
+
list.push(binaryOnPathCheck("bandit", "P1 scanner — install with `uv tool install 'bandit[sarif]'`"));
|
|
69
|
+
list.push(binaryOnPathCheck("ruff", "P1 scanner — install with `uv tool install ruff`"));
|
|
70
|
+
list.push(binaryOnPathCheck("grype", "P1 scanner — install with `brew install anchore/grype/grype` or from https://github.com/anchore/grype"));
|
|
71
|
+
list.push(binaryOnPathCheck("vulture", "P1 scanner — install with `uv tool install vulture`"));
|
|
72
|
+
list.push(binaryOnPathCheck("pip-audit", "P1 scanner — install with `uv tool install pip-audit`"));
|
|
73
|
+
list.push(binaryOnPathCheck("radon", "P2 scanner — install with `uv tool install radon`"));
|
|
74
|
+
list.push(binaryOnPathCheck("ty", "P2 scanner (beta) — install with `uv tool install ty` (Astral)"));
|
|
75
|
+
list.push(embedderWeightsCheck(home));
|
|
76
|
+
list.push(registryPathCheck(home));
|
|
77
|
+
list.push(sarifSchemaCheck(repoRoot));
|
|
78
|
+
return list;
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Individual checks
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
function nodeVersionCheck() {
|
|
84
|
+
return {
|
|
85
|
+
name: "node >= 20",
|
|
86
|
+
async run() {
|
|
87
|
+
const v = process.versions.node;
|
|
88
|
+
const major = Number.parseInt(v.split(".")[0] ?? "0", 10);
|
|
89
|
+
if (!Number.isFinite(major) || major < 20) {
|
|
90
|
+
return {
|
|
91
|
+
status: "fail",
|
|
92
|
+
message: `node v${v} — minimum is v20.10`,
|
|
93
|
+
hint: "install node 20+ with `mise use node@20` or `nvm install 20`",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { status: "ok", message: `node v${v}` };
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function pnpmInstalledCheck() {
|
|
101
|
+
return {
|
|
102
|
+
name: "pnpm installed",
|
|
103
|
+
async run() {
|
|
104
|
+
const res = await runCommand("pnpm", ["--version"]);
|
|
105
|
+
if (res.status !== 0) {
|
|
106
|
+
return {
|
|
107
|
+
status: "fail",
|
|
108
|
+
message: "pnpm not on PATH",
|
|
109
|
+
hint: "install pnpm via `npm install -g pnpm` or `corepack enable`",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { status: "ok", message: `pnpm ${res.stdout.trim()}` };
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function treeSitterNativeCheck(repoRoot) {
|
|
117
|
+
return {
|
|
118
|
+
name: "tree-sitter native binding",
|
|
119
|
+
async run() {
|
|
120
|
+
try {
|
|
121
|
+
// tree-sitter ships a native .node binding. Loading the grammar
|
|
122
|
+
// for TS is the cheapest health signal.
|
|
123
|
+
const tsPath = resolveFromRoot(repoRoot, "tree-sitter");
|
|
124
|
+
const tssPath = resolveFromRoot(repoRoot, "tree-sitter-typescript");
|
|
125
|
+
if (!tsPath || !tssPath) {
|
|
126
|
+
return {
|
|
127
|
+
status: "warn",
|
|
128
|
+
message: "tree-sitter or tree-sitter-typescript not installed",
|
|
129
|
+
hint: "run `pnpm install` at the repo root",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const Parser = (await import(tsPath));
|
|
133
|
+
const tsMod = (await import(tssPath));
|
|
134
|
+
const ParserCtor = Parser.default ?? Parser;
|
|
135
|
+
const parser = new ParserCtor();
|
|
136
|
+
const language = tsMod.typescript ?? tsMod.default?.typescript;
|
|
137
|
+
if (!language) {
|
|
138
|
+
return {
|
|
139
|
+
status: "fail",
|
|
140
|
+
message: "tree-sitter-typescript has no `typescript` export",
|
|
141
|
+
hint: "re-run `pnpm install` to rebuild native grammars",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
parser.setLanguage(language);
|
|
145
|
+
return { status: "ok", message: "tree-sitter + typescript grammar load OK" };
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
return {
|
|
149
|
+
status: "fail",
|
|
150
|
+
message: `failed to load tree-sitter: ${err instanceof Error ? err.message : String(err)}`,
|
|
151
|
+
hint: "re-run `pnpm install` to rebuild native bindings (requires clang/g++)",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function duckdbWorksCheck(repoRoot) {
|
|
158
|
+
return {
|
|
159
|
+
name: "duckdb native binding",
|
|
160
|
+
async run() {
|
|
161
|
+
try {
|
|
162
|
+
const duckPath = resolveFromRoot(repoRoot, "@duckdb/node-api");
|
|
163
|
+
if (!duckPath) {
|
|
164
|
+
return {
|
|
165
|
+
status: "warn",
|
|
166
|
+
message: "@duckdb/node-api not installed",
|
|
167
|
+
hint: "run `pnpm install` at the repo root",
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// The @duckdb/node-api 1.x surface exposes Sync teardown helpers
|
|
171
|
+
// (`disconnectSync`, `closeSync`). The async `.close()` accessors
|
|
172
|
+
// were dropped in 1.0.0; depending on them produced a false FAIL.
|
|
173
|
+
const mod = (await import(duckPath));
|
|
174
|
+
// In-memory instance: never touches disk, never lingers.
|
|
175
|
+
const inst = await mod.DuckDBInstance.create(":memory:");
|
|
176
|
+
const conn = await inst.connect();
|
|
177
|
+
if (typeof conn.disconnectSync === "function")
|
|
178
|
+
conn.disconnectSync();
|
|
179
|
+
else if (typeof conn.close === "function")
|
|
180
|
+
await conn.close();
|
|
181
|
+
if (typeof inst.closeSync === "function")
|
|
182
|
+
inst.closeSync();
|
|
183
|
+
else if (typeof inst.close === "function")
|
|
184
|
+
await inst.close();
|
|
185
|
+
return { status: "ok", message: "duckdb open/close OK" };
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return {
|
|
189
|
+
status: "fail",
|
|
190
|
+
message: `duckdb failed to open: ${err instanceof Error ? err.message : String(err)}`,
|
|
191
|
+
hint: "check platform support — pnpm only prebuilds linux-x64/arm64, darwin-arm64/x64, win32-x64",
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Mirror of {@link duckdbWorksCheck} for the optional `@ladybugdb/core`
|
|
199
|
+
* graph-db backend. Emits `warn` (not `fail`) when the package is
|
|
200
|
+
* uninstalled because `@ladybugdb/core` is opt-in: a default `duck`
|
|
201
|
+
* deployment never needs it. When the package IS installed and the
|
|
202
|
+
* smoke test fails we surface `fail` so a broken native binding can be
|
|
203
|
+
* triaged the same way duckdb's is.
|
|
204
|
+
*/
|
|
205
|
+
function lbugWorksCheck(repoRoot) {
|
|
206
|
+
return {
|
|
207
|
+
name: "graph-db native binding",
|
|
208
|
+
async run() {
|
|
209
|
+
try {
|
|
210
|
+
const lbugPath = resolveFromRoot(repoRoot, "@ladybugdb/core");
|
|
211
|
+
if (!lbugPath) {
|
|
212
|
+
return {
|
|
213
|
+
status: "warn",
|
|
214
|
+
message: "@ladybugdb/core not installed (optional graph-db backend)",
|
|
215
|
+
hint: "run `pnpm install` and set `CODEHUB_STORE=lbug` to opt in; otherwise ignore",
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// The opt-in graph-db backend uses `@ladybugdb/core`'s `Database`
|
|
219
|
+
// entry. We exercise the load-and-close cycle the same way the
|
|
220
|
+
// duckdb check does — anything heavier would couple this probe to
|
|
221
|
+
// the adapter's evolving smoke-test surface.
|
|
222
|
+
const mod = (await import(lbugPath));
|
|
223
|
+
const ctorRaw = mod["Database"] ?? mod["default"]?.["Database"];
|
|
224
|
+
if (typeof ctorRaw !== "function") {
|
|
225
|
+
return {
|
|
226
|
+
status: "fail",
|
|
227
|
+
message: "@ladybugdb/core is installed but exports no Database constructor",
|
|
228
|
+
hint: "re-run `pnpm install` to refresh the graph-db backend bindings",
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return { status: "ok", message: "@ladybugdb/core load OK" };
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
return {
|
|
235
|
+
status: "fail",
|
|
236
|
+
message: `@ladybugdb/core failed to load: ${err instanceof Error ? err.message : String(err)}`,
|
|
237
|
+
hint: "the graph-db backend is opt-in; unset `CODEHUB_STORE=lbug` or reinstall the binding",
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function binaryOnPathCheck(bin, hint) {
|
|
244
|
+
return {
|
|
245
|
+
name: `${bin} binary`,
|
|
246
|
+
async run() {
|
|
247
|
+
const res = await runCommand(bin, ["--version"]);
|
|
248
|
+
if (res.status !== 0) {
|
|
249
|
+
return {
|
|
250
|
+
status: "warn",
|
|
251
|
+
message: `${bin} not on PATH`,
|
|
252
|
+
hint,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return { status: "ok", message: `${bin}: ${firstLine(res.stdout)}` };
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function embedderWeightsCheck(home) {
|
|
260
|
+
return {
|
|
261
|
+
name: "embedder weights",
|
|
262
|
+
async run() {
|
|
263
|
+
// Filename convention matches `embedder/src/paths.ts:modelFileName` —
|
|
264
|
+
// fp32 uses `model.onnx`, int8 uses `model_int8.onnx` (underscore,
|
|
265
|
+
// NOT hyphen). A historical hyphenated path name lingered here and
|
|
266
|
+
// caused false-negative `warn`s for users who had int8 weights on
|
|
267
|
+
// disk.
|
|
268
|
+
const base = join(home, ".codehub", "models", "gte-modernbert-base");
|
|
269
|
+
const fp32 = join(base, "fp32", "model.onnx");
|
|
270
|
+
const int8 = join(base, "int8", "model_int8.onnx");
|
|
271
|
+
const fp32Ok = await fileExists(fp32);
|
|
272
|
+
const int8Ok = await fileExists(int8);
|
|
273
|
+
if (!fp32Ok && !int8Ok) {
|
|
274
|
+
return {
|
|
275
|
+
status: "warn",
|
|
276
|
+
message: "no embedder weights found",
|
|
277
|
+
hint: "run `codehub setup --embeddings` (fp32) or `codehub setup --embeddings --int8`",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const variant = fp32Ok ? "fp32" : "int8";
|
|
281
|
+
return { status: "ok", message: `embedder weights present (${variant})` };
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function registryPathCheck(home) {
|
|
286
|
+
return {
|
|
287
|
+
name: "registry path",
|
|
288
|
+
async run() {
|
|
289
|
+
const regPath = join(home, ".codehub", "registry.json");
|
|
290
|
+
// Single attempt: branch on `ENOENT` for the missing-file case so
|
|
291
|
+
// the existence check and the read share one syscall — closes the
|
|
292
|
+
// TOCTOU gap flagged by js/file-system-race.
|
|
293
|
+
let raw;
|
|
294
|
+
try {
|
|
295
|
+
raw = await readFile(regPath, "utf8");
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
if (err.code === "ENOENT") {
|
|
299
|
+
return {
|
|
300
|
+
status: "warn",
|
|
301
|
+
message: `~/.codehub/registry.json missing`,
|
|
302
|
+
hint: "run `codehub analyze` in any git repo to create the registry",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
status: "fail",
|
|
307
|
+
message: `registry read failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
308
|
+
hint: "delete ~/.codehub/registry.json and re-run `codehub analyze`",
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
const parsed = JSON.parse(raw);
|
|
313
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
314
|
+
return {
|
|
315
|
+
status: "fail",
|
|
316
|
+
message: "registry.json is not an object",
|
|
317
|
+
hint: "back up the file and run `codehub analyze` again",
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const count = Object.keys(parsed).length;
|
|
321
|
+
return {
|
|
322
|
+
status: "ok",
|
|
323
|
+
message: `registry readable (${count} repo${count === 1 ? "" : "s"})`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
return {
|
|
328
|
+
status: "fail",
|
|
329
|
+
message: `registry parse failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
330
|
+
hint: "delete ~/.codehub/registry.json and re-run `codehub analyze`",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function sarifSchemaCheck(repoRoot) {
|
|
337
|
+
return {
|
|
338
|
+
name: "@opencodehub/sarif build",
|
|
339
|
+
async run() {
|
|
340
|
+
const pkgDir = join(repoRoot, "packages", "sarif", "dist");
|
|
341
|
+
try {
|
|
342
|
+
const s = await stat(pkgDir);
|
|
343
|
+
if (!s.isDirectory()) {
|
|
344
|
+
return {
|
|
345
|
+
status: "fail",
|
|
346
|
+
message: "@opencodehub/sarif dist is not a directory",
|
|
347
|
+
hint: "run `pnpm -r build`",
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return { status: "ok", message: "@opencodehub/sarif built" };
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return {
|
|
354
|
+
status: "warn",
|
|
355
|
+
message: "@opencodehub/sarif not built yet",
|
|
356
|
+
hint: "run `pnpm -r build`",
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Helpers
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
function printTable(rows) {
|
|
366
|
+
const table = new Table({
|
|
367
|
+
head: ["CHECK", "STATUS", "DETAIL", "HINT"],
|
|
368
|
+
style: { head: [], border: [] },
|
|
369
|
+
colWidths: [32, 8, 48, 48],
|
|
370
|
+
wordWrap: true,
|
|
371
|
+
});
|
|
372
|
+
for (const { name, result } of rows) {
|
|
373
|
+
const glyph = result.status === "ok" ? "OK" : result.status === "warn" ? "WARN" : "FAIL";
|
|
374
|
+
table.push([name, glyph, result.message, result.hint ?? ""]);
|
|
375
|
+
}
|
|
376
|
+
console.log(table.toString());
|
|
377
|
+
}
|
|
378
|
+
async function runCommand(cmd, args) {
|
|
379
|
+
return await new Promise((resolveProm) => {
|
|
380
|
+
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
381
|
+
let stdout = "";
|
|
382
|
+
let stderr = "";
|
|
383
|
+
child.stdout.on("data", (d) => {
|
|
384
|
+
stdout += d.toString("utf8");
|
|
385
|
+
});
|
|
386
|
+
child.stderr.on("data", (d) => {
|
|
387
|
+
stderr += d.toString("utf8");
|
|
388
|
+
});
|
|
389
|
+
child.on("error", () => {
|
|
390
|
+
resolveProm({ status: 127, stdout, stderr });
|
|
391
|
+
});
|
|
392
|
+
child.on("close", (code) => {
|
|
393
|
+
resolveProm({ status: code ?? 0, stdout, stderr });
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
async function fileExists(path) {
|
|
398
|
+
try {
|
|
399
|
+
await access(path);
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function firstLine(s) {
|
|
407
|
+
const idx = s.indexOf("\n");
|
|
408
|
+
return idx < 0 ? s.trim() : s.slice(0, idx).trim();
|
|
409
|
+
}
|
|
410
|
+
function guessRepoRoot() {
|
|
411
|
+
// `codehub` ships from packages/cli/dist/commands — walking four levels
|
|
412
|
+
// up lands at the monorepo root where `packages/` and `node_modules/`
|
|
413
|
+
// live.
|
|
414
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
415
|
+
return resolve(here, "..", "..", "..", "..");
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Resolve an npm package for the native-binding checks. We try two
|
|
419
|
+
* environments in order, so doctor returns `OK` in every realistic install
|
|
420
|
+
* shape — monorepo checkout, global `pnpm i -g`, tarball, symlinked bin:
|
|
421
|
+
*
|
|
422
|
+
* 1. The CLI package's own `node_modules` — `createRequire` off
|
|
423
|
+
* `import.meta.url` walks outward through the same resolution chain
|
|
424
|
+
* Node would use for a real `await import(pkg)` from inside the CLI,
|
|
425
|
+
* so this is the authoritative answer for "can the CLI load this?".
|
|
426
|
+
* 2. The supplied `repoRoot` — legacy fallback for the in-monorepo case
|
|
427
|
+
* where the CLI is running from `packages/cli/dist/` and dependencies
|
|
428
|
+
* hoist to the workspace root.
|
|
429
|
+
*
|
|
430
|
+
* We stop at the first hit. Returning `null` preserves the existing
|
|
431
|
+
* semantics of the caller (`warn` result with a reinstall hint).
|
|
432
|
+
*/
|
|
433
|
+
function resolveFromRoot(repoRoot, pkg) {
|
|
434
|
+
// 1. CLI's own resolution context — the canonical answer.
|
|
435
|
+
try {
|
|
436
|
+
const req = createRequire(import.meta.url);
|
|
437
|
+
return req.resolve(pkg);
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
// fall through to repoRoot
|
|
441
|
+
}
|
|
442
|
+
// 2. Workspace/monorepo root fallback.
|
|
443
|
+
try {
|
|
444
|
+
const req = createRequire(join(repoRoot, "package.json"));
|
|
445
|
+
return req.resolve(pkg);
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
// fall through to per-package fallbacks
|
|
449
|
+
}
|
|
450
|
+
// 3. Per-workspace fallback. Under pnpm strict isolation, native bindings
|
|
451
|
+
// are direct deps of the package that uses them — `tree-sitter*` lives
|
|
452
|
+
// in `packages/ingestion`, `@duckdb/node-api` in `packages/storage`.
|
|
453
|
+
// Probing those package.json contexts lets `codehub doctor` resolve
|
|
454
|
+
// the bindings even when neither the CLI nor the workspace root
|
|
455
|
+
// declare them as direct deps.
|
|
456
|
+
const owners = pkg.startsWith("@duckdb/") || pkg.startsWith("@ladybugdb/")
|
|
457
|
+
? ["packages/storage"]
|
|
458
|
+
: pkg.startsWith("tree-sitter")
|
|
459
|
+
? ["packages/ingestion"]
|
|
460
|
+
: [];
|
|
461
|
+
for (const owner of owners) {
|
|
462
|
+
try {
|
|
463
|
+
const req = createRequire(join(repoRoot, owner, "package.json"));
|
|
464
|
+
return req.resolve(pkg);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// try next
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,MAAM,YAAY,CAAC;AA6B/B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAsB,EAAE;IACtD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,IAAI,GAAiD,EAAE,CAAC;IAC9D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CACpC,CAAC,GAAY,EAAe,EAAE,CAAC,CAAC;YAC9B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,gBAAgB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAC3E,IAAI,EAAE,mEAAmE;SAC1E,CAAC,CACH,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,UAAU,CAAC,IAAI,CAAC,CAAC;IAEjB,IAAI,QAAQ,GAAc,CAAC,CAAC;IAC5B,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC7B,QAAQ,GAAG,CAAC,CAAC;YACb,MAAM;QACR,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC/C,QAAQ,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC5B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAsB,EAAE;IAClD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,aAAa,EAAE,CAAC;IAClD,MAAM,IAAI,GAAY,CAAC,gBAAgB,EAAE,EAAE,kBAAkB,EAAE,CAAC,CAAC;IACjE,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,CAAC,IAAI,CACP,iBAAiB,CACf,SAAS,EACT,sFAAsF,CACvF,CACF,CAAC;IACF,IAAI,CAAC,IAAI,CACP,iBAAiB,CACf,aAAa,EACb,iEAAiE,CAClE,CACF,CAAC;IACF,IAAI,CAAC,IAAI,CACP,iBAAiB,CAAC,QAAQ,EAAE,6DAA6D,CAAC,CAC3F,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,kDAAkD,CAAC,CAAC,CAAC;IACzF,IAAI,CAAC,IAAI,CACP,iBAAiB,CACf,OAAO,EACP,uGAAuG,CACxG,CACF,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,SAAS,EAAE,qDAAqD,CAAC,CAAC,CAAC;IAC/F,IAAI,CAAC,IAAI,CACP,iBAAiB,CAAC,WAAW,EAAE,uDAAuD,CAAC,CACxF,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,mDAAmD,CAAC,CAAC,CAAC;IAC3F,IAAI,CAAC,IAAI,CACP,iBAAiB,CAAC,IAAI,EAAE,gEAAgE,CAAC,CAC1F,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,SAAS,gBAAgB;IACvB,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,KAAK,CAAC,GAAG;YACP,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;YAChC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;YAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;gBAC1C,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,SAAS,CAAC,sBAAsB;oBACzC,IAAI,EAAE,8DAA8D;iBACrE,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC;QACjD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB;IACzB,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,KAAK,CAAC,GAAG;YACP,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;YACpD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrB,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,kBAAkB;oBAC3B,IAAI,EAAE,6DAA6D;iBACpE,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC;QAChE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAgB;IAC7C,OAAO;QACL,IAAI,EAAE,4BAA4B;QAClC,KAAK,CAAC,GAAG;YACP,IAAI,CAAC;gBACH,gEAAgE;gBAChE,wCAAwC;gBACxC,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;gBACxD,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,EAAE,wBAAwB,CAAC,CAAC;gBACpE,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;oBACxB,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,qDAAqD;wBAC9D,IAAI,EAAE,qCAAqC;qBAC5C,CAAC;gBACJ,CAAC;gBACD,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,CAA8C,CAAC;gBACnF,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAGnC,CAAC;gBACF,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,IAAK,MAAuC,CAAC;gBAC9E,MAAM,MAAM,GAAG,IAAK,UAA8D,EAAE,CAAC;gBACrF,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC;gBAC/D,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,mDAAmD;wBAC5D,IAAI,EAAE,kDAAkD;qBACzD,CAAC;gBACJ,CAAC;gBACD,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;gBAC7B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,0CAA0C,EAAE,CAAC;YAC/E,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;oBAC1F,IAAI,EAAE,uEAAuE;iBAC9E,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB;IACxC,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,KAAK,CAAC,GAAG;YACP,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;gBAC/D,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,gCAAgC;wBACzC,IAAI,EAAE,qCAAqC;qBAC5C,CAAC;gBACJ,CAAC;gBACD,iEAAiE;gBACjE,kEAAkE;gBAClE,kEAAkE;gBAClE,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAWlC,CAAC;gBACF,yDAAyD;gBACzD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBACzD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClC,IAAI,OAAO,IAAI,CAAC,cAAc,KAAK,UAAU;oBAAE,IAAI,CAAC,cAAc,EAAE,CAAC;qBAChE,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,UAAU;oBAAE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9D,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,UAAU;oBAAE,IAAI,CAAC,SAAS,EAAE,CAAC;qBACtD,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,UAAU;oBAAE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9D,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,sBAAsB,EAAE,CAAC;YAC3D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,0BAA0B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;oBACrF,IAAI,EAAE,2FAA2F;iBAClG,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,cAAc,CAAC,QAAgB;IACtC,OAAO;QACL,IAAI,EAAE,yBAAyB;QAC/B,KAAK,CAAC,GAAG;YACP,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;gBAC9D,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,2DAA2D;wBACpE,IAAI,EAAE,6EAA6E;qBACpF,CAAC;gBACJ,CAAC;gBACD,kEAAkE;gBAClE,+DAA+D;gBAC/D,kEAAkE;gBAClE,6CAA6C;gBAC7C,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAA4B,CAAC;gBAChE,MAAM,OAAO,GACX,GAAG,CAAC,UAAU,CAAC,IAAK,GAAG,CAAC,SAAS,CAAyC,EAAE,CAAC,UAAU,CAAC,CAAC;gBAC3F,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;oBAClC,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,kEAAkE;wBAC3E,IAAI,EAAE,gEAAgE;qBACvE,CAAC;gBACJ,CAAC;gBACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC;YAC9D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,mCAAmC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;oBAC9F,IAAI,EAAE,qFAAqF;iBAC5F,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAW,EAAE,IAAY;IAClD,OAAO;QACL,IAAI,EAAE,GAAG,GAAG,SAAS;QACrB,KAAK,CAAC,GAAG;YACP,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;YACjD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrB,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,GAAG,GAAG,cAAc;oBAC7B,IAAI;iBACL,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,GAAG,KAAK,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACvE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAY;IACxC,OAAO;QACL,IAAI,EAAE,kBAAkB;QACxB,KAAK,CAAC,GAAG;YACP,sEAAsE;YACtE,mEAAmE;YACnE,mEAAmE;YACnE,kEAAkE;YAClE,QAAQ;YACR,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC;YACrE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;YAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;gBACvB,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,2BAA2B;oBACpC,IAAI,EAAE,gFAAgF;iBACvF,CAAC;YACJ,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;YACzC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,6BAA6B,OAAO,GAAG,EAAE,CAAC;QAC5E,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,KAAK,CAAC,GAAG;YACP,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;YACxD,kEAAkE;YAClE,kEAAkE;YAClE,6CAA6C;YAC7C,IAAI,GAAW,CAAC;YAChB,IAAI,CAAC;gBACH,GAAG,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACxC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACrD,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,kCAAkC;wBAC3C,IAAI,EAAE,8DAA8D;qBACrE,CAAC;gBACJ,CAAC;gBACD,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,yBAAyB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;oBACpF,IAAI,EAAE,8DAA8D;iBACrE,CAAC;YACJ,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;gBAC1C,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC3E,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,gCAAgC;wBACzC,IAAI,EAAE,kDAAkD;qBACzD,CAAC;gBACJ,CAAC;gBACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;gBACzC,OAAO;oBACL,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,sBAAsB,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG;iBACtE,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,0BAA0B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;oBACrF,IAAI,EAAE,8DAA8D;iBACrE,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB;IACxC,OAAO;QACL,IAAI,EAAE,0BAA0B;QAChC,KAAK,CAAC,GAAG;YACP,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YAC3D,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC7B,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;oBACrB,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,4CAA4C;wBACrD,IAAI,EAAE,qBAAqB;qBAC5B,CAAC;gBACJ,CAAC;gBACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;YAC/D,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,kCAAkC;oBAC3C,IAAI,EAAE,qBAAqB;iBAC5B,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,UAAU,CACjB,IAAwE;IAExE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;QACtB,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAC3C,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QAC/B,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC;QAC1B,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IACH,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QACzF,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,GAAW,EACX,IAAuB;IAEvB,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,EAAE;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACtE,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YACpC,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YACpC,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;YACxC,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,CAAS;IAC1B,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,aAAa;IACpB,wEAAwE;IACxE,sEAAsE;IACtE,QAAQ;IACR,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,eAAe,CAAC,QAAgB,EAAE,GAAW;IACpD,0DAA0D;IAC1D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,2BAA2B;IAC7B,CAAC;IACD,uCAAuC;IACvC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC;QAC1D,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IACD,0EAA0E;IAC1E,0EAA0E;IAC1E,wEAAwE;IACxE,uEAAuE;IACvE,mEAAmE;IACnE,kCAAkC;IAClC,MAAM,MAAM,GACV,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC;QACzD,CAAC,CAAC,CAAC,kBAAkB,CAAC;QACtB,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC;YAC7B,CAAC,CAAC,CAAC,oBAAoB,CAAC;YACxB,CAAC,CAAC,EAAE,CAAC;IACX,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;YACjE,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,WAAW;QACb,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `findEnclosingSymbolId` — deterministic tightest-span lookup that maps a
|
|
3
|
+
* `(filePath, line)` pair back to the OpenCodeHub graph node that owns the
|
|
4
|
+
* line (a Function / Method / Class / etc.). Used by `ingest-sarif` to link
|
|
5
|
+
* SARIF `Finding` nodes to the enclosing code symbol when the scanner did
|
|
6
|
+
* not populate `result.properties["opencodehub.symbolId"]` itself.
|
|
7
|
+
*
|
|
8
|
+
* This is a clone of the algorithm in
|
|
9
|
+
* `packages/ingestion/src/pipeline/phases/scip-index.ts:indexNodesByFile` +
|
|
10
|
+
* `findEnclosingNodeId`. The two call sites live in different packages
|
|
11
|
+
* (`@opencodehub/cli` vs `@opencodehub/ingestion`), and extracting a shared
|
|
12
|
+
* helper would require a cross-package refactor that is explicitly out of
|
|
13
|
+
* scope for the SARIF linkage task. If these functions need to converge
|
|
14
|
+
* later, promote this file to a shared util package (e.g.
|
|
15
|
+
* `@opencodehub/graph-utils`) and delete the duplicate in scip-index.ts in
|
|
16
|
+
* a single atomic change.
|
|
17
|
+
*
|
|
18
|
+
* Notes on 1-indexing: both SARIF 2.1.0 `region.startLine` and
|
|
19
|
+
* OpenCodeHub node `startLine`/`endLine` are 1-based, so no offset
|
|
20
|
+
* adjustment is needed at the call site.
|
|
21
|
+
*/
|
|
22
|
+
import type { NodeId, NodeKind } from "@opencodehub/core-types";
|
|
23
|
+
/** A graph node projection carrying only the fields the lookup needs. */
|
|
24
|
+
export interface NodeRow {
|
|
25
|
+
readonly id: NodeId;
|
|
26
|
+
readonly filePath: string;
|
|
27
|
+
readonly startLine: number;
|
|
28
|
+
readonly endLine: number;
|
|
29
|
+
readonly kind: NodeKind;
|
|
30
|
+
}
|
|
31
|
+
/** Per-file, start-line-ascending index used by `findEnclosingSymbolId`. */
|
|
32
|
+
export type NodesByFile = ReadonlyMap<string, readonly NodeRow[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Code-kind allow set used when resolving SARIF findings back to an
|
|
35
|
+
* enclosing symbol. Covers Function, Method, Constructor, Class,
|
|
36
|
+
* Interface, Struct, Enum, and Trait — a strict superset of
|
|
37
|
+
* `SCIP_SYMBOL_KINDS`; we additionally allow `Constructor` here because
|
|
38
|
+
* SARIF tooling routinely emits findings inside constructor bodies.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ENCLOSING_SYMBOL_KINDS: ReadonlySet<NodeKind>;
|
|
41
|
+
/**
|
|
42
|
+
* Build a per-file, start-line-ascending index over the supplied node
|
|
43
|
+
* rows, filtering to nodes whose `kind` is in `ENCLOSING_SYMBOL_KINDS`.
|
|
44
|
+
* Rows missing either `startLine` or `endLine` are skipped silently —
|
|
45
|
+
* they cannot participate in a range containment check.
|
|
46
|
+
*
|
|
47
|
+
* Ordering: within each file the array is sorted by `startLine` ascending
|
|
48
|
+
* with `endLine` ascending as the tie-breaker. `findEnclosingSymbolId`
|
|
49
|
+
* still scans the whole candidate list for the tightest span, so the
|
|
50
|
+
* sort is primarily an early-break optimization (once `startLine > line`
|
|
51
|
+
* we can stop).
|
|
52
|
+
*/
|
|
53
|
+
export declare function indexNodesByFile(rows: readonly NodeRow[]): NodesByFile;
|
|
54
|
+
/**
|
|
55
|
+
* Return the id of the tightest-span node in `nodesByFile[filePath]`
|
|
56
|
+
* that encloses `line` (`startLine <= line <= endLine`). "Tightest"
|
|
57
|
+
* means smallest `endLine - startLine` span — this makes nested methods
|
|
58
|
+
* win over their containing classes. When two candidates have the same
|
|
59
|
+
* span, the earlier `startLine` wins (which falls out of the deterministic
|
|
60
|
+
* input sort).
|
|
61
|
+
*
|
|
62
|
+
* Returns `undefined` when the file is unknown, when no candidate
|
|
63
|
+
* contains the line, or when every candidate has been filtered out by
|
|
64
|
+
* the allow-set at index time.
|
|
65
|
+
*/
|
|
66
|
+
export declare function findEnclosingSymbolId(nodesByFile: NodesByFile, filePath: string, line: number): NodeId | undefined;
|
|
67
|
+
//# sourceMappingURL=find-enclosing-symbol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-enclosing-symbol.d.ts","sourceRoot":"","sources":["../../src/commands/find-enclosing-symbol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEhE,yEAAyE;AACzE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AAED,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,QAAQ,CASvD,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,WAAW,CAgBtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAiBpB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `findEnclosingSymbolId` — deterministic tightest-span lookup that maps a
|
|
3
|
+
* `(filePath, line)` pair back to the OpenCodeHub graph node that owns the
|
|
4
|
+
* line (a Function / Method / Class / etc.). Used by `ingest-sarif` to link
|
|
5
|
+
* SARIF `Finding` nodes to the enclosing code symbol when the scanner did
|
|
6
|
+
* not populate `result.properties["opencodehub.symbolId"]` itself.
|
|
7
|
+
*
|
|
8
|
+
* This is a clone of the algorithm in
|
|
9
|
+
* `packages/ingestion/src/pipeline/phases/scip-index.ts:indexNodesByFile` +
|
|
10
|
+
* `findEnclosingNodeId`. The two call sites live in different packages
|
|
11
|
+
* (`@opencodehub/cli` vs `@opencodehub/ingestion`), and extracting a shared
|
|
12
|
+
* helper would require a cross-package refactor that is explicitly out of
|
|
13
|
+
* scope for the SARIF linkage task. If these functions need to converge
|
|
14
|
+
* later, promote this file to a shared util package (e.g.
|
|
15
|
+
* `@opencodehub/graph-utils`) and delete the duplicate in scip-index.ts in
|
|
16
|
+
* a single atomic change.
|
|
17
|
+
*
|
|
18
|
+
* Notes on 1-indexing: both SARIF 2.1.0 `region.startLine` and
|
|
19
|
+
* OpenCodeHub node `startLine`/`endLine` are 1-based, so no offset
|
|
20
|
+
* adjustment is needed at the call site.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Code-kind allow set used when resolving SARIF findings back to an
|
|
24
|
+
* enclosing symbol. Covers Function, Method, Constructor, Class,
|
|
25
|
+
* Interface, Struct, Enum, and Trait — a strict superset of
|
|
26
|
+
* `SCIP_SYMBOL_KINDS`; we additionally allow `Constructor` here because
|
|
27
|
+
* SARIF tooling routinely emits findings inside constructor bodies.
|
|
28
|
+
*/
|
|
29
|
+
export const ENCLOSING_SYMBOL_KINDS = new Set([
|
|
30
|
+
"Function",
|
|
31
|
+
"Method",
|
|
32
|
+
"Constructor",
|
|
33
|
+
"Class",
|
|
34
|
+
"Interface",
|
|
35
|
+
"Struct",
|
|
36
|
+
"Enum",
|
|
37
|
+
"Trait",
|
|
38
|
+
]);
|
|
39
|
+
/**
|
|
40
|
+
* Build a per-file, start-line-ascending index over the supplied node
|
|
41
|
+
* rows, filtering to nodes whose `kind` is in `ENCLOSING_SYMBOL_KINDS`.
|
|
42
|
+
* Rows missing either `startLine` or `endLine` are skipped silently —
|
|
43
|
+
* they cannot participate in a range containment check.
|
|
44
|
+
*
|
|
45
|
+
* Ordering: within each file the array is sorted by `startLine` ascending
|
|
46
|
+
* with `endLine` ascending as the tie-breaker. `findEnclosingSymbolId`
|
|
47
|
+
* still scans the whole candidate list for the tightest span, so the
|
|
48
|
+
* sort is primarily an early-break optimization (once `startLine > line`
|
|
49
|
+
* we can stop).
|
|
50
|
+
*/
|
|
51
|
+
export function indexNodesByFile(rows) {
|
|
52
|
+
const map = new Map();
|
|
53
|
+
for (const row of rows) {
|
|
54
|
+
if (!ENCLOSING_SYMBOL_KINDS.has(row.kind))
|
|
55
|
+
continue;
|
|
56
|
+
if (!Number.isFinite(row.startLine) || !Number.isFinite(row.endLine))
|
|
57
|
+
continue;
|
|
58
|
+
const bucket = map.get(row.filePath);
|
|
59
|
+
if (bucket === undefined)
|
|
60
|
+
map.set(row.filePath, [row]);
|
|
61
|
+
else
|
|
62
|
+
bucket.push(row);
|
|
63
|
+
}
|
|
64
|
+
for (const arr of map.values()) {
|
|
65
|
+
arr.sort((a, b) => {
|
|
66
|
+
if (a.startLine !== b.startLine)
|
|
67
|
+
return a.startLine - b.startLine;
|
|
68
|
+
return a.endLine - b.endLine;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return map;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Return the id of the tightest-span node in `nodesByFile[filePath]`
|
|
75
|
+
* that encloses `line` (`startLine <= line <= endLine`). "Tightest"
|
|
76
|
+
* means smallest `endLine - startLine` span — this makes nested methods
|
|
77
|
+
* win over their containing classes. When two candidates have the same
|
|
78
|
+
* span, the earlier `startLine` wins (which falls out of the deterministic
|
|
79
|
+
* input sort).
|
|
80
|
+
*
|
|
81
|
+
* Returns `undefined` when the file is unknown, when no candidate
|
|
82
|
+
* contains the line, or when every candidate has been filtered out by
|
|
83
|
+
* the allow-set at index time.
|
|
84
|
+
*/
|
|
85
|
+
export function findEnclosingSymbolId(nodesByFile, filePath, line) {
|
|
86
|
+
const candidates = nodesByFile.get(filePath);
|
|
87
|
+
if (candidates === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
let best;
|
|
90
|
+
let bestSpan = Number.POSITIVE_INFINITY;
|
|
91
|
+
for (const rec of candidates) {
|
|
92
|
+
// Candidates are sorted by startLine; once we pass the target line
|
|
93
|
+
// no later row can enclose it.
|
|
94
|
+
if (rec.startLine > line)
|
|
95
|
+
break;
|
|
96
|
+
if (rec.endLine < line)
|
|
97
|
+
continue;
|
|
98
|
+
const span = rec.endLine - rec.startLine;
|
|
99
|
+
if (span < bestSpan) {
|
|
100
|
+
best = rec;
|
|
101
|
+
bestSpan = span;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return best?.id;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=find-enclosing-symbol.js.map
|