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,141 @@
|
|
|
1
|
+
import { resolveDefaultBranchName } from "../git/default_branch.js";
|
|
2
|
+
import { normalizePath, } from "../review/revision.js";
|
|
3
|
+
import { parseNameStatusZ, parseNumstatZ, pathsMissingPatches, splitDiffPatches, } from "./git_parse.js";
|
|
4
|
+
import { revisionFileFromUntracked, } from "./git_untracked.js";
|
|
5
|
+
import { ReviewCliError } from "./git_ops.js";
|
|
6
|
+
const diffEnv = ["-c", "core.quotePath=false"];
|
|
7
|
+
const unifiedDiffArgs = [
|
|
8
|
+
"diff",
|
|
9
|
+
"--no-color",
|
|
10
|
+
"--no-ext-diff",
|
|
11
|
+
"--no-textconv",
|
|
12
|
+
"-M",
|
|
13
|
+
"--unified=3",
|
|
14
|
+
];
|
|
15
|
+
/** Single-file unified patch when bulk `git diff` did not map a path (plan §10.7). */
|
|
16
|
+
export async function perFileUnifiedPatch(cwd, base, path, run) {
|
|
17
|
+
const result = await run("git", [...diffEnv, ...unifiedDiffArgs, base, "--", path], cwd);
|
|
18
|
+
if (result.code !== 0 || !result.stdout.trim())
|
|
19
|
+
return "";
|
|
20
|
+
const map = splitDiffPatches(result.stdout);
|
|
21
|
+
return map.get(path) ?? map.values().next().value ?? "";
|
|
22
|
+
}
|
|
23
|
+
export async function mergeBase(cwd, baseRef, remote, run) {
|
|
24
|
+
const result = await run("git", [...diffEnv, "merge-base", baseRef, "HEAD"], cwd);
|
|
25
|
+
if (result.code === 0 && result.stdout.trim()) {
|
|
26
|
+
return result.stdout.trim();
|
|
27
|
+
}
|
|
28
|
+
const shallow = await run("git", ["rev-parse", "--is-shallow-repository"], cwd);
|
|
29
|
+
if (shallow.stdout.trim() === "true") {
|
|
30
|
+
await run("git", ["fetch", "--quiet", "--unshallow", remote], cwd);
|
|
31
|
+
const retry = await run("git", [...diffEnv, "merge-base", baseRef, "HEAD"], cwd);
|
|
32
|
+
if (retry.code === 0 && retry.stdout.trim())
|
|
33
|
+
return retry.stdout.trim();
|
|
34
|
+
}
|
|
35
|
+
throw new ReviewCliError("no_merge_base", "Could not find a merge base between HEAD and the target branch.");
|
|
36
|
+
}
|
|
37
|
+
export async function resolveBaseRef(cwd, remote, toBranch, run) {
|
|
38
|
+
let branch = toBranch;
|
|
39
|
+
if (!branch) {
|
|
40
|
+
branch = await resolveDefaultBranchName(cwd, remote, run);
|
|
41
|
+
}
|
|
42
|
+
if (!branch) {
|
|
43
|
+
throw new ReviewCliError("base_not_found", "Could not determine the default branch.", "--to-branch=<name>");
|
|
44
|
+
}
|
|
45
|
+
const ref = `refs/remotes/${remote}/${branch}`;
|
|
46
|
+
const verify = await run("git", ["rev-parse", "--verify", ref], cwd);
|
|
47
|
+
if (verify.code !== 0) {
|
|
48
|
+
await run("git", ["fetch", "--quiet", remote, branch], cwd);
|
|
49
|
+
}
|
|
50
|
+
const again = await run("git", ["rev-parse", "--verify", ref], cwd);
|
|
51
|
+
if (again.code !== 0) {
|
|
52
|
+
throw new ReviewCliError("base_not_found", `Could not resolve ${remote}/${branch}.`, "--to-branch=<name>");
|
|
53
|
+
}
|
|
54
|
+
return { ref, label: `${remote}/${branch}` };
|
|
55
|
+
}
|
|
56
|
+
export async function buildLocalRevision(cwd, mergeBaseSha, baseLabel, run) {
|
|
57
|
+
const base = mergeBaseSha;
|
|
58
|
+
const nameStatus = await run("git", [
|
|
59
|
+
...diffEnv,
|
|
60
|
+
"diff",
|
|
61
|
+
"--no-color",
|
|
62
|
+
"--no-ext-diff",
|
|
63
|
+
"--no-textconv",
|
|
64
|
+
"-M",
|
|
65
|
+
"--name-status",
|
|
66
|
+
"-z",
|
|
67
|
+
base,
|
|
68
|
+
], cwd);
|
|
69
|
+
const numstat = await run("git", [
|
|
70
|
+
...diffEnv,
|
|
71
|
+
"diff",
|
|
72
|
+
"--no-color",
|
|
73
|
+
"--no-ext-diff",
|
|
74
|
+
"--no-textconv",
|
|
75
|
+
"-M",
|
|
76
|
+
"--numstat",
|
|
77
|
+
"-z",
|
|
78
|
+
base,
|
|
79
|
+
], cwd);
|
|
80
|
+
const unified = await run("git", [...diffEnv, ...unifiedDiffArgs, base], cwd);
|
|
81
|
+
if (nameStatus.code !== 0) {
|
|
82
|
+
throw new ReviewCliError("internal", "git diff failed.");
|
|
83
|
+
}
|
|
84
|
+
const statuses = parseNameStatusZ(nameStatus.stdout);
|
|
85
|
+
const stats = parseNumstatZ(numstat.stdout);
|
|
86
|
+
const patches = unified.code === 0
|
|
87
|
+
? splitDiffPatches(unified.stdout)
|
|
88
|
+
: new Map();
|
|
89
|
+
const needsPerFile = new Set(unified.code !== 0
|
|
90
|
+
? statuses.filter((e) => e.status !== "removed").map((e) => e.path)
|
|
91
|
+
: pathsMissingPatches(statuses, patches));
|
|
92
|
+
const files = [];
|
|
93
|
+
for (const entry of statuses) {
|
|
94
|
+
const stat = stats.get(entry.path);
|
|
95
|
+
const file = {
|
|
96
|
+
path: normalizePath(entry.path),
|
|
97
|
+
previousPath: entry.previousPath
|
|
98
|
+
? normalizePath(entry.previousPath)
|
|
99
|
+
: null,
|
|
100
|
+
status: entry.status,
|
|
101
|
+
binary: stat?.binary ?? false,
|
|
102
|
+
additions: stat?.additions ?? 0,
|
|
103
|
+
deletions: stat?.deletions ?? 0,
|
|
104
|
+
patch: patches.get(entry.path) ?? "",
|
|
105
|
+
};
|
|
106
|
+
if (needsPerFile.has(entry.path) &&
|
|
107
|
+
file.status !== "removed" &&
|
|
108
|
+
!file.binary) {
|
|
109
|
+
file.patch = await perFileUnifiedPatch(cwd, base, entry.path, run);
|
|
110
|
+
}
|
|
111
|
+
files.push(file);
|
|
112
|
+
}
|
|
113
|
+
const warnings = [];
|
|
114
|
+
const tracked = new Set(files.map((f) => f.path));
|
|
115
|
+
const untracked = await run("git", [...diffEnv, "ls-files", "--others", "--exclude-standard", "-z"], cwd);
|
|
116
|
+
if (untracked.code === 0 && untracked.stdout) {
|
|
117
|
+
for (const rel of untracked.stdout.split("\0").filter(Boolean)) {
|
|
118
|
+
const path = normalizePath(rel);
|
|
119
|
+
if (tracked.has(path))
|
|
120
|
+
continue;
|
|
121
|
+
tracked.add(path);
|
|
122
|
+
const result = await revisionFileFromUntracked(cwd, rel);
|
|
123
|
+
if ("warning" in result) {
|
|
124
|
+
warnings.push(result.warning);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
files.push(result.file);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
131
|
+
return {
|
|
132
|
+
revision: {
|
|
133
|
+
files,
|
|
134
|
+
title: "",
|
|
135
|
+
description: "",
|
|
136
|
+
baseLabel,
|
|
137
|
+
producer: "local",
|
|
138
|
+
},
|
|
139
|
+
warnings,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type RevisionFile } from "../review/revision.ts";
|
|
2
|
+
export type UntrackedWarning = {
|
|
3
|
+
code: "untracked_unreadable";
|
|
4
|
+
message: string;
|
|
5
|
+
};
|
|
6
|
+
/** Build patch for a new untracked file (plan §10.8). */
|
|
7
|
+
export declare function patchFromNewFileContent(content: Uint8Array): {
|
|
8
|
+
patch: string;
|
|
9
|
+
additions: number;
|
|
10
|
+
binary: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function patchFromSymlinkTarget(target: string): {
|
|
13
|
+
patch: string;
|
|
14
|
+
additions: number;
|
|
15
|
+
binary: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare function revisionFileFromUntracked(cwd: string, relPath: string): Promise<{
|
|
18
|
+
file: RevisionFile;
|
|
19
|
+
} | {
|
|
20
|
+
warning: UntrackedWarning;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { normalizePath } from "../review/revision.js";
|
|
3
|
+
import { lstat, readFile, readLink } from "../util/runtime.js";
|
|
4
|
+
const NUL_SCAN_BYTES = 8000;
|
|
5
|
+
/** Build patch for a new untracked file (plan §10.8). */
|
|
6
|
+
export function patchFromNewFileContent(content) {
|
|
7
|
+
const scan = content.subarray(0, Math.min(content.length, NUL_SCAN_BYTES));
|
|
8
|
+
if (scan.includes(0)) {
|
|
9
|
+
return { patch: "", additions: 0, binary: true };
|
|
10
|
+
}
|
|
11
|
+
let text;
|
|
12
|
+
try {
|
|
13
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return { patch: "", additions: 0, binary: true };
|
|
17
|
+
}
|
|
18
|
+
if (text.length === 0) {
|
|
19
|
+
return { patch: "", additions: 0, binary: false };
|
|
20
|
+
}
|
|
21
|
+
const endsWithNewline = text.endsWith("\n");
|
|
22
|
+
const lines = text.split("\n");
|
|
23
|
+
if (endsWithNewline && lines.at(-1) === "")
|
|
24
|
+
lines.pop();
|
|
25
|
+
const n = lines.length;
|
|
26
|
+
const body = lines.map((line) => `+${line}`).join("\n");
|
|
27
|
+
const header = `@@ -0,0 +1,${n} @@`;
|
|
28
|
+
const patch = endsWithNewline
|
|
29
|
+
? `${header}\n${body}\n`
|
|
30
|
+
: `${header}\n${body}\n\\n`;
|
|
31
|
+
return { patch, additions: n, binary: false };
|
|
32
|
+
}
|
|
33
|
+
export function patchFromSymlinkTarget(target) {
|
|
34
|
+
const patch = `@@ -0,0 +1 @@\n+${target}\n`;
|
|
35
|
+
return { patch, additions: 1, binary: false };
|
|
36
|
+
}
|
|
37
|
+
export async function revisionFileFromUntracked(cwd, relPath) {
|
|
38
|
+
const path = normalizePath(relPath);
|
|
39
|
+
const abs = join(cwd, relPath);
|
|
40
|
+
try {
|
|
41
|
+
const info = await lstat(abs);
|
|
42
|
+
if (info.isSymbolicLink()) {
|
|
43
|
+
const target = await readLink(abs);
|
|
44
|
+
const { patch, additions, binary } = patchFromSymlinkTarget(target);
|
|
45
|
+
return {
|
|
46
|
+
file: {
|
|
47
|
+
path,
|
|
48
|
+
previousPath: null,
|
|
49
|
+
status: "added",
|
|
50
|
+
binary,
|
|
51
|
+
additions,
|
|
52
|
+
deletions: 0,
|
|
53
|
+
patch,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const content = await readFile(abs);
|
|
58
|
+
const { patch, additions, binary } = patchFromNewFileContent(content);
|
|
59
|
+
return {
|
|
60
|
+
file: {
|
|
61
|
+
path,
|
|
62
|
+
previousPath: null,
|
|
63
|
+
status: "added",
|
|
64
|
+
binary,
|
|
65
|
+
additions,
|
|
66
|
+
deletions: 0,
|
|
67
|
+
patch,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return {
|
|
73
|
+
warning: {
|
|
74
|
+
code: "untracked_unreadable",
|
|
75
|
+
message: `Could not read untracked file: ${path}`,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { parseArgs } from "../cli/args.js";
|
|
2
|
+
import { filterReviewConfigArgs, } from "../cli/review_args.js";
|
|
3
|
+
import { buildLocalReviewJson, formatHumanLocalReview, resolvedFromFirstReview, reviewExitCodeFromResolved, } from "../cli/review_result.js";
|
|
4
|
+
import { emptyAiMetrics, recordAiCost, runInitOrRemake, } from "../services/setup.js";
|
|
5
|
+
import { aiFor } from "../services/review.js";
|
|
6
|
+
import { loadGuides } from "../review/guides.js";
|
|
7
|
+
import { buildCarryPromptSection, classifyCarryItems, incrementalDiffPaths, parsePreviousVerdicts, resolveCarryOutcomes, } from "../review/carry_over.js";
|
|
8
|
+
import { revisionHash } from "../review/revision.js";
|
|
9
|
+
import { parseFindings } from "../pr/findings.js";
|
|
10
|
+
import { reviewWorkspaceRevision } from "../pr/reviewer.js";
|
|
11
|
+
import { runCommand } from "../pr/checkout.js";
|
|
12
|
+
import { log, startHeartbeat, timed, withCliLogsToStderr, } from "../util/log.js";
|
|
13
|
+
import { setCliInteractive } from "../cli/args.js";
|
|
14
|
+
import { prepareLocalCodegraph } from "./codegraph_prepare.js";
|
|
15
|
+
import { acquireLocalReviewLock } from "./review_lock.js";
|
|
16
|
+
import { assertGitQuiet, currentBranch, detectRemoteRepo, gitRoot, headSha, ReviewCliError, } from "./git_ops.js";
|
|
17
|
+
import { buildLocalRevision, mergeBase, resolveBaseRef, } from "./git_revision.js";
|
|
18
|
+
import { clearLocalCarry, localSubjectId, saveLocalCarry, tryLoadLocalCarry, } from "./carry_over_store.js";
|
|
19
|
+
function storedFindings(resolved) {
|
|
20
|
+
return resolved
|
|
21
|
+
.filter((row) => row.state === "new" || row.state === "open")
|
|
22
|
+
.map((row) => ({
|
|
23
|
+
id: row.id,
|
|
24
|
+
path: row.path,
|
|
25
|
+
lineFrom: row.lineFrom,
|
|
26
|
+
lineTo: row.lineTo,
|
|
27
|
+
title: row.title,
|
|
28
|
+
bodyMd: row.bodyMd,
|
|
29
|
+
anchorText: row.anchorText,
|
|
30
|
+
severity: row.severity,
|
|
31
|
+
firstSeenReviewId: row.firstSeenReviewId,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
function fail(error, json) {
|
|
35
|
+
if (json) {
|
|
36
|
+
console.log(JSON.stringify({
|
|
37
|
+
schemaVersion: 1,
|
|
38
|
+
ok: false,
|
|
39
|
+
error: { code: error.code, message: error.message, hint: error.hint },
|
|
40
|
+
exitCode: error.exitCode,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.error(error.message);
|
|
45
|
+
if (error.hint)
|
|
46
|
+
console.error(`Hint: ${error.hint}`);
|
|
47
|
+
}
|
|
48
|
+
process.exit(error.exitCode);
|
|
49
|
+
}
|
|
50
|
+
function installInterruptCleanup(releaseSync, json) {
|
|
51
|
+
const onSignal = () => {
|
|
52
|
+
releaseSync()?.();
|
|
53
|
+
if (json) {
|
|
54
|
+
console.log(JSON.stringify({
|
|
55
|
+
schemaVersion: 1,
|
|
56
|
+
ok: false,
|
|
57
|
+
error: {
|
|
58
|
+
code: "aborted",
|
|
59
|
+
message: "Review canceled.",
|
|
60
|
+
},
|
|
61
|
+
exitCode: 3,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
process.exit(3);
|
|
65
|
+
};
|
|
66
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
67
|
+
try {
|
|
68
|
+
process.on(signal, onSignal);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// unavailable on some platforms
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return () => {
|
|
75
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
76
|
+
try {
|
|
77
|
+
process.removeListener(signal, onSignal);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// ignore
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export async function runLocalReview(cli) {
|
|
86
|
+
const json = cli.json;
|
|
87
|
+
setCliInteractive(!json);
|
|
88
|
+
let lock = null;
|
|
89
|
+
const clearInterrupt = installInterruptCleanup(() => lock?.releaseSync ?? null, json);
|
|
90
|
+
await withCliLogsToStderr(async () => {
|
|
91
|
+
try {
|
|
92
|
+
const cwd = process.cwd();
|
|
93
|
+
const root = await gitRoot(cwd);
|
|
94
|
+
await assertGitQuiet(root);
|
|
95
|
+
const repo = await detectRemoteRepo(root, cli.repoOverride);
|
|
96
|
+
const branch = await currentBranch(root, cli.branch);
|
|
97
|
+
const options = await parseArgs([
|
|
98
|
+
"review",
|
|
99
|
+
repo,
|
|
100
|
+
...filterReviewConfigArgs(cli.rawArgs),
|
|
101
|
+
]);
|
|
102
|
+
options.useCodegraph = cli.disableCodegraph ? false : true;
|
|
103
|
+
const guides = await loadGuides(repo);
|
|
104
|
+
if (!guides.shortGuide && !guides.skill) {
|
|
105
|
+
throw new ReviewCliError("not_initialized", `Review guides are missing for ${repo}.`, `co-maintainer init ${repo}`);
|
|
106
|
+
}
|
|
107
|
+
if (cli.remakeBeforeReview) {
|
|
108
|
+
if (options.auth !== "gh") {
|
|
109
|
+
throw new ReviewCliError("usage", "Remake before review requires GitHub CLI authentication (`--auth=gh`).");
|
|
110
|
+
}
|
|
111
|
+
await runInitOrRemake({ ...options, command: "remake" });
|
|
112
|
+
}
|
|
113
|
+
const remotes = await runCommand("git", ["remote"], root);
|
|
114
|
+
const remoteName = ["upstream", "origin"].find((n) => remotes.stdout
|
|
115
|
+
.split("\n")
|
|
116
|
+
.map((l) => l.trim())
|
|
117
|
+
.includes(n)) ??
|
|
118
|
+
remotes.stdout
|
|
119
|
+
.split("\n")
|
|
120
|
+
.map((l) => l.trim())
|
|
121
|
+
.filter(Boolean)[0];
|
|
122
|
+
const base = await resolveBaseRef(root, remoteName, cli.toBranch, runCommand);
|
|
123
|
+
const baseSha = await mergeBase(root, base.ref, remoteName, runCommand);
|
|
124
|
+
const built = await buildLocalRevision(root, baseSha, base.label, runCommand);
|
|
125
|
+
let revision = built.revision;
|
|
126
|
+
const warnings = built.warnings.map((w) => ({
|
|
127
|
+
code: w.code,
|
|
128
|
+
message: w.message,
|
|
129
|
+
}));
|
|
130
|
+
if (revision.files.length === 0) {
|
|
131
|
+
if (json) {
|
|
132
|
+
console.log(JSON.stringify({
|
|
133
|
+
schemaVersion: 1,
|
|
134
|
+
ok: true,
|
|
135
|
+
mode: "local",
|
|
136
|
+
message: "No changes to review.",
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.log("No changes to review.");
|
|
141
|
+
}
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
const subjectId = localSubjectId(repo, root, branch);
|
|
145
|
+
if (cli.fresh)
|
|
146
|
+
await clearLocalCarry(repo, root, branch);
|
|
147
|
+
const carryLoad = await tryLoadLocalCarry(repo, root, branch);
|
|
148
|
+
if (carryLoad.unavailable) {
|
|
149
|
+
warnings.push({
|
|
150
|
+
code: "carry_over_unavailable",
|
|
151
|
+
message: "Could not read the local carry-over cache; continuing without prior findings.",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const previous = carryLoad.data;
|
|
155
|
+
let carryPrevious = null;
|
|
156
|
+
let carryItems = [];
|
|
157
|
+
const extras = {};
|
|
158
|
+
if (previous) {
|
|
159
|
+
carryPrevious = {
|
|
160
|
+
files: previous.files,
|
|
161
|
+
visiblePaths: new Set(previous.visiblePaths),
|
|
162
|
+
findings: previous.findings,
|
|
163
|
+
guideBuiltAt: previous.guideBuiltAt,
|
|
164
|
+
};
|
|
165
|
+
const { unchanged } = incrementalDiffPaths(revision, previous.files);
|
|
166
|
+
extras.unchangedPaths = unchanged;
|
|
167
|
+
carryItems = classifyCarryItems(carryPrevious, revision, carryPrevious.visiblePaths, guides.guideBuiltAt);
|
|
168
|
+
extras.carryPrompt = buildCarryPromptSection(carryItems, revision);
|
|
169
|
+
}
|
|
170
|
+
const sha = await headSha(root);
|
|
171
|
+
const revisionHashBefore = await revisionHash(revision);
|
|
172
|
+
lock = await acquireLocalReviewLock(root);
|
|
173
|
+
const codegraphPrep = await prepareLocalCodegraph({
|
|
174
|
+
gitRoot: root,
|
|
175
|
+
enabled: options.useCodegraph === true,
|
|
176
|
+
allowInstall: cli.allowToolInstall,
|
|
177
|
+
interactive: !json,
|
|
178
|
+
});
|
|
179
|
+
extras.prepareCodegraphTools = () => Promise.resolve(codegraphPrep.tools);
|
|
180
|
+
const stopHeartbeat = startHeartbeat("reviewing local changes");
|
|
181
|
+
const started = performance.now();
|
|
182
|
+
const aiMetrics = emptyAiMetrics();
|
|
183
|
+
let response;
|
|
184
|
+
try {
|
|
185
|
+
response = await timed("local review AI", options.logTime, () => reviewWorkspaceRevision(revision, options, sha, async (usage) => {
|
|
186
|
+
aiMetrics.calls++;
|
|
187
|
+
aiMetrics.tokensIn += usage.tokensIn;
|
|
188
|
+
aiMetrics.tokensOut += usage.tokensOut;
|
|
189
|
+
if (usage.cost === undefined)
|
|
190
|
+
aiMetrics.costKnown = false;
|
|
191
|
+
else
|
|
192
|
+
aiMetrics.cost += usage.cost;
|
|
193
|
+
await recordAiCost(repo, "review_local", usage);
|
|
194
|
+
}, aiFor(options), (message) => log("review", message), extras));
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
await lock?.release();
|
|
198
|
+
lock = null;
|
|
199
|
+
}
|
|
200
|
+
stopHeartbeat();
|
|
201
|
+
const codegraphState = codegraphPrep.state;
|
|
202
|
+
const visiblePaths = new Set(response.visiblePaths);
|
|
203
|
+
if (carryPrevious) {
|
|
204
|
+
carryItems = classifyCarryItems(carryPrevious, revision, visiblePaths, response.guideBuiltAt);
|
|
205
|
+
}
|
|
206
|
+
const parsed = parseFindings(response.text);
|
|
207
|
+
const filesByPath = new Map(revision.files.map((f) => [f.path, f]));
|
|
208
|
+
const allResolved = carryPrevious
|
|
209
|
+
? resolveCarryOutcomes(carryItems, revision, visiblePaths, parsePreviousVerdicts(response.text), parsed, response.guideBuiltAt, carryPrevious)
|
|
210
|
+
: resolvedFromFirstReview(parsed, filesByPath);
|
|
211
|
+
const findingsToStore = storedFindings(allResolved);
|
|
212
|
+
const afterBuilt = await buildLocalRevision(root, baseSha, base.label, runCommand);
|
|
213
|
+
const revisionHashAfter = await revisionHash(afterBuilt.revision);
|
|
214
|
+
if (revisionHashAfter !== revisionHashBefore) {
|
|
215
|
+
warnings.push({
|
|
216
|
+
code: "working_tree_changed",
|
|
217
|
+
message: "Files changed while the review was running. Results reflect the state at submit time.",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
await saveLocalCarry({
|
|
222
|
+
subjectId,
|
|
223
|
+
files: revision.files,
|
|
224
|
+
visiblePaths: [...visiblePaths],
|
|
225
|
+
findings: findingsToStore,
|
|
226
|
+
guideBuiltAt: response.guideBuiltAt,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
warnings.push({
|
|
231
|
+
code: "carry_over_unavailable",
|
|
232
|
+
message: "Could not save local carry-over state for the next review.",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const header = `co-maintainer review · ${repo} · ${branch} → ${base.label}`;
|
|
236
|
+
const durationMs = Math.round(performance.now() - started);
|
|
237
|
+
const usage = {
|
|
238
|
+
tokensIn: aiMetrics.tokensIn,
|
|
239
|
+
tokensOut: aiMetrics.tokensOut,
|
|
240
|
+
costUsd: aiMetrics.costKnown ? aiMetrics.cost : null,
|
|
241
|
+
};
|
|
242
|
+
if (json) {
|
|
243
|
+
console.log(buildLocalReviewJson({
|
|
244
|
+
repo,
|
|
245
|
+
branch,
|
|
246
|
+
baseLabel: base.label,
|
|
247
|
+
revision,
|
|
248
|
+
guideBuiltAt: response.guideBuiltAt,
|
|
249
|
+
codegraphState,
|
|
250
|
+
codegraphReason: codegraphPrep.reason,
|
|
251
|
+
findings: allResolved,
|
|
252
|
+
warnings,
|
|
253
|
+
usage,
|
|
254
|
+
durationMs,
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
console.log("\n" +
|
|
259
|
+
formatHumanLocalReview(header, revision, response.guideBuiltAt, codegraphState, allResolved, warnings) +
|
|
260
|
+
"\n");
|
|
261
|
+
}
|
|
262
|
+
process.exit(reviewExitCodeFromResolved(allResolved));
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (error instanceof ReviewCliError)
|
|
266
|
+
fail(error, json);
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
clearInterrupt();
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type LocalReviewLock = {
|
|
2
|
+
release: () => Promise<void>;
|
|
3
|
+
/** For signal handlers — must not await before exit. */
|
|
4
|
+
releaseSync: () => void;
|
|
5
|
+
};
|
|
6
|
+
/** One local review per repo root (plan §13.3). */
|
|
7
|
+
export declare function acquireLocalReviewLock(gitRoot: string): Promise<LocalReviewLock>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getCacheDir } from "../config.js";
|
|
2
|
+
import { ReviewCliError } from "./git_ops.js";
|
|
3
|
+
import { isNotFound, isProcessAlive, mkdir, readTextFile, remove, removeSync, writeTextFile, } from "../util/runtime.js";
|
|
4
|
+
async function lockPath(gitRoot) {
|
|
5
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(gitRoot));
|
|
6
|
+
const hex = [...new Uint8Array(digest)]
|
|
7
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
8
|
+
.join("")
|
|
9
|
+
.slice(0, 16);
|
|
10
|
+
const dir = `${getCacheDir()}/co-maintainer/locks`;
|
|
11
|
+
await mkdir(dir, { recursive: true });
|
|
12
|
+
return `${dir}/${hex}.lock`;
|
|
13
|
+
}
|
|
14
|
+
/** One local review per repo root (plan §13.3). */
|
|
15
|
+
export async function acquireLocalReviewLock(gitRoot) {
|
|
16
|
+
const path = await lockPath(gitRoot);
|
|
17
|
+
let existingPid;
|
|
18
|
+
try {
|
|
19
|
+
existingPid = Number((await readTextFile(path)).trim());
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!isNotFound(error))
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
if (existingPid !== undefined &&
|
|
26
|
+
Number.isInteger(existingPid) &&
|
|
27
|
+
isProcessAlive(existingPid)) {
|
|
28
|
+
throw new ReviewCliError("usage", `Another co-maintainer review is running in this repository (PID ${existingPid}).`);
|
|
29
|
+
}
|
|
30
|
+
await writeTextFile(path, String(process.pid));
|
|
31
|
+
const releaseSync = () => {
|
|
32
|
+
try {
|
|
33
|
+
removeSync(path);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// already removed
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
release: async () => {
|
|
41
|
+
await remove(path).catch(() => { });
|
|
42
|
+
},
|
|
43
|
+
releaseSync,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare function withCloneLock<T>(repo: string, fn: () => Promise<T>): Promise<T>;
|
|
2
|
+
/** Shared git plumbing for anything that needs a real checkout of a
|
|
3
|
+
* repository — the codegraph map and the upstream/own-work scope both need
|
|
4
|
+
* a clone and a way to resolve a commit that is not necessarily reachable
|
|
5
|
+
* from any branch (a squash-merged or rebased-away PR head). One
|
|
6
|
+
* implementation, so the two never manage the same on-disk clone in two
|
|
7
|
+
* slightly different ways. */
|
|
8
|
+
export type CommandResult = {
|
|
9
|
+
code: number;
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
};
|
|
13
|
+
export type Run = (command: string, args: string[], cwd?: string) => Promise<CommandResult>;
|
|
14
|
+
export declare function runCommand(command: string, args: string[], cwd?: string): Promise<CommandResult>;
|
|
15
|
+
export declare function pathExists(path: string): Promise<boolean>;
|
|
16
|
+
/** Like `ensureClone` but assumes the caller already holds `withCloneLock`. */
|
|
17
|
+
export declare function ensureCloneUnlocked(repo: string, run: Run): Promise<string>;
|
|
18
|
+
export declare function ensureClone(repo: string, run: Run): Promise<string>;
|
|
19
|
+
/** Resolves `revision` — a branch name, a remote-tracking ref, or a commit SHA
|
|
20
|
+
* — to a commit SHA, fetching it directly if it is a SHA the clone does not
|
|
21
|
+
* have. A round's boundary commit is often reachable only by SHA once its
|
|
22
|
+
* branch has moved on or the PR was squash-merged, so no local or
|
|
23
|
+
* remote-tracking ref points at it any more. */
|
|
24
|
+
export declare function ensureCommit(clone: string, revision: string, run: Run): Promise<string>;
|
|
25
|
+
export declare function ensureWorktree(repo: string, pr: number, commit: string, run: Run): Promise<string>;
|