pi-plans 0.2.0 → 0.3.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/README.md +74 -21
- package/index.ts +115 -9
- package/package.json +7 -1
- package/references/pi-planning-workflow.md +18 -3
- package/references/state-and-config.md +34 -2
- package/scripts/validate.ts +4 -0
- package/src/code-graph/commands.ts +437 -0
- package/src/code-graph/discovery.ts +118 -0
- package/src/code-graph/git.ts +108 -0
- package/src/code-graph/identity.ts +59 -0
- package/src/code-graph/indexer.ts +281 -0
- package/src/code-graph/materialize.ts +166 -0
- package/src/code-graph/mode.ts +28 -0
- package/src/code-graph/mutations.ts +160 -0
- package/src/code-graph/parser.ts +51 -0
- package/src/code-graph/parsers/javascript.ts +35 -0
- package/src/code-graph/parsers/python.ts +160 -0
- package/src/code-graph/parsers/tree-sitter.ts +316 -0
- package/src/code-graph/paths.ts +85 -0
- package/src/code-graph/prompts.ts +18 -0
- package/src/code-graph/resolver.ts +69 -0
- package/src/code-graph/runtime.ts +158 -0
- package/src/code-graph/schema.ts +135 -0
- package/src/code-graph/screening.ts +82 -0
- package/src/code-graph/store.ts +278 -0
- package/src/code-graph/summary.ts +435 -0
- package/src/code-graph/types.ts +163 -0
- package/src/compaction.ts +1125 -371
- package/src/config-command.ts +326 -0
- package/src/exec.ts +356 -686
- package/src/refine-prompts.ts +50 -0
- package/src/refine-ui-helpers.ts +71 -18
- package/src/refine-ui-state.ts +87 -21
- package/src/refine-ui.ts +210 -102
- package/src/state.ts +19 -6
- package/src/subagent.ts +163 -61
- package/tests/ask-choice.test.ts +263 -0
- package/tests/autocomplete.test.ts +6 -1
- package/tests/code-graph-apply.test.ts +185 -0
- package/tests/code-graph-commands.test.ts +211 -0
- package/tests/code-graph-db.test.ts +166 -0
- package/tests/code-graph-discovery.test.ts +38 -0
- package/tests/code-graph-git.test.ts +94 -0
- package/tests/code-graph-index.test.ts +175 -0
- package/tests/code-graph-loop.e2e.test.ts +159 -0
- package/tests/code-graph-mutations.test.ts +117 -0
- package/tests/code-graph-parser.test.ts +85 -0
- package/tests/code-graph-rollback.test.ts +100 -0
- package/tests/code-graph-summary-batching.test.ts +518 -0
- package/tests/code-graph-summary.test.ts +148 -0
- package/tests/compaction.test.ts +371 -57
- package/tests/config-command.test.ts +255 -0
- package/tests/exec.test.ts +665 -241
- package/tests/fixtures/code-graph/sample.js +36 -0
- package/tests/fixtures/code-graph/sample.py +20 -0
- package/tests/fixtures/code-graph/sample.ts +15 -0
- package/tests/graph-aware-file-tools.test.ts +411 -0
- package/tests/refine-prompts.test.ts +67 -2
- package/tests/refine-ui.test.ts +337 -72
- package/tests/subagent.test.ts +26 -20
- package/tools/ask-choice.ts +158 -11
- package/tools/code-graph.ts +254 -0
- package/tools/graph-aware-file-tools.ts +392 -0
- package/tools/plans.ts +84 -1
- package/tools/refine.ts +61 -15
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conservative static call resolution. Captures call expressions of the form
|
|
3
|
+
* `name(...)` and `obj.method(...)` inside the same file; cross-file
|
|
4
|
+
* resolution is limited to relative imports and explicit module exports.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SourceLocation } from "./types.ts";
|
|
8
|
+
import type { DiscoveredFile } from "./discovery.ts";
|
|
9
|
+
|
|
10
|
+
export interface CallSite {
|
|
11
|
+
fromFunction: string;
|
|
12
|
+
calleeText: string;
|
|
13
|
+
kind: "call" | "definition" | "import";
|
|
14
|
+
resolution: "resolved" | "ambiguous" | "unresolved";
|
|
15
|
+
target?: { fileDir: string; fileName: string; functionName: string };
|
|
16
|
+
reason?: string;
|
|
17
|
+
provenance: SourceLocation;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CALL_PATTERN = /\b([a-zA-Z_$][\w$]*)(?:\.([a-zA-Z_$][\w$]*))?\s*\(/g;
|
|
21
|
+
|
|
22
|
+
export function resolveCalls(fromFunction: string, code: string, file: DiscoveredFile, out: CallSite[]): void {
|
|
23
|
+
for (const match of code.matchAll(CALL_PATTERN)) {
|
|
24
|
+
const head = match[1];
|
|
25
|
+
const member = match[2];
|
|
26
|
+
const start = match.index ?? 0;
|
|
27
|
+
const calleeText = match[0];
|
|
28
|
+
out.push({
|
|
29
|
+
fromFunction,
|
|
30
|
+
calleeText,
|
|
31
|
+
kind: "call",
|
|
32
|
+
resolution: "unresolved",
|
|
33
|
+
reason: "conservative resolution: same-file or relative import only",
|
|
34
|
+
provenance: locationOf(code, start, calleeText.length),
|
|
35
|
+
});
|
|
36
|
+
void head;
|
|
37
|
+
void member;
|
|
38
|
+
void file;
|
|
39
|
+
}
|
|
40
|
+
for (const match of code.matchAll(/^\s*(?:import|from)\s+([\w./-]+)/gm)) {
|
|
41
|
+
const start = match.index ?? 0;
|
|
42
|
+
out.push({
|
|
43
|
+
fromFunction,
|
|
44
|
+
calleeText: match[0],
|
|
45
|
+
kind: "import",
|
|
46
|
+
resolution: "unresolved",
|
|
47
|
+
reason: "import resolution deferred to module graph (not implemented in v1)",
|
|
48
|
+
provenance: locationOf(code, start, match[0].length),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function locationOf(code: string, byteStart: number, length: number): SourceLocation {
|
|
54
|
+
const before = code.slice(0, byteStart);
|
|
55
|
+
const startLine = before.split("\n").length;
|
|
56
|
+
const startColumn = before.length - before.lastIndexOf("\n");
|
|
57
|
+
const targetText = code.slice(byteStart, byteStart + length);
|
|
58
|
+
const endLine = startLine + targetText.split("\n").length - 1;
|
|
59
|
+
const endColumn =
|
|
60
|
+
targetText.split("\n").pop()?.length ?? startColumn;
|
|
61
|
+
return {
|
|
62
|
+
startByte: byteStart,
|
|
63
|
+
endByte: byteStart + length,
|
|
64
|
+
startLine,
|
|
65
|
+
startColumn,
|
|
66
|
+
endLine,
|
|
67
|
+
endColumn,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy runtime guards for the code-graph module. Imports happen on first use
|
|
3
|
+
* so that a missing parser dependency or unsupported Node version cannot
|
|
4
|
+
* prevent the rest of pi-plans from registering.
|
|
5
|
+
*
|
|
6
|
+
* The module never throws at import time; every consumer calls one of the
|
|
7
|
+
* `ensure*` helpers and inspects the resulting status object.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
export type RuntimeIssue =
|
|
13
|
+
| { kind: "node-version"; detail: string }
|
|
14
|
+
| { kind: "sqlite-import"; detail: string }
|
|
15
|
+
| { kind: "parser-import"; detail: string; package: string }
|
|
16
|
+
| { kind: "runtime-unsupported"; detail: string };
|
|
17
|
+
|
|
18
|
+
export interface RuntimeStatus {
|
|
19
|
+
nodeVersion: string;
|
|
20
|
+
nodeMajor: number;
|
|
21
|
+
hasExperimentalSqliteFlag: boolean;
|
|
22
|
+
graphEffectiveFloorMet: boolean;
|
|
23
|
+
parserAvailable: boolean;
|
|
24
|
+
sqliteAvailable: boolean;
|
|
25
|
+
bunDetected: boolean;
|
|
26
|
+
issues: RuntimeIssue[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const MAJOR = Number(process.versions.node.split(".")[0]);
|
|
30
|
+
const MINOR = Number(process.versions.node.split(".")[1] ?? "0");
|
|
31
|
+
|
|
32
|
+
export const IS_BUN = typeof (process as { versions?: { bun?: string } }).versions?.bun === "string";
|
|
33
|
+
|
|
34
|
+
function flagHasExperimentalSqlite(): boolean {
|
|
35
|
+
try {
|
|
36
|
+
const out = execSync(process.execPath, ["--experimental-sqlite", "-e", "1"], { encoding: "utf8" });
|
|
37
|
+
return out === "1";
|
|
38
|
+
} catch {
|
|
39
|
+
try {
|
|
40
|
+
const out = execSync(process.execPath, ["-e", "process.features.sqlite ?? \"off\""], { encoding: "utf8" }).trim();
|
|
41
|
+
return out !== "off" && out !== "undefined" && out !== "\"off\"";
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function importSqlite(): Promise<unknown> {
|
|
49
|
+
return await import("node:sqlite");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function importParser(): Promise<unknown> {
|
|
53
|
+
const mod = await import("tree-sitter");
|
|
54
|
+
const js = await import("tree-sitter-javascript");
|
|
55
|
+
const ts = await import("tree-sitter-typescript");
|
|
56
|
+
const py = await import("tree-sitter-python");
|
|
57
|
+
return { default: mod.default ?? mod, js: js.default ?? js, ts: ts.default ?? ts, py: py.default ?? py };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function detectRuntimeStatus(): Promise<RuntimeStatus> {
|
|
61
|
+
const issues: RuntimeIssue[] = [];
|
|
62
|
+
if (IS_BUN) {
|
|
63
|
+
issues.push({
|
|
64
|
+
kind: "runtime-unsupported",
|
|
65
|
+
detail: "graph feature is Node-only; detected Bun runtime",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const nodeVersion = process.versions.node;
|
|
69
|
+
const graphEffectiveFloorMet = IS_BUN
|
|
70
|
+
? false
|
|
71
|
+
: (MAJOR === 22 && MINOR >= 13) || MAJOR > 22;
|
|
72
|
+
const hasFlag = flagHasExperimentalSqlite();
|
|
73
|
+
const sqliteAvailable = await importSqlite()
|
|
74
|
+
.then(() => true)
|
|
75
|
+
.catch((err: Error) => {
|
|
76
|
+
issues.push({ kind: "sqlite-import", detail: err.message });
|
|
77
|
+
return false;
|
|
78
|
+
});
|
|
79
|
+
if (!sqliteAvailable && !hasFlag && !IS_BUN) {
|
|
80
|
+
issues.push({
|
|
81
|
+
kind: "node-version",
|
|
82
|
+
detail: `node:sqlite requires Node >=22.13 or --experimental-sqlite; current ${nodeVersion}`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const parserAvailable = await importParser()
|
|
86
|
+
.then(() => true)
|
|
87
|
+
.catch((err: Error) => {
|
|
88
|
+
issues.push({
|
|
89
|
+
kind: "parser-import",
|
|
90
|
+
package: "tree-sitter",
|
|
91
|
+
detail: err.message,
|
|
92
|
+
});
|
|
93
|
+
return false;
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
nodeVersion,
|
|
97
|
+
nodeMajor: MAJOR,
|
|
98
|
+
hasExperimentalSqliteFlag: hasFlag,
|
|
99
|
+
graphEffectiveFloorMet,
|
|
100
|
+
parserAvailable,
|
|
101
|
+
sqliteAvailable,
|
|
102
|
+
bunDetected: IS_BUN,
|
|
103
|
+
issues,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface GraphRuntime {
|
|
108
|
+
sqlite: typeof import("node:sqlite");
|
|
109
|
+
parser: {
|
|
110
|
+
Parser: typeof import("tree-sitter").default;
|
|
111
|
+
javascript: unknown;
|
|
112
|
+
typescript: unknown;
|
|
113
|
+
tsx: unknown;
|
|
114
|
+
python: unknown;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let cached: GraphRuntime | null = null;
|
|
119
|
+
|
|
120
|
+
export async function loadGraphRuntime(): Promise<{ runtime: GraphRuntime; status: RuntimeStatus }> {
|
|
121
|
+
if (cached) return { runtime: cached, status: await detectRuntimeStatus() };
|
|
122
|
+
const sqlite = await importSqlite();
|
|
123
|
+
const ParserMod = await import("tree-sitter");
|
|
124
|
+
const jsMod = await import("tree-sitter-javascript");
|
|
125
|
+
const tsMod = await import("tree-sitter-typescript");
|
|
126
|
+
const pyMod = await import("tree-sitter-python");
|
|
127
|
+
const Parser = (ParserMod as { default?: unknown }).default ?? ParserMod;
|
|
128
|
+
const runtime: GraphRuntime = {
|
|
129
|
+
sqlite: sqlite as typeof import("node:sqlite"),
|
|
130
|
+
parser: {
|
|
131
|
+
Parser: Parser as typeof import("tree-sitter").default,
|
|
132
|
+
javascript: (jsMod as { default?: unknown }).default ?? jsMod,
|
|
133
|
+
typescript: (tsMod as { default?: { typescript: unknown; tsx: unknown } }).default?.typescript ?? tsMod,
|
|
134
|
+
tsx: (tsMod as { default?: { tsx: unknown } }).default?.tsx ?? tsMod,
|
|
135
|
+
python: (pyMod as { default?: unknown }).default ?? pyMod,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
cached = runtime;
|
|
139
|
+
return { runtime, status: await detectRuntimeStatus() };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function describeRuntimeIssues(status: RuntimeStatus): string[] {
|
|
143
|
+
if (status.issues.length === 0) return [];
|
|
144
|
+
return status.issues.map((issue) => {
|
|
145
|
+
switch (issue.kind) {
|
|
146
|
+
case "node-version":
|
|
147
|
+
return `Node version: ${issue.detail}`;
|
|
148
|
+
case "sqlite-import":
|
|
149
|
+
return `node:sqlite import failed: ${issue.detail}`;
|
|
150
|
+
case "parser-import":
|
|
151
|
+
return `parser import failed (${issue.package}): ${issue.detail}`;
|
|
152
|
+
case "runtime-unsupported":
|
|
153
|
+
return `runtime: ${issue.detail}`;
|
|
154
|
+
default:
|
|
155
|
+
return "unknown runtime issue";
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema definition and migrations for the code-graph SQLite database. The
|
|
3
|
+
* database is intentionally normalized — JSON views are derived from the
|
|
4
|
+
* edge tables at query time and never stored alongside them.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const CURRENT_SCHEMA_VERSION = 2;
|
|
8
|
+
|
|
9
|
+
export const SCHEMA_STATEMENTS: string[] = [
|
|
10
|
+
`CREATE TABLE IF NOT EXISTS graph_meta (
|
|
11
|
+
schema_version INTEGER PRIMARY KEY,
|
|
12
|
+
worktree_root TEXT NOT NULL,
|
|
13
|
+
git_common_dir TEXT NOT NULL,
|
|
14
|
+
parser_versions TEXT NOT NULL,
|
|
15
|
+
updated_at TEXT NOT NULL
|
|
16
|
+
)`,
|
|
17
|
+
`CREATE TABLE IF NOT EXISTS files (
|
|
18
|
+
file_dir TEXT NOT NULL,
|
|
19
|
+
file_name TEXT NOT NULL,
|
|
20
|
+
language TEXT NOT NULL,
|
|
21
|
+
source_hash TEXT NOT NULL,
|
|
22
|
+
source_text TEXT NOT NULL,
|
|
23
|
+
pending_kind TEXT,
|
|
24
|
+
updated_at TEXT NOT NULL,
|
|
25
|
+
PRIMARY KEY (file_dir, file_name)
|
|
26
|
+
)`,
|
|
27
|
+
`CREATE TABLE IF NOT EXISTS file_entries (
|
|
28
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
29
|
+
file_dir TEXT NOT NULL,
|
|
30
|
+
file_name TEXT NOT NULL,
|
|
31
|
+
ordinal INTEGER NOT NULL,
|
|
32
|
+
kind TEXT NOT NULL,
|
|
33
|
+
function_name TEXT,
|
|
34
|
+
start_byte INTEGER NOT NULL,
|
|
35
|
+
end_byte INTEGER NOT NULL,
|
|
36
|
+
text TEXT NOT NULL,
|
|
37
|
+
UNIQUE (file_dir, file_name, ordinal)
|
|
38
|
+
)`,
|
|
39
|
+
`CREATE INDEX IF NOT EXISTS idx_file_entries_file ON file_entries (file_dir, file_name, ordinal)`,
|
|
40
|
+
`CREATE TABLE IF NOT EXISTS functions (
|
|
41
|
+
file_dir TEXT NOT NULL,
|
|
42
|
+
file_name TEXT NOT NULL,
|
|
43
|
+
function_name TEXT NOT NULL,
|
|
44
|
+
language TEXT NOT NULL,
|
|
45
|
+
kind TEXT NOT NULL,
|
|
46
|
+
full_code TEXT NOT NULL,
|
|
47
|
+
full_code_hash TEXT NOT NULL,
|
|
48
|
+
render_code TEXT NOT NULL,
|
|
49
|
+
render_code_hash TEXT NOT NULL,
|
|
50
|
+
parent TEXT,
|
|
51
|
+
container TEXT,
|
|
52
|
+
move_supported INTEGER NOT NULL,
|
|
53
|
+
is_primary INTEGER NOT NULL,
|
|
54
|
+
overload_signatures TEXT,
|
|
55
|
+
provenance_start_byte INTEGER NOT NULL,
|
|
56
|
+
provenance_end_byte INTEGER NOT NULL,
|
|
57
|
+
provenance_start_line INTEGER NOT NULL,
|
|
58
|
+
provenance_start_col INTEGER NOT NULL,
|
|
59
|
+
provenance_end_line INTEGER NOT NULL,
|
|
60
|
+
provenance_end_col INTEGER NOT NULL,
|
|
61
|
+
summary_description TEXT,
|
|
62
|
+
summary_inputs TEXT,
|
|
63
|
+
summary_outputs TEXT,
|
|
64
|
+
summary_status TEXT,
|
|
65
|
+
summary_model TEXT,
|
|
66
|
+
summary_schema_version INTEGER,
|
|
67
|
+
summary_effective_effort TEXT,
|
|
68
|
+
summary_error TEXT,
|
|
69
|
+
summary_updated_at TEXT,
|
|
70
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
71
|
+
PRIMARY KEY (file_dir, file_name, function_name)
|
|
72
|
+
)`,
|
|
73
|
+
`CREATE TABLE IF NOT EXISTS call_edges (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
from_file_dir TEXT NOT NULL,
|
|
76
|
+
from_file_name TEXT NOT NULL,
|
|
77
|
+
from_function TEXT NOT NULL,
|
|
78
|
+
to_file_dir TEXT,
|
|
79
|
+
to_file_name TEXT,
|
|
80
|
+
to_function TEXT,
|
|
81
|
+
to_callee_text TEXT NOT NULL,
|
|
82
|
+
kind TEXT NOT NULL,
|
|
83
|
+
resolution TEXT NOT NULL,
|
|
84
|
+
reason TEXT,
|
|
85
|
+
provenance_start_byte INTEGER NOT NULL,
|
|
86
|
+
provenance_end_byte INTEGER NOT NULL,
|
|
87
|
+
provenance_start_line INTEGER NOT NULL,
|
|
88
|
+
provenance_start_col INTEGER NOT NULL,
|
|
89
|
+
provenance_end_line INTEGER NOT NULL,
|
|
90
|
+
provenance_end_col INTEGER NOT NULL
|
|
91
|
+
)`,
|
|
92
|
+
`CREATE INDEX IF NOT EXISTS idx_call_edges_from ON call_edges (from_file_dir, from_file_name, from_function)`,
|
|
93
|
+
`CREATE INDEX IF NOT EXISTS idx_call_edges_to ON call_edges (to_file_dir, to_file_name, to_function)`,
|
|
94
|
+
`CREATE TABLE IF NOT EXISTS change_log (
|
|
95
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
96
|
+
kind TEXT NOT NULL,
|
|
97
|
+
detail TEXT NOT NULL,
|
|
98
|
+
recorded_at TEXT NOT NULL
|
|
99
|
+
)`,
|
|
100
|
+
`CREATE TABLE IF NOT EXISTS reindex_conflicts (
|
|
101
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
102
|
+
file_dir TEXT NOT NULL,
|
|
103
|
+
file_name TEXT NOT NULL,
|
|
104
|
+
kind TEXT NOT NULL,
|
|
105
|
+
detail TEXT NOT NULL,
|
|
106
|
+
recorded_at TEXT NOT NULL
|
|
107
|
+
)`,
|
|
108
|
+
`CREATE TABLE IF NOT EXISTS code_graph_snapshot (
|
|
109
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
|
+
head_commit TEXT NOT NULL,
|
|
111
|
+
uncommitted_paths TEXT NOT NULL,
|
|
112
|
+
recorded_at TEXT NOT NULL
|
|
113
|
+
)`,
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
export const FUNCTION_RECORDS_VIEW = `
|
|
117
|
+
CREATE VIEW IF NOT EXISTS function_records AS
|
|
118
|
+
SELECT
|
|
119
|
+
f.file_dir AS file_dir,
|
|
120
|
+
f.file_name AS file_name,
|
|
121
|
+
f.function_name AS function_name,
|
|
122
|
+
f.language AS language,
|
|
123
|
+
f.kind AS kind,
|
|
124
|
+
f.parent AS parent,
|
|
125
|
+
f.container AS container,
|
|
126
|
+
f.move_supported AS move_supported,
|
|
127
|
+
f.is_primary AS is_primary,
|
|
128
|
+
f.provenance_start_byte AS provenance_start_byte,
|
|
129
|
+
f.provenance_end_byte AS provenance_end_byte,
|
|
130
|
+
(SELECT json_group_array(json_object('file_dir', e.from_file_dir, 'file_name', e.from_file_name, 'function_name', e.from_function))
|
|
131
|
+
FROM call_edges e WHERE e.to_file_dir = f.file_dir AND e.to_file_name = f.file_name AND e.to_function = f.function_name AND e.resolution = 'resolved') AS in_links_json,
|
|
132
|
+
(SELECT json_group_array(json_object('file_dir', e.to_file_dir, 'file_name', e.to_file_name, 'function_name', e.to_function))
|
|
133
|
+
FROM call_edges e WHERE e.from_file_dir = f.file_dir AND e.from_file_name = f.file_name AND e.from_function = f.function_name AND e.resolution = 'resolved') AS out_links_json
|
|
134
|
+
FROM functions f
|
|
135
|
+
`;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only screening queries that exclude full_code from the response.
|
|
3
|
+
* Used by the agent-facing tool and `/graph-status`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Store } from "./store.ts";
|
|
7
|
+
import type { FunctionScreening, Language } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export interface ScreeningOptions {
|
|
10
|
+
store: Store;
|
|
11
|
+
language?: Language;
|
|
12
|
+
functionNameLike?: string;
|
|
13
|
+
limit?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function screeningQuery(opts: ScreeningOptions): FunctionScreening[] {
|
|
17
|
+
const conditions: string[] = [];
|
|
18
|
+
const params: Array<string | number> = [];
|
|
19
|
+
if (opts.language) {
|
|
20
|
+
conditions.push("f.language = ?");
|
|
21
|
+
params.push(opts.language);
|
|
22
|
+
}
|
|
23
|
+
if (opts.functionNameLike) {
|
|
24
|
+
conditions.push("f.function_name LIKE ?");
|
|
25
|
+
params.push(`%${opts.functionNameLike}%`);
|
|
26
|
+
}
|
|
27
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
28
|
+
const limit = Math.max(1, Math.min(opts.limit ?? 100, 500));
|
|
29
|
+
const rows = opts.store
|
|
30
|
+
.read(() =>
|
|
31
|
+
opts.store.db
|
|
32
|
+
.prepare(
|
|
33
|
+
`SELECT
|
|
34
|
+
f.file_dir AS file_dir,
|
|
35
|
+
f.file_name AS file_name,
|
|
36
|
+
f.function_name AS function_name,
|
|
37
|
+
f.language AS language,
|
|
38
|
+
f.kind AS kind,
|
|
39
|
+
f.summary_description AS description,
|
|
40
|
+
f.summary_inputs AS inputs_json,
|
|
41
|
+
f.summary_outputs AS outputs_json,
|
|
42
|
+
f.summary_status AS summary_status,
|
|
43
|
+
f.version AS version,
|
|
44
|
+
(SELECT json_group_array(json_object('file_dir', e.from_file_dir, 'file_name', e.from_file_name, 'function_name', e.from_function))
|
|
45
|
+
FROM call_edges e WHERE e.to_file_dir = f.file_dir AND e.to_file_name = f.file_name AND e.to_function = f.function_name AND e.resolution = 'resolved') AS in_json,
|
|
46
|
+
(SELECT json_group_array(json_object('file_dir', e.to_file_dir, 'file_name', e.to_file_name, 'function_name', e.to_function))
|
|
47
|
+
FROM call_edges e WHERE e.from_file_dir = f.file_dir AND e.from_file_name = f.file_name AND e.from_function = f.function_name AND e.resolution = 'resolved') AS out_json
|
|
48
|
+
FROM functions f
|
|
49
|
+
${where}
|
|
50
|
+
ORDER BY f.file_dir, f.file_name, f.function_name
|
|
51
|
+
LIMIT ${limit}`,
|
|
52
|
+
)
|
|
53
|
+
.all(...params),
|
|
54
|
+
) as Array<{
|
|
55
|
+
file_dir: string;
|
|
56
|
+
file_name: string;
|
|
57
|
+
function_name: string;
|
|
58
|
+
language: Language;
|
|
59
|
+
kind: string;
|
|
60
|
+
description: string | null;
|
|
61
|
+
inputs_json: string | null;
|
|
62
|
+
outputs_json: string | null;
|
|
63
|
+
summary_status: string | null;
|
|
64
|
+
version: number;
|
|
65
|
+
in_json: string | null;
|
|
66
|
+
out_json: string | null;
|
|
67
|
+
}>;
|
|
68
|
+
return rows.map((row) => ({
|
|
69
|
+
fileDir: row.file_dir,
|
|
70
|
+
fileName: row.file_name,
|
|
71
|
+
functionName: row.function_name,
|
|
72
|
+
language: row.language,
|
|
73
|
+
kind: row.kind as FunctionScreening["kind"],
|
|
74
|
+
description: row.description,
|
|
75
|
+
inputs: row.inputs_json ? (JSON.parse(row.inputs_json) as string[]) : null,
|
|
76
|
+
outputs: row.outputs_json ? (JSON.parse(row.outputs_json) as string[]) : null,
|
|
77
|
+
version: row.version,
|
|
78
|
+
summaryStatus: row.summary_status as FunctionScreening["summaryStatus"],
|
|
79
|
+
inLinks: row.in_json ? (JSON.parse(row.in_json) as FunctionScreening["inLinks"]) : [],
|
|
80
|
+
outLinks: row.out_json ? (JSON.parse(row.out_json) as FunctionScreening["outLinks"]) : [],
|
|
81
|
+
}));
|
|
82
|
+
}
|