co-maintainer 0.4.0-beta.1
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 +21 -0
- package/README.md +105 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +9 -0
- package/dist/mod.d.ts +4 -0
- package/dist/mod.js +2 -0
- package/dist/package.json +54 -0
- package/dist/src/ai/batch.d.ts +16 -0
- package/dist/src/ai/batch.js +117 -0
- package/dist/src/ai/fake.d.ts +7 -0
- package/dist/src/ai/fake.js +40 -0
- package/dist/src/ai/hetzner.d.ts +16 -0
- package/dist/src/ai/hetzner.js +69 -0
- package/dist/src/ai/mermaid.d.ts +27 -0
- package/dist/src/ai/mermaid.js +457 -0
- package/dist/src/ai/mermaid_loop.d.ts +12 -0
- package/dist/src/ai/mermaid_loop.js +100 -0
- package/dist/src/ai/openrouter.d.ts +9 -0
- package/dist/src/ai/openrouter.js +29 -0
- package/dist/src/ai/provider.d.ts +4 -0
- package/dist/src/ai/provider.js +63 -0
- package/dist/src/cli/args.d.ts +5 -0
- package/dist/src/cli/args.js +252 -0
- package/dist/src/cli/commands/probe.d.ts +2 -0
- package/dist/src/cli/commands/probe.js +122 -0
- package/dist/src/cli/commands/review.d.ts +3 -0
- package/dist/src/cli/commands/review.js +84 -0
- package/dist/src/cli/commands/serve.d.ts +18 -0
- package/dist/src/cli/commands/serve.js +183 -0
- package/dist/src/cli/commands/set.d.ts +7 -0
- package/dist/src/cli/commands/set.js +171 -0
- package/dist/src/cli/main.d.ts +1 -0
- package/dist/src/cli/main.js +27 -0
- package/dist/src/cli/prompt.d.ts +4 -0
- package/dist/src/cli/prompt.js +26 -0
- package/dist/src/cli/review_args.d.ts +28 -0
- package/dist/src/cli/review_args.js +84 -0
- package/dist/src/cli/review_output.d.ts +2 -0
- package/dist/src/cli/review_output.js +10 -0
- package/dist/src/cli/review_result.d.ts +81 -0
- package/dist/src/cli/review_result.js +281 -0
- package/dist/src/config.d.ts +108 -0
- package/dist/src/config.js +138 -0
- package/dist/src/git/default_branch.d.ts +5 -0
- package/dist/src/git/default_branch.js +22 -0
- package/dist/src/github/app.d.ts +43 -0
- package/dist/src/github/app.js +120 -0
- package/dist/src/github/client.d.ts +17 -0
- package/dist/src/github/client.js +66 -0
- package/dist/src/github/collect.d.ts +7 -0
- package/dist/src/github/collect.js +368 -0
- package/dist/src/github/gh.d.ts +8 -0
- package/dist/src/github/gh.js +63 -0
- package/dist/src/github/jwt.d.ts +12 -0
- package/dist/src/github/jwt.js +84 -0
- package/dist/src/github/oauth.d.ts +9 -0
- package/dist/src/github/oauth.js +51 -0
- package/dist/src/github/pat.d.ts +10 -0
- package/dist/src/github/pat.js +60 -0
- package/dist/src/knowledge/facts.d.ts +3 -0
- package/dist/src/knowledge/facts.js +325 -0
- package/dist/src/knowledge/guide.d.ts +6 -0
- package/dist/src/knowledge/guide.js +82 -0
- package/dist/src/knowledge/probe.d.ts +12 -0
- package/dist/src/knowledge/probe.js +144 -0
- package/dist/src/knowledge/sections.d.ts +2 -0
- package/dist/src/knowledge/sections.js +13 -0
- package/dist/src/knowledge/skill.d.ts +10 -0
- package/dist/src/knowledge/skill.js +106 -0
- package/dist/src/knowledge/synthesis.d.ts +7 -0
- package/dist/src/knowledge/synthesis.js +315 -0
- package/dist/src/knowledge/types.d.ts +49 -0
- package/dist/src/knowledge/types.js +1 -0
- package/dist/src/knowledge/validate.d.ts +6 -0
- package/dist/src/knowledge/validate.js +134 -0
- package/dist/src/local/carry_over_store.d.ts +18 -0
- package/dist/src/local/carry_over_store.js +40 -0
- package/dist/src/local/codegraph_exclude.d.ts +3 -0
- package/dist/src/local/codegraph_exclude.js +26 -0
- package/dist/src/local/codegraph_prepare.d.ts +17 -0
- package/dist/src/local/codegraph_prepare.js +51 -0
- package/dist/src/local/git_ops.d.ts +12 -0
- package/dist/src/local/git_ops.js +98 -0
- package/dist/src/local/git_parse.d.ts +27 -0
- package/dist/src/local/git_parse.js +131 -0
- package/dist/src/local/git_revision.d.ts +15 -0
- package/dist/src/local/git_revision.js +141 -0
- package/dist/src/local/git_untracked.d.ts +21 -0
- package/dist/src/local/git_untracked.js +79 -0
- package/dist/src/local/review_local.d.ts +4 -0
- package/dist/src/local/review_local.js +273 -0
- package/dist/src/local/review_lock.d.ts +7 -0
- package/dist/src/local/review_lock.js +45 -0
- package/dist/src/pr/checkout.d.ts +25 -0
- package/dist/src/pr/checkout.js +136 -0
- package/dist/src/pr/codegraph_tool_args.d.ts +8 -0
- package/dist/src/pr/codegraph_tool_args.js +34 -0
- package/dist/src/pr/codegraph_tools.d.ts +15 -0
- package/dist/src/pr/codegraph_tools.js +408 -0
- package/dist/src/pr/diff_summary.d.ts +6 -0
- package/dist/src/pr/diff_summary.js +52 -0
- package/dist/src/pr/findings.d.ts +14 -0
- package/dist/src/pr/findings.js +94 -0
- package/dist/src/pr/github_revision.d.ts +4 -0
- package/dist/src/pr/github_revision.js +39 -0
- package/dist/src/pr/hunks.d.ts +18 -0
- package/dist/src/pr/hunks.js +73 -0
- package/dist/src/pr/reviewer.d.ts +40 -0
- package/dist/src/pr/reviewer.js +582 -0
- package/dist/src/pr/rounds.d.ts +17 -0
- package/dist/src/pr/rounds.js +43 -0
- package/dist/src/pr/scope.d.ts +35 -0
- package/dist/src/pr/scope.js +84 -0
- package/dist/src/pr/snapshot.d.ts +11 -0
- package/dist/src/pr/snapshot.js +1 -0
- package/dist/src/pr/suggestion.d.ts +17 -0
- package/dist/src/pr/suggestion.js +70 -0
- package/dist/src/remote/client.d.ts +4 -0
- package/dist/src/remote/client.js +199 -0
- package/dist/src/remote/client_tools.d.ts +13 -0
- package/dist/src/remote/client_tools.js +30 -0
- package/dist/src/remote/fixtures.d.ts +1 -0
- package/dist/src/remote/fixtures.js +30 -0
- package/dist/src/remote/revision_from_submit.d.ts +2 -0
- package/dist/src/remote/revision_from_submit.js +26 -0
- package/dist/src/remote/schema.d.ts +14 -0
- package/dist/src/remote/schema.js +30 -0
- package/dist/src/remote/server/body.d.ts +18 -0
- package/dist/src/remote/server/body.js +73 -0
- package/dist/src/remote/server/routes.d.ts +1 -0
- package/dist/src/remote/server/routes.js +150 -0
- package/dist/src/remote/server/sessions.d.ts +16 -0
- package/dist/src/remote/server/sessions.js +48 -0
- package/dist/src/remote/server/sync.d.ts +3 -0
- package/dist/src/remote/server/sync.js +90 -0
- package/dist/src/remote/tool_bridge.d.ts +15 -0
- package/dist/src/remote/tool_bridge.js +84 -0
- package/dist/src/remote/validate.d.ts +8 -0
- package/dist/src/remote/validate.js +248 -0
- package/dist/src/review/carry_over.d.ts +63 -0
- package/dist/src/review/carry_over.js +372 -0
- package/dist/src/review/engine.d.ts +13 -0
- package/dist/src/review/engine.js +4 -0
- package/dist/src/review/guides.d.ts +12 -0
- package/dist/src/review/guides.js +44 -0
- package/dist/src/review/revision.d.ts +26 -0
- package/dist/src/review/revision.js +57 -0
- package/dist/src/server/api/activity.d.ts +1 -0
- package/dist/src/server/api/activity.js +12 -0
- package/dist/src/server/api/analytics.d.ts +1 -0
- package/dist/src/server/api/analytics.js +9 -0
- package/dist/src/server/api/installations.d.ts +4 -0
- package/dist/src/server/api/installations.js +24 -0
- package/dist/src/server/api/jobs.d.ts +1 -0
- package/dist/src/server/api/jobs.js +86 -0
- package/dist/src/server/api/json_body.d.ts +1 -0
- package/dist/src/server/api/json_body.js +14 -0
- package/dist/src/server/api/remote_tokens.d.ts +1 -0
- package/dist/src/server/api/remote_tokens.js +71 -0
- package/dist/src/server/api/repos.d.ts +4 -0
- package/dist/src/server/api/repos.js +136 -0
- package/dist/src/server/api/settings.d.ts +1 -0
- package/dist/src/server/api/settings.js +210 -0
- package/dist/src/server/app.d.ts +27 -0
- package/dist/src/server/app.js +147 -0
- package/dist/src/server/assets.d.ts +3 -0
- package/dist/src/server/assets.js +21 -0
- package/dist/src/server/auth.d.ts +44 -0
- package/dist/src/server/auth.js +188 -0
- package/dist/src/server/errors.d.ts +8 -0
- package/dist/src/server/errors.js +10 -0
- package/dist/src/server/http.d.ts +8 -0
- package/dist/src/server/http.js +117 -0
- package/dist/src/server/logo.d.ts +1 -0
- package/dist/src/server/logo.js +2 -0
- package/dist/src/server/pages/activity.d.ts +4 -0
- package/dist/src/server/pages/activity.js +200 -0
- package/dist/src/server/pages/add_repo.d.ts +9 -0
- package/dist/src/server/pages/add_repo.js +56 -0
- package/dist/src/server/pages/analytics.d.ts +2 -0
- package/dist/src/server/pages/analytics.js +193 -0
- package/dist/src/server/pages/client.d.ts +2 -0
- package/dist/src/server/pages/client.js +2 -0
- package/dist/src/server/pages/home.d.ts +16 -0
- package/dist/src/server/pages/home.js +87 -0
- package/dist/src/server/pages/knowledge.d.ts +2 -0
- package/dist/src/server/pages/knowledge.js +64 -0
- package/dist/src/server/pages/layout.d.ts +32 -0
- package/dist/src/server/pages/layout.js +230 -0
- package/dist/src/server/pages/pr.d.ts +2 -0
- package/dist/src/server/pages/pr.js +109 -0
- package/dist/src/server/pages/repo.d.ts +2 -0
- package/dist/src/server/pages/repo.js +92 -0
- package/dist/src/server/pages/repo_prs.d.ts +2 -0
- package/dist/src/server/pages/repo_prs.js +75 -0
- package/dist/src/server/pages/repo_remote.d.ts +2 -0
- package/dist/src/server/pages/repo_remote.js +38 -0
- package/dist/src/server/pages/repo_settings.d.ts +3 -0
- package/dist/src/server/pages/repo_settings.js +127 -0
- package/dist/src/server/pages/router.d.ts +17 -0
- package/dist/src/server/pages/router.js +312 -0
- package/dist/src/server/pages/settings.d.ts +2 -0
- package/dist/src/server/pages/settings.js +337 -0
- package/dist/src/server/pages/setup.d.ts +5 -0
- package/dist/src/server/pages/setup.js +38 -0
- package/dist/src/server/pages/styles.d.ts +2 -0
- package/dist/src/server/pages/styles.js +2 -0
- package/dist/src/server/webhook/index.d.ts +1 -0
- package/dist/src/server/webhook/index.js +64 -0
- package/dist/src/server/webhook/signature.d.ts +7 -0
- package/dist/src/server/webhook/signature.js +41 -0
- package/dist/src/services/credentials.d.ts +19 -0
- package/dist/src/services/credentials.js +146 -0
- package/dist/src/services/dashboard.d.ts +165 -0
- package/dist/src/services/dashboard.js +200 -0
- package/dist/src/services/drift.d.ts +13 -0
- package/dist/src/services/drift.js +76 -0
- package/dist/src/services/jobs.d.ts +34 -0
- package/dist/src/services/jobs.js +222 -0
- package/dist/src/services/remote_review.d.ts +10 -0
- package/dist/src/services/remote_review.js +274 -0
- package/dist/src/services/remote_review_abort.d.ts +3 -0
- package/dist/src/services/remote_review_abort.js +11 -0
- package/dist/src/services/remote_token_jobs.d.ts +1 -0
- package/dist/src/services/remote_token_jobs.js +20 -0
- package/dist/src/services/remote_tokens.d.ts +13 -0
- package/dist/src/services/remote_tokens.js +57 -0
- package/dist/src/services/replies.d.ts +7 -0
- package/dist/src/services/replies.js +248 -0
- package/dist/src/services/review.d.ts +35 -0
- package/dist/src/services/review.js +671 -0
- package/dist/src/services/setup.d.ts +29 -0
- package/dist/src/services/setup.js +364 -0
- package/dist/src/services/webhook.d.ts +22 -0
- package/dist/src/services/webhook.js +401 -0
- package/dist/src/store/app_db.d.ts +10 -0
- package/dist/src/store/app_db.js +85 -0
- package/dist/src/store/cache_db.d.ts +4 -0
- package/dist/src/store/cache_db.js +63 -0
- package/dist/src/store/deliveries.d.ts +12 -0
- package/dist/src/store/deliveries.js +23 -0
- package/dist/src/store/drift.d.ts +4 -0
- package/dist/src/store/drift.js +22 -0
- package/dist/src/store/findings.d.ts +31 -0
- package/dist/src/store/findings.js +64 -0
- package/dist/src/store/installations.d.ts +9 -0
- package/dist/src/store/installations.js +35 -0
- package/dist/src/store/job_logs.d.ts +5 -0
- package/dist/src/store/job_logs.js +17 -0
- package/dist/src/store/jobs.d.ts +27 -0
- package/dist/src/store/jobs.js +86 -0
- package/dist/src/store/migrations.d.ts +5 -0
- package/dist/src/store/migrations.js +258 -0
- package/dist/src/store/remote_review_inputs.d.ts +11 -0
- package/dist/src/store/remote_review_inputs.js +25 -0
- package/dist/src/store/remote_tokens.d.ts +10 -0
- package/dist/src/store/remote_tokens.js +45 -0
- package/dist/src/store/replies.d.ts +24 -0
- package/dist/src/store/replies.js +79 -0
- package/dist/src/store/repos.d.ts +11 -0
- package/dist/src/store/repos.js +59 -0
- package/dist/src/store/reviews.d.ts +86 -0
- package/dist/src/store/reviews.js +220 -0
- package/dist/src/store/rows.d.ts +179 -0
- package/dist/src/store/rows.js +4 -0
- package/dist/src/store/sessions.d.ts +8 -0
- package/dist/src/store/sessions.js +34 -0
- package/dist/src/store/skill_state.d.ts +3 -0
- package/dist/src/store/skill_state.js +24 -0
- package/dist/src/store/sqlite.d.ts +39 -0
- package/dist/src/store/sqlite.js +77 -0
- package/dist/src/store/subjects.d.ts +15 -0
- package/dist/src/store/subjects.js +78 -0
- package/dist/src/tools/codegraph.d.ts +58 -0
- package/dist/src/tools/codegraph.js +165 -0
- package/dist/src/tools/codegraph_exec.d.ts +13 -0
- package/dist/src/tools/codegraph_exec.js +66 -0
- package/dist/src/types.d.ts +89 -0
- package/dist/src/types.js +1 -0
- package/dist/src/util/atomic.d.ts +4 -0
- package/dist/src/util/atomic.js +78 -0
- package/dist/src/util/keyed_lock.d.ts +3 -0
- package/dist/src/util/keyed_lock.js +33 -0
- package/dist/src/util/log.d.ts +11 -0
- package/dist/src/util/log.js +42 -0
- package/dist/src/util/redact.d.ts +5 -0
- package/dist/src/util/redact.js +30 -0
- package/dist/src/util/runtime.d.ts +90 -0
- package/dist/src/util/runtime.js +206 -0
- package/dist/src/util/time.d.ts +9 -0
- package/dist/src/util/time.js +36 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +5 -0
- package/package.json +54 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { cloneDir, worktreeDir } from "../config.js";
|
|
2
|
+
import { log } from "../util/log.js";
|
|
3
|
+
import { withKeyedLock } from "../util/keyed_lock.js";
|
|
4
|
+
import { commandOutput, isWindows, stat } from "../util/runtime.js";
|
|
5
|
+
export function withCloneLock(repo, fn) {
|
|
6
|
+
return withKeyedLock(`clone:${repo}`, fn);
|
|
7
|
+
}
|
|
8
|
+
export async function runCommand(command, args, cwd) {
|
|
9
|
+
// Windows resolves git and other shims through the shell, not as bare exes.
|
|
10
|
+
const windows = isWindows();
|
|
11
|
+
const output = await commandOutput(windows ? "cmd" : command, {
|
|
12
|
+
args: windows ? ["/c", command, ...args] : args,
|
|
13
|
+
cwd,
|
|
14
|
+
stdout: "piped",
|
|
15
|
+
stderr: "piped",
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
code: output.code,
|
|
19
|
+
stdout: new TextDecoder().decode(output.stdout),
|
|
20
|
+
stderr: new TextDecoder().decode(output.stderr),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export async function pathExists(path) {
|
|
24
|
+
try {
|
|
25
|
+
await stat(path);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function cloneInto(repo, dir, run) {
|
|
33
|
+
log("checkout", `cloning ${repo} into ${dir}`);
|
|
34
|
+
const started = performance.now();
|
|
35
|
+
const result = await run("git", [
|
|
36
|
+
"clone",
|
|
37
|
+
"--filter=blob:none",
|
|
38
|
+
`https://github.com/${repo}`,
|
|
39
|
+
dir,
|
|
40
|
+
]);
|
|
41
|
+
if (result.code !== 0) {
|
|
42
|
+
throw new Error(`git clone failed: ${result.stderr.trim()}`);
|
|
43
|
+
}
|
|
44
|
+
// Deep source trees overrun the Windows path limit even under a short root.
|
|
45
|
+
await run("git", ["config", "core.longpaths", "true"], dir);
|
|
46
|
+
log("checkout", `cloned in ${((performance.now() - started) / 1000).toFixed(1)}s`);
|
|
47
|
+
return dir;
|
|
48
|
+
}
|
|
49
|
+
/** In-flight clone per destination directory. A benchmark run reviews several
|
|
50
|
+
* PRs of the same repo concurrently, and each one's scope/map computation
|
|
51
|
+
* calls `ensureClone` independently; without this, the first concurrent burst
|
|
52
|
+
* has every caller see "not cloned yet" and race `git clone` into the same
|
|
53
|
+
* directory — one wins, the rest fail with "already exists" and silently fall
|
|
54
|
+
* back to an unscoped review. Concurrent callers now await the one real clone
|
|
55
|
+
* instead. The two statements between the `pathExists` check and this map's
|
|
56
|
+
* `get`/`set` are synchronous (no `await` between them), so whichever caller's
|
|
57
|
+
* continuation runs first registers the clone before any other caller can
|
|
58
|
+
* observe an empty map entry — ordinary JS single-threaded scheduling, not a
|
|
59
|
+
* lock. */
|
|
60
|
+
const cloningInFlight = new Map();
|
|
61
|
+
/** Like `ensureClone` but assumes the caller already holds `withCloneLock`. */
|
|
62
|
+
export async function ensureCloneUnlocked(repo, run) {
|
|
63
|
+
const dir = cloneDir(repo);
|
|
64
|
+
if (await pathExists(`${dir}/.git`))
|
|
65
|
+
return dir;
|
|
66
|
+
let promise = cloningInFlight.get(dir);
|
|
67
|
+
if (!promise) {
|
|
68
|
+
promise = cloneInto(repo, dir, run).finally(() => {
|
|
69
|
+
cloningInFlight.delete(dir);
|
|
70
|
+
});
|
|
71
|
+
cloningInFlight.set(dir, promise);
|
|
72
|
+
}
|
|
73
|
+
return promise;
|
|
74
|
+
}
|
|
75
|
+
export async function ensureClone(repo, run) {
|
|
76
|
+
return withCloneLock(repo, () => ensureCloneUnlocked(repo, run));
|
|
77
|
+
}
|
|
78
|
+
/** Resolves `candidate` to a commit SHA, or returns undefined. Two plain
|
|
79
|
+
* commands rather than one `rev-parse <rev>^{commit}`: on Windows this runs
|
|
80
|
+
* through `cmd /c`, where `^` is the shell's own escape character and would
|
|
81
|
+
* silently eat the peel suffix, making a present commit look absent — the
|
|
82
|
+
* same trap `benchmark/core_v2/runners.ts`'s `requireCommit` avoids for the same
|
|
83
|
+
* reason. `cat-file -t` after a caret-free `rev-parse` sidesteps it. */
|
|
84
|
+
async function resolveCommit(clone, candidate, run) {
|
|
85
|
+
const rev = await run("git", ["rev-parse", "--verify", "-q", candidate], clone);
|
|
86
|
+
if (rev.code !== 0)
|
|
87
|
+
return undefined;
|
|
88
|
+
const sha = rev.stdout.trim();
|
|
89
|
+
const type = await run("git", ["cat-file", "-t", sha], clone);
|
|
90
|
+
if (type.code !== 0 || type.stdout.trim() !== "commit")
|
|
91
|
+
return undefined;
|
|
92
|
+
return sha;
|
|
93
|
+
}
|
|
94
|
+
/** Resolves `revision` — a branch name, a remote-tracking ref, or a commit SHA
|
|
95
|
+
* — to a commit SHA, fetching it directly if it is a SHA the clone does not
|
|
96
|
+
* have. A round's boundary commit is often reachable only by SHA once its
|
|
97
|
+
* branch has moved on or the PR was squash-merged, so no local or
|
|
98
|
+
* remote-tracking ref points at it any more. */
|
|
99
|
+
export async function ensureCommit(clone, revision, run) {
|
|
100
|
+
for (const candidate of [revision, `origin/${revision}`]) {
|
|
101
|
+
const sha = await resolveCommit(clone, candidate, run);
|
|
102
|
+
if (sha)
|
|
103
|
+
return sha;
|
|
104
|
+
}
|
|
105
|
+
log("checkout", `fetching ${revision.slice(0, 10)}`);
|
|
106
|
+
const fetched = await run("git", ["fetch", "origin", revision], clone);
|
|
107
|
+
if (fetched.code !== 0) {
|
|
108
|
+
throw new Error(`git fetch ${revision} failed: ${fetched.stderr.trim()}`);
|
|
109
|
+
}
|
|
110
|
+
const sha = await resolveCommit(clone, revision, run);
|
|
111
|
+
if (!sha) {
|
|
112
|
+
throw new Error(`fetched ${revision} but it still does not resolve to a commit`);
|
|
113
|
+
}
|
|
114
|
+
return sha;
|
|
115
|
+
}
|
|
116
|
+
export async function ensureWorktree(repo, pr, commit, run) {
|
|
117
|
+
return withCloneLock(repo, async () => {
|
|
118
|
+
const clone = await ensureCloneUnlocked(repo, run);
|
|
119
|
+
const sha = await ensureCommit(clone, commit, run);
|
|
120
|
+
const dir = worktreeDir(repo, pr);
|
|
121
|
+
if (await pathExists(`${dir}/.git`)) {
|
|
122
|
+
log("checkout", `reusing worktree ${dir}`);
|
|
123
|
+
const reset = await run("git", ["checkout", "-q", "--detach", sha], dir);
|
|
124
|
+
if (reset.code !== 0) {
|
|
125
|
+
throw new Error(`checkout ${sha} failed: ${reset.stderr.trim()}`);
|
|
126
|
+
}
|
|
127
|
+
return dir;
|
|
128
|
+
}
|
|
129
|
+
log("checkout", `creating worktree ${dir} at ${sha.slice(0, 10)}`);
|
|
130
|
+
const added = await run("git", ["worktree", "add", "-q", "--detach", dir, sha], clone);
|
|
131
|
+
if (added.code !== 0) {
|
|
132
|
+
throw new Error(`git worktree add failed: ${added.stderr.trim()}`);
|
|
133
|
+
}
|
|
134
|
+
return dir;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type Args = Record<string, unknown>;
|
|
2
|
+
export declare function toolError(message: string): string;
|
|
3
|
+
export declare function rejectUnknownKeys(args: Args, allowed: ReadonlySet<string>): string | null;
|
|
4
|
+
export declare function rejectFlagLike(value: string, label: string): string | null;
|
|
5
|
+
/** Repo-relative path only — no `..`, no absolute paths (plan E103). */
|
|
6
|
+
export declare function rejectEscapingPath(file: string): string | null;
|
|
7
|
+
export declare function guardToolArgs(args: Args, allowed: ReadonlySet<string>): string | null;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function toolError(message) {
|
|
2
|
+
return `Tool error: ${message}`;
|
|
3
|
+
}
|
|
4
|
+
export function rejectUnknownKeys(args, allowed) {
|
|
5
|
+
for (const key of Object.keys(args)) {
|
|
6
|
+
if (!allowed.has(key))
|
|
7
|
+
return toolError(`unknown key: ${key}`);
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
export function rejectFlagLike(value, label) {
|
|
12
|
+
if (value.startsWith("-")) {
|
|
13
|
+
return toolError(`${label} must not start with '-'`);
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/** Repo-relative path only — no `..`, no absolute paths (plan E103). */
|
|
18
|
+
export function rejectEscapingPath(file) {
|
|
19
|
+
const flag = rejectFlagLike(file, "file");
|
|
20
|
+
if (flag)
|
|
21
|
+
return flag;
|
|
22
|
+
const norm = file.replace(/\\/g, "/");
|
|
23
|
+
if (norm.startsWith("/") ||
|
|
24
|
+
norm.startsWith("//") ||
|
|
25
|
+
/^[A-Za-z]:\//.test(norm) ||
|
|
26
|
+
/^[A-Za-z]:$/.test(norm) ||
|
|
27
|
+
norm.split("/").includes("..")) {
|
|
28
|
+
return toolError("file must stay inside the repository");
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
export function guardToolArgs(args, allowed) {
|
|
33
|
+
return rejectUnknownKeys(args, allowed);
|
|
34
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ToolHandler } from "../ai/mermaid_loop.ts";
|
|
2
|
+
import { type Run } from "./checkout.ts";
|
|
3
|
+
import { type Presence } from "../tools/codegraph.ts";
|
|
4
|
+
import { type CodegraphRunner } from "../tools/codegraph_exec.ts";
|
|
5
|
+
export declare function ensureCodegraphIndex(binary: string, worktree: string, runner: CodegraphRunner, indexDir?: string): Promise<void>;
|
|
6
|
+
/** Uses `detect`, never `ensureCodegraph`: the latter can call `Deno.exit` on
|
|
7
|
+
* a declined or failed install, fine for a one-time `init`/`remake`/`serve`
|
|
8
|
+
* setup step but not for a single review that happens to want this tool. */
|
|
9
|
+
export declare function prepareCodegraphTools(repo: string, pr: number, commit: string, options?: {
|
|
10
|
+
run?: Run;
|
|
11
|
+
detect?: () => Promise<Presence>;
|
|
12
|
+
}): Promise<ToolHandler[]>;
|
|
13
|
+
/** Tool schemas for the remote bridge (runs are proxied to the CLI). */
|
|
14
|
+
export declare function codegraphToolHandlersForBridge(): ToolHandler[];
|
|
15
|
+
export declare function codegraphTools(binary: string, worktree: string, runner?: CodegraphRunner): ToolHandler[];
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { ensureWorktree, pathExists, runCommand, } from "./checkout.js";
|
|
2
|
+
import { detect } from "../tools/codegraph.js";
|
|
3
|
+
import { createCodegraphRunner, LOCAL_CODEGRAPH_DIR, runCodegraphTool, SERVER_CODEGRAPH_DIR, } from "../tools/codegraph_exec.js";
|
|
4
|
+
import { log } from "../util/log.js";
|
|
5
|
+
import { guardToolArgs, rejectEscapingPath, rejectFlagLike, } from "./codegraph_tool_args.js";
|
|
6
|
+
function str(args, key) {
|
|
7
|
+
const value = args[key];
|
|
8
|
+
return typeof value === "string" && value.trim() !== ""
|
|
9
|
+
? value.trim()
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
function num(args, key) {
|
|
13
|
+
const value = args[key];
|
|
14
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
15
|
+
? value
|
|
16
|
+
: undefined;
|
|
17
|
+
}
|
|
18
|
+
function strArray(args, key) {
|
|
19
|
+
const value = args[key];
|
|
20
|
+
return Array.isArray(value) ? value.map(String).filter(Boolean) : [];
|
|
21
|
+
}
|
|
22
|
+
export async function ensureCodegraphIndex(binary, worktree, runner, indexDir = SERVER_CODEGRAPH_DIR) {
|
|
23
|
+
const started = performance.now();
|
|
24
|
+
const fresh = !(await pathExists(`${worktree}/${indexDir}`));
|
|
25
|
+
const initCmd = indexDir === LOCAL_CODEGRAPH_DIR ? ["init", "-y", "."] : ["init", "."];
|
|
26
|
+
let result = await runner(binary, fresh ? initCmd : ["sync", "."], worktree);
|
|
27
|
+
if (!fresh &&
|
|
28
|
+
result.code !== 0 &&
|
|
29
|
+
/lock/i.test(result.stderr + result.stdout)) {
|
|
30
|
+
await runner(binary, ["unlock", "."], worktree);
|
|
31
|
+
result = await runner(binary, ["sync", "."], worktree);
|
|
32
|
+
}
|
|
33
|
+
if (result.code !== 0) {
|
|
34
|
+
result = await runner(binary, ["index", "."], worktree);
|
|
35
|
+
}
|
|
36
|
+
if (result.code !== 0) {
|
|
37
|
+
throw new Error(`codegraph ${fresh ? "init" : "sync"} failed: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
38
|
+
}
|
|
39
|
+
const indexed = result.stdout.match(/Indexed ([\d.]+) files/)?.[1] ??
|
|
40
|
+
result.stdout.match(/Synced (\d+) changed files/)?.[1] ??
|
|
41
|
+
"?";
|
|
42
|
+
log("codegraph", `${fresh ? "init" : "sync"} · ${indexed} files · ${((performance.now() - started) /
|
|
43
|
+
1000).toFixed(1)}s`);
|
|
44
|
+
}
|
|
45
|
+
/** Uses `detect`, never `ensureCodegraph`: the latter can call `Deno.exit` on
|
|
46
|
+
* a declined or failed install, fine for a one-time `init`/`remake`/`serve`
|
|
47
|
+
* setup step but not for a single review that happens to want this tool. */
|
|
48
|
+
export async function prepareCodegraphTools(repo, pr, commit, options = {}) {
|
|
49
|
+
const runner = options.run ?? runCommand;
|
|
50
|
+
const detectPresence = options.detect ?? detect;
|
|
51
|
+
try {
|
|
52
|
+
const presence = await detectPresence();
|
|
53
|
+
if (presence.state !== "ok") {
|
|
54
|
+
log("codegraph", `tools unavailable · codegraph is ${presence.state}`);
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const worktree = await ensureWorktree(repo, pr, commit, runner);
|
|
58
|
+
const cgRun = createCodegraphRunner(SERVER_CODEGRAPH_DIR);
|
|
59
|
+
await ensureCodegraphIndex(presence.path, worktree, cgRun);
|
|
60
|
+
return codegraphTools(presence.path, worktree, cgRun);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
log("codegraph", `tools unavailable · ${String(error)}`);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Tool schemas for the remote bridge (runs are proxied to the CLI). */
|
|
68
|
+
export function codegraphToolHandlersForBridge() {
|
|
69
|
+
return codegraphTools("codegraph", ".", createCodegraphRunner(SERVER_CODEGRAPH_DIR));
|
|
70
|
+
}
|
|
71
|
+
export function codegraphTools(binary, worktree, runner = createCodegraphRunner(SERVER_CODEGRAPH_DIR)) {
|
|
72
|
+
const run = (args) => runCodegraphTool(binary, args, worktree, runner);
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
name: "codegraph-query",
|
|
76
|
+
tool: {
|
|
77
|
+
type: "function",
|
|
78
|
+
function: {
|
|
79
|
+
name: "codegraph-query",
|
|
80
|
+
description: "Search for symbols (functions, classes, types, etc.) by name across the indexed repository.",
|
|
81
|
+
parameters: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
search: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "The symbol name or search term.",
|
|
87
|
+
},
|
|
88
|
+
kind: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Restrict to a node kind, e.g. function, class.",
|
|
91
|
+
},
|
|
92
|
+
limit: {
|
|
93
|
+
type: "number",
|
|
94
|
+
description: "Maximum results (default 10).",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ["search"],
|
|
98
|
+
additionalProperties: false,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
run: (args) => {
|
|
103
|
+
const a = args;
|
|
104
|
+
const keys = new Set(["search", "kind", "limit"]);
|
|
105
|
+
const guard = guardToolArgs(a, keys);
|
|
106
|
+
if (guard)
|
|
107
|
+
return Promise.resolve(guard);
|
|
108
|
+
const search = str(a, "search");
|
|
109
|
+
if (!search) {
|
|
110
|
+
return Promise.resolve("codegraph-query requires 'search'.");
|
|
111
|
+
}
|
|
112
|
+
const flag = rejectFlagLike(search, "search");
|
|
113
|
+
if (flag)
|
|
114
|
+
return Promise.resolve(flag);
|
|
115
|
+
const kind = str(a, "kind");
|
|
116
|
+
if (kind) {
|
|
117
|
+
const kindFlag = rejectFlagLike(kind, "kind");
|
|
118
|
+
if (kindFlag)
|
|
119
|
+
return Promise.resolve(kindFlag);
|
|
120
|
+
}
|
|
121
|
+
const limit = num(a, "limit");
|
|
122
|
+
return run([
|
|
123
|
+
"query",
|
|
124
|
+
search,
|
|
125
|
+
...(kind ? ["-k", kind] : []),
|
|
126
|
+
...(limit ? ["-l", String(limit)] : []),
|
|
127
|
+
]);
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "codegraph-node",
|
|
132
|
+
tool: {
|
|
133
|
+
type: "function",
|
|
134
|
+
function: {
|
|
135
|
+
name: "codegraph-node",
|
|
136
|
+
description: "One symbol's source plus its caller/callee trail, given its name. Or, with `file` set instead, that file's own symbol map and dependents.",
|
|
137
|
+
parameters: {
|
|
138
|
+
type: "object",
|
|
139
|
+
properties: {
|
|
140
|
+
name: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "A symbol name to look up.",
|
|
143
|
+
},
|
|
144
|
+
file: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "A file path — reads the file's symbol map instead of a single symbol.",
|
|
147
|
+
},
|
|
148
|
+
symbolsOnly: {
|
|
149
|
+
type: "boolean",
|
|
150
|
+
description: "With `file`: return only the symbol map, not source text.",
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
additionalProperties: false,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
run: (args) => {
|
|
158
|
+
const a = args;
|
|
159
|
+
const keys = new Set(["name", "file", "symbolsOnly"]);
|
|
160
|
+
const guard = guardToolArgs(a, keys);
|
|
161
|
+
if (guard)
|
|
162
|
+
return Promise.resolve(guard);
|
|
163
|
+
const name = str(a, "name");
|
|
164
|
+
const file = str(a, "file");
|
|
165
|
+
if (!name && !file) {
|
|
166
|
+
return Promise.resolve("codegraph-node requires 'name' or 'file'.");
|
|
167
|
+
}
|
|
168
|
+
if (name) {
|
|
169
|
+
const flag = rejectFlagLike(name, "name");
|
|
170
|
+
if (flag)
|
|
171
|
+
return Promise.resolve(flag);
|
|
172
|
+
}
|
|
173
|
+
if (file) {
|
|
174
|
+
const pathErr = rejectEscapingPath(file);
|
|
175
|
+
if (pathErr)
|
|
176
|
+
return Promise.resolve(pathErr);
|
|
177
|
+
}
|
|
178
|
+
return run([
|
|
179
|
+
"node",
|
|
180
|
+
...(name ? [name] : []),
|
|
181
|
+
...(file ? ["-f", file] : []),
|
|
182
|
+
...(a.symbolsOnly ? ["--symbols-only"] : []),
|
|
183
|
+
]);
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "codegraph-explore",
|
|
188
|
+
tool: {
|
|
189
|
+
type: "function",
|
|
190
|
+
function: {
|
|
191
|
+
name: "codegraph-explore",
|
|
192
|
+
description: "Explore an area of the codebase by a natural-language query: relevant symbols' source and call paths in one shot.",
|
|
193
|
+
parameters: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {
|
|
196
|
+
query: {
|
|
197
|
+
type: "string",
|
|
198
|
+
description: "What to explore, in plain words.",
|
|
199
|
+
},
|
|
200
|
+
maxFiles: {
|
|
201
|
+
type: "number",
|
|
202
|
+
description: "Maximum number of files to include source from.",
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
required: ["query"],
|
|
206
|
+
additionalProperties: false,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
run: (args) => {
|
|
211
|
+
const a = args;
|
|
212
|
+
const keys = new Set(["query", "maxFiles"]);
|
|
213
|
+
const guard = guardToolArgs(a, keys);
|
|
214
|
+
if (guard)
|
|
215
|
+
return Promise.resolve(guard);
|
|
216
|
+
const query = str(a, "query");
|
|
217
|
+
if (!query) {
|
|
218
|
+
return Promise.resolve("codegraph-explore requires 'query'.");
|
|
219
|
+
}
|
|
220
|
+
const flag = rejectFlagLike(query, "query");
|
|
221
|
+
if (flag)
|
|
222
|
+
return Promise.resolve(flag);
|
|
223
|
+
const parts = query.split(/\s+/).filter(Boolean);
|
|
224
|
+
for (const part of parts) {
|
|
225
|
+
const partFlag = rejectFlagLike(part, "query");
|
|
226
|
+
if (partFlag)
|
|
227
|
+
return Promise.resolve(partFlag);
|
|
228
|
+
}
|
|
229
|
+
const maxFiles = num(a, "maxFiles");
|
|
230
|
+
return run([
|
|
231
|
+
"explore",
|
|
232
|
+
...parts,
|
|
233
|
+
...(maxFiles ? ["--max-files", String(maxFiles)] : []),
|
|
234
|
+
]);
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "codegraph-callers",
|
|
239
|
+
tool: {
|
|
240
|
+
type: "function",
|
|
241
|
+
function: {
|
|
242
|
+
name: "codegraph-callers",
|
|
243
|
+
description: "Find every function or method that calls a specific symbol.",
|
|
244
|
+
parameters: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
symbol: { type: "string" },
|
|
248
|
+
limit: {
|
|
249
|
+
type: "number",
|
|
250
|
+
description: "Maximum results (default 20).",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
required: ["symbol"],
|
|
254
|
+
additionalProperties: false,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
run: (args) => {
|
|
259
|
+
const a = args;
|
|
260
|
+
const keys = new Set(["symbol", "limit"]);
|
|
261
|
+
const guard = guardToolArgs(a, keys);
|
|
262
|
+
if (guard)
|
|
263
|
+
return Promise.resolve(guard);
|
|
264
|
+
const symbol = str(a, "symbol");
|
|
265
|
+
if (!symbol) {
|
|
266
|
+
return Promise.resolve("codegraph-callers requires 'symbol'.");
|
|
267
|
+
}
|
|
268
|
+
const flag = rejectFlagLike(symbol, "symbol");
|
|
269
|
+
if (flag)
|
|
270
|
+
return Promise.resolve(flag);
|
|
271
|
+
const limit = num(a, "limit");
|
|
272
|
+
return run([
|
|
273
|
+
"callers",
|
|
274
|
+
symbol,
|
|
275
|
+
...(limit ? ["-l", String(limit)] : []),
|
|
276
|
+
]);
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "codegraph-callees",
|
|
281
|
+
tool: {
|
|
282
|
+
type: "function",
|
|
283
|
+
function: {
|
|
284
|
+
name: "codegraph-callees",
|
|
285
|
+
description: "Find every function or method that a specific symbol calls.",
|
|
286
|
+
parameters: {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
symbol: { type: "string" },
|
|
290
|
+
limit: {
|
|
291
|
+
type: "number",
|
|
292
|
+
description: "Maximum results (default 20).",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
required: ["symbol"],
|
|
296
|
+
additionalProperties: false,
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
run: (args) => {
|
|
301
|
+
const a = args;
|
|
302
|
+
const keys = new Set(["symbol", "limit"]);
|
|
303
|
+
const guard = guardToolArgs(a, keys);
|
|
304
|
+
if (guard)
|
|
305
|
+
return Promise.resolve(guard);
|
|
306
|
+
const symbol = str(a, "symbol");
|
|
307
|
+
if (!symbol) {
|
|
308
|
+
return Promise.resolve("codegraph-callees requires 'symbol'.");
|
|
309
|
+
}
|
|
310
|
+
const flag = rejectFlagLike(symbol, "symbol");
|
|
311
|
+
if (flag)
|
|
312
|
+
return Promise.resolve(flag);
|
|
313
|
+
const limit = num(a, "limit");
|
|
314
|
+
return run([
|
|
315
|
+
"callees",
|
|
316
|
+
symbol,
|
|
317
|
+
...(limit ? ["-l", String(limit)] : []),
|
|
318
|
+
]);
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "codegraph-impact",
|
|
323
|
+
tool: {
|
|
324
|
+
type: "function",
|
|
325
|
+
function: {
|
|
326
|
+
name: "codegraph-impact",
|
|
327
|
+
description: "Analyze what else in the codebase is affected by changing a specific symbol — its blast radius.",
|
|
328
|
+
parameters: {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
symbol: { type: "string" },
|
|
332
|
+
depth: {
|
|
333
|
+
type: "number",
|
|
334
|
+
description: "Traversal depth (default 2).",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
required: ["symbol"],
|
|
338
|
+
additionalProperties: false,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
run: (args) => {
|
|
343
|
+
const a = args;
|
|
344
|
+
const keys = new Set(["symbol", "depth"]);
|
|
345
|
+
const guard = guardToolArgs(a, keys);
|
|
346
|
+
if (guard)
|
|
347
|
+
return Promise.resolve(guard);
|
|
348
|
+
const symbol = str(a, "symbol");
|
|
349
|
+
if (!symbol) {
|
|
350
|
+
return Promise.resolve("codegraph-impact requires 'symbol'.");
|
|
351
|
+
}
|
|
352
|
+
const flag = rejectFlagLike(symbol, "symbol");
|
|
353
|
+
if (flag)
|
|
354
|
+
return Promise.resolve(flag);
|
|
355
|
+
const depth = num(a, "depth");
|
|
356
|
+
return run(["impact", symbol, ...(depth ? ["-d", String(depth)] : [])]);
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: "codegraph-affected",
|
|
361
|
+
tool: {
|
|
362
|
+
type: "function",
|
|
363
|
+
function: {
|
|
364
|
+
name: "codegraph-affected",
|
|
365
|
+
description: "Find test files affected by one or more changed source files — the direct way to check whether a change is covered by any test, instead of guessing from the diff.",
|
|
366
|
+
parameters: {
|
|
367
|
+
type: "object",
|
|
368
|
+
properties: {
|
|
369
|
+
files: {
|
|
370
|
+
type: "array",
|
|
371
|
+
items: { type: "string" },
|
|
372
|
+
description: "Changed file paths, exactly as shown in this pull request.",
|
|
373
|
+
},
|
|
374
|
+
depth: {
|
|
375
|
+
type: "number",
|
|
376
|
+
description: "Max dependency traversal depth (default 5).",
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
required: ["files"],
|
|
380
|
+
additionalProperties: false,
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
run: (args) => {
|
|
385
|
+
const a = args;
|
|
386
|
+
const keys = new Set(["files", "depth"]);
|
|
387
|
+
const guard = guardToolArgs(a, keys);
|
|
388
|
+
if (guard)
|
|
389
|
+
return Promise.resolve(guard);
|
|
390
|
+
const files = strArray(a, "files");
|
|
391
|
+
if (files.length === 0) {
|
|
392
|
+
return Promise.resolve("codegraph-affected requires a non-empty 'files' array.");
|
|
393
|
+
}
|
|
394
|
+
for (const file of files) {
|
|
395
|
+
const pathErr = rejectEscapingPath(file);
|
|
396
|
+
if (pathErr)
|
|
397
|
+
return Promise.resolve(pathErr);
|
|
398
|
+
}
|
|
399
|
+
const depth = num(a, "depth");
|
|
400
|
+
return run([
|
|
401
|
+
"affected",
|
|
402
|
+
...files,
|
|
403
|
+
...(depth ? ["-d", String(depth)] : []),
|
|
404
|
+
]);
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
];
|
|
408
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AiProvider, AiResponse, Json } from "../types.ts";
|
|
2
|
+
export declare const DIFF_SUMMARY_THRESHOLD = 500;
|
|
3
|
+
export declare function needsSummary(changes: number, patch: string): boolean;
|
|
4
|
+
export declare function summarizeDiff(path: string, patch: string, provider: AiProvider, usage?: (response: AiResponse) => Promise<void>): Promise<string>;
|
|
5
|
+
export declare const READ_FULL_DIFF_TOOL: Json;
|
|
6
|
+
export declare function readFullDiff(patchByPath: Map<string, string>, args: unknown): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { numberPatch } from "./hunks.js";
|
|
2
|
+
export const DIFF_SUMMARY_THRESHOLD = 500;
|
|
3
|
+
export function needsSummary(changes, patch) {
|
|
4
|
+
return patch !== "" && changes > DIFF_SUMMARY_THRESHOLD;
|
|
5
|
+
}
|
|
6
|
+
// No system prompt, low max output: a mechanical description, not reasoning.
|
|
7
|
+
export async function summarizeDiff(path, patch, provider, usage) {
|
|
8
|
+
const response = await provider.complete({
|
|
9
|
+
job: "summarize_large_diff",
|
|
10
|
+
prompt: `Summarize this diff for \`${path}\` in 3-5 sentences: what changed, the
|
|
11
|
+
mechanism, and precisely which branches, patterns, or call paths are affected.
|
|
12
|
+
Do not speculate beyond what the diff shows. Do not suggest fixes or judge
|
|
13
|
+
whether the change is correct — only describe it.
|
|
14
|
+
|
|
15
|
+
${patch}`,
|
|
16
|
+
maxTokens: 500,
|
|
17
|
+
});
|
|
18
|
+
if (usage)
|
|
19
|
+
await usage(response);
|
|
20
|
+
return response.text.trim();
|
|
21
|
+
}
|
|
22
|
+
export const READ_FULL_DIFF_TOOL = {
|
|
23
|
+
type: "function",
|
|
24
|
+
function: {
|
|
25
|
+
name: "read-full-diff",
|
|
26
|
+
description: "Read the complete, untruncated diff for one file in this pull request's own changes. Only files whose diff was summarized (over 500 changed lines) are available this way — everything else is already shown in full.",
|
|
27
|
+
parameters: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
path: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "The file path exactly as it appears in this prompt's DIFF or PULL REQUEST section.",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ["path"],
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
// The patch is already in memory from the compare API, so this is a plain
|
|
41
|
+
// lookup, not a network or git call.
|
|
42
|
+
export function readFullDiff(patchByPath, args) {
|
|
43
|
+
const path = String(args?.path ?? "");
|
|
44
|
+
const patch = patchByPath.get(path);
|
|
45
|
+
if (patch)
|
|
46
|
+
return numberPatch(patch);
|
|
47
|
+
return patchByPath.size === 0
|
|
48
|
+
? "No large files were summarized in this review; every file's diff is already shown in full above."
|
|
49
|
+
: `No summarized diff found for \`${path}\`. Summarized files: ${[
|
|
50
|
+
...patchByPath.keys(),
|
|
51
|
+
].join(", ")}`;
|
|
52
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Span = {
|
|
2
|
+
path: string;
|
|
3
|
+
from: number;
|
|
4
|
+
to: number;
|
|
5
|
+
};
|
|
6
|
+
export type ParsedFinding = Span & {
|
|
7
|
+
heading: string;
|
|
8
|
+
excerpt: string;
|
|
9
|
+
severity?: string;
|
|
10
|
+
blocking?: boolean;
|
|
11
|
+
symbol?: string;
|
|
12
|
+
summary?: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function parseFindings(markdown: string): ParsedFinding[];
|