@basou/core 0.35.1 → 0.37.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/dist/index.d.ts +145 -1
- package/dist/index.js +593 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/event.schema.json +21 -0
- package/schemas/session-import.schema.json +21 -0
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/adapters/command-lookup.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
async function isOnPath(command) {
|
|
4
|
-
return new Promise((
|
|
4
|
+
return new Promise((resolve4) => {
|
|
5
5
|
const child = spawn("which", [command], { stdio: "ignore" });
|
|
6
|
-
child.on("error", () =>
|
|
7
|
-
child.on("exit", (code) =>
|
|
6
|
+
child.on("error", () => resolve4(false));
|
|
7
|
+
child.on("exit", (code) => resolve4(code === 0));
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -1534,6 +1534,9 @@ var ReviewRecordedEventSchema = BaseEventSchema.extend({
|
|
|
1534
1534
|
type: z3.literal("review_recorded"),
|
|
1535
1535
|
reviewer: z3.string().min(1),
|
|
1536
1536
|
target: z3.string().min(1),
|
|
1537
|
+
repos: z3.array(z3.string().min(1)).optional(),
|
|
1538
|
+
repos_resolved: z3.array(z3.string().min(1)).optional(),
|
|
1539
|
+
commits: z3.array(z3.string().min(1)).optional(),
|
|
1537
1540
|
verdict: z3.enum(["pass", "needs-attention", "fail"]).optional(),
|
|
1538
1541
|
findings: z3.array(ReviewFindingSchema).optional(),
|
|
1539
1542
|
blocked: z3.array(ReviewBlockedSchema).optional()
|
|
@@ -5548,13 +5551,13 @@ async function realpathBestEffort(absPath) {
|
|
|
5548
5551
|
}
|
|
5549
5552
|
return normalize(absPath);
|
|
5550
5553
|
}
|
|
5551
|
-
function expandTilde(p,
|
|
5552
|
-
if (p === "~") return
|
|
5553
|
-
if (p.startsWith("~/")) return join14(
|
|
5554
|
+
function expandTilde(p, homedir5) {
|
|
5555
|
+
if (p === "~") return homedir5;
|
|
5556
|
+
if (p.startsWith("~/")) return join14(homedir5, p.slice(2));
|
|
5554
5557
|
return p;
|
|
5555
5558
|
}
|
|
5556
|
-
function toAbsolute(p, workingDirAbs,
|
|
5557
|
-
const expanded = expandTilde(p,
|
|
5559
|
+
function toAbsolute(p, workingDirAbs, homedir5) {
|
|
5560
|
+
const expanded = expandTilde(p, homedir5);
|
|
5558
5561
|
if (isAbsolute(expanded)) return normalize(expanded);
|
|
5559
5562
|
return normalize(resolve2(workingDirAbs, expanded));
|
|
5560
5563
|
}
|
|
@@ -5567,18 +5570,18 @@ async function classifyFilesBySourceRoot(input) {
|
|
|
5567
5570
|
const inRoot = [];
|
|
5568
5571
|
const outOfRoot = [];
|
|
5569
5572
|
if (input.files.length === 0) return { inRoot, outOfRoot };
|
|
5570
|
-
const
|
|
5571
|
-
const workingDirAbs = toAbsolute(input.workingDirectory,
|
|
5573
|
+
const homedir5 = input.homedir ?? osHomedir();
|
|
5574
|
+
const workingDirAbs = toAbsolute(input.workingDirectory, homedir5, homedir5);
|
|
5572
5575
|
const declared = input.sourceRoots && input.sourceRoots.length > 0 ? [...input.sourceRoots] : ["."];
|
|
5573
5576
|
const rootsAbs = [];
|
|
5574
5577
|
for (const r of declared) {
|
|
5575
|
-
const expanded = expandTilde(r,
|
|
5578
|
+
const expanded = expandTilde(r, homedir5);
|
|
5576
5579
|
const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(input.masterRoot, expanded));
|
|
5577
5580
|
rootsAbs.push(await realpathBestEffort(abs));
|
|
5578
5581
|
}
|
|
5579
5582
|
for (const e of input.extraInRoot ?? []) {
|
|
5580
|
-
const expanded = expandTilde(e,
|
|
5581
|
-
const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(
|
|
5583
|
+
const expanded = expandTilde(e, homedir5);
|
|
5584
|
+
const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(homedir5, expanded));
|
|
5582
5585
|
rootsAbs.push(await realpathBestEffort(abs));
|
|
5583
5586
|
}
|
|
5584
5587
|
if (rootsAbs.length === 0) {
|
|
@@ -5586,7 +5589,7 @@ async function classifyFilesBySourceRoot(input) {
|
|
|
5586
5589
|
}
|
|
5587
5590
|
for (const file of input.files) {
|
|
5588
5591
|
try {
|
|
5589
|
-
const abs = toAbsolute(file, workingDirAbs,
|
|
5592
|
+
const abs = toAbsolute(file, workingDirAbs, homedir5);
|
|
5590
5593
|
const real = await realpathBestEffort(abs);
|
|
5591
5594
|
const within = rootsAbs.some((root) => isUnder(real, root));
|
|
5592
5595
|
(within ? inRoot : outOfRoot).push(file);
|
|
@@ -7493,8 +7496,417 @@ function formatInt(n) {
|
|
|
7493
7496
|
|
|
7494
7497
|
// src/review/review-gaps.ts
|
|
7495
7498
|
import { existsSync, realpathSync } from "fs";
|
|
7499
|
+
import { homedir as homedir3 } from "os";
|
|
7500
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join18 } from "path";
|
|
7501
|
+
|
|
7502
|
+
// src/review/command-workdir.ts
|
|
7496
7503
|
import { homedir as homedir2 } from "os";
|
|
7497
|
-
import { basename as basename3, isAbsolute as isAbsolute2,
|
|
7504
|
+
import { basename as basename3, isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
|
|
7505
|
+
function ambiguous(reason) {
|
|
7506
|
+
return { kind: "ambiguous", reason };
|
|
7507
|
+
}
|
|
7508
|
+
var SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "ksh", "mksh"]);
|
|
7509
|
+
var INDIRECT_EXECUTORS = /* @__PURE__ */ new Set([
|
|
7510
|
+
".",
|
|
7511
|
+
"alias",
|
|
7512
|
+
"bash",
|
|
7513
|
+
"builtin",
|
|
7514
|
+
"chroot",
|
|
7515
|
+
"command",
|
|
7516
|
+
"dash",
|
|
7517
|
+
"doas",
|
|
7518
|
+
"env",
|
|
7519
|
+
"eval",
|
|
7520
|
+
"exec",
|
|
7521
|
+
"ksh",
|
|
7522
|
+
"mksh",
|
|
7523
|
+
"nice",
|
|
7524
|
+
"nohup",
|
|
7525
|
+
"sh",
|
|
7526
|
+
"source",
|
|
7527
|
+
"ssh",
|
|
7528
|
+
"stdbuf",
|
|
7529
|
+
"su",
|
|
7530
|
+
"sudo",
|
|
7531
|
+
"timeout",
|
|
7532
|
+
"trap",
|
|
7533
|
+
"xargs",
|
|
7534
|
+
"zsh"
|
|
7535
|
+
]);
|
|
7536
|
+
var DIRECTORY_STACK = /* @__PURE__ */ new Set(["pushd", "popd"]);
|
|
7537
|
+
var SHELL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
7538
|
+
"!",
|
|
7539
|
+
"case",
|
|
7540
|
+
"coproc",
|
|
7541
|
+
"do",
|
|
7542
|
+
"done",
|
|
7543
|
+
"elif",
|
|
7544
|
+
"else",
|
|
7545
|
+
"esac",
|
|
7546
|
+
"fi",
|
|
7547
|
+
"for",
|
|
7548
|
+
"function",
|
|
7549
|
+
"if",
|
|
7550
|
+
"in",
|
|
7551
|
+
"select",
|
|
7552
|
+
"then",
|
|
7553
|
+
"time",
|
|
7554
|
+
"until",
|
|
7555
|
+
"while",
|
|
7556
|
+
"{",
|
|
7557
|
+
"}"
|
|
7558
|
+
]);
|
|
7559
|
+
var RELOCATING_VARIABLES = /* @__PURE__ */ new Set([
|
|
7560
|
+
"CDPATH",
|
|
7561
|
+
"GIT_COMMON_DIR",
|
|
7562
|
+
"GIT_DIR",
|
|
7563
|
+
"GIT_WORK_TREE",
|
|
7564
|
+
"HOME",
|
|
7565
|
+
"OLDPWD",
|
|
7566
|
+
"PWD"
|
|
7567
|
+
]);
|
|
7568
|
+
var CHDIR_OPTIONS = /* @__PURE__ */ new Map([
|
|
7569
|
+
["git", /* @__PURE__ */ new Set(["-C", "--git-dir", "--work-tree"])],
|
|
7570
|
+
["make", /* @__PURE__ */ new Set(["-C", "--directory"])],
|
|
7571
|
+
["gmake", /* @__PURE__ */ new Set(["-C", "--directory"])],
|
|
7572
|
+
["env", /* @__PURE__ */ new Set(["-C", "--chdir"])],
|
|
7573
|
+
["tar", /* @__PURE__ */ new Set(["-C", "--directory"])],
|
|
7574
|
+
["pnpm", /* @__PURE__ */ new Set(["-C", "--dir", "--workspace-root", "-w"])],
|
|
7575
|
+
["npm", /* @__PURE__ */ new Set(["-C", "--prefix"])],
|
|
7576
|
+
["yarn", /* @__PURE__ */ new Set(["--cwd"])],
|
|
7577
|
+
["just", /* @__PURE__ */ new Set(["-d", "--working-directory"])]
|
|
7578
|
+
]);
|
|
7579
|
+
function deriveCommandWorkdir(command, args, cwd) {
|
|
7580
|
+
const program = basename3(command);
|
|
7581
|
+
if (SHELLS.has(program)) {
|
|
7582
|
+
const script = args.length === 2 && args[0] === "-c" ? args[1] : void 0;
|
|
7583
|
+
if (script === void 0) return ambiguous("unsupported_invocation");
|
|
7584
|
+
return readShellProgram(script, cwd);
|
|
7585
|
+
}
|
|
7586
|
+
if (!isPlainCommandHead(command)) return ambiguous("unrecognized_command_word");
|
|
7587
|
+
if (INDIRECT_EXECUTORS.has(program)) return ambiguous("indirect_execution");
|
|
7588
|
+
const words = args.map((text) => ({
|
|
7589
|
+
text,
|
|
7590
|
+
mask: "n".repeat(text.length),
|
|
7591
|
+
quoteOpens: []
|
|
7592
|
+
}));
|
|
7593
|
+
if (hasChdirOption(program, words)) return ambiguous("chdir_option");
|
|
7594
|
+
return { kind: "cwd" };
|
|
7595
|
+
}
|
|
7596
|
+
function readShellProgram(script, cwd) {
|
|
7597
|
+
const scan = scanProgram(script);
|
|
7598
|
+
if (!scan.ok) return ambiguous(scan.reason);
|
|
7599
|
+
const cds = [];
|
|
7600
|
+
let rank = 0;
|
|
7601
|
+
for (const [index, segment] of scan.segments.entries()) {
|
|
7602
|
+
const stripped = stripRedirections(segment);
|
|
7603
|
+
if (!stripped.ok) return ambiguous(stripped.reason);
|
|
7604
|
+
const words = stripped.words;
|
|
7605
|
+
const start = firstNonAssignment(words);
|
|
7606
|
+
for (const word of words.slice(0, start ?? words.length)) {
|
|
7607
|
+
const name2 = assignmentName(word);
|
|
7608
|
+
if (name2 === void 0) continue;
|
|
7609
|
+
if (RELOCATING_VARIABLES.has(name2)) return ambiguous("shell_state_assignment");
|
|
7610
|
+
if (hasLiveDollar(word)) return ambiguous("unreadable_assignment_value");
|
|
7611
|
+
if (assignmentValueHasTildeUser(word, name2.length)) return ambiguous("tilde_user");
|
|
7612
|
+
}
|
|
7613
|
+
if (start === void 0) continue;
|
|
7614
|
+
const head = words[start];
|
|
7615
|
+
if (head === void 0) continue;
|
|
7616
|
+
if (looksLikeAssignmentPrefix(head)) return ambiguous("assignment_or_command");
|
|
7617
|
+
if (head.text === "cd") {
|
|
7618
|
+
cds.push({ index, rank, operands: words.slice(start + 1) });
|
|
7619
|
+
rank++;
|
|
7620
|
+
continue;
|
|
7621
|
+
}
|
|
7622
|
+
rank++;
|
|
7623
|
+
if (hasLiveDollar(head)) return ambiguous("unexpanded_variable");
|
|
7624
|
+
if (SHELL_KEYWORDS.has(head.text)) return ambiguous("compound_command");
|
|
7625
|
+
if (!isPlainCommandHead(head.text)) return ambiguous("unrecognized_command_word");
|
|
7626
|
+
const name = basename3(head.text);
|
|
7627
|
+
if (DIRECTORY_STACK.has(name)) return ambiguous("directory_stack");
|
|
7628
|
+
if (INDIRECT_EXECUTORS.has(name)) return ambiguous("indirect_execution");
|
|
7629
|
+
const operands = words.slice(start + 1);
|
|
7630
|
+
if (hasChdirOption(name, operands)) return ambiguous("chdir_option");
|
|
7631
|
+
if (CHDIR_OPTIONS.has(name) && operands.some((w) => hasUnquoted(w, /\$/))) {
|
|
7632
|
+
return ambiguous("unexpanded_variable");
|
|
7633
|
+
}
|
|
7634
|
+
}
|
|
7635
|
+
if (cds.length === 0) return { kind: "cwd" };
|
|
7636
|
+
if (cds.length > 1) return ambiguous("multiple_cd");
|
|
7637
|
+
const cd = cds[0];
|
|
7638
|
+
if (cd === void 0) return ambiguous("unsupported_cd_form");
|
|
7639
|
+
if (cd.rank !== 0) return ambiguous("cd_not_first");
|
|
7640
|
+
if (scan.separators[cd.index] === "|") return ambiguous("cd_in_pipeline");
|
|
7641
|
+
return readCdTarget(cd.operands, cwd);
|
|
7642
|
+
}
|
|
7643
|
+
function scanProgram(script) {
|
|
7644
|
+
const segments = [];
|
|
7645
|
+
const separators = [];
|
|
7646
|
+
let words = [];
|
|
7647
|
+
let text = "";
|
|
7648
|
+
let mask = "";
|
|
7649
|
+
let quoteOpens = [];
|
|
7650
|
+
let started = false;
|
|
7651
|
+
const endWord = () => {
|
|
7652
|
+
if (started) words.push({ text, mask, quoteOpens });
|
|
7653
|
+
text = "";
|
|
7654
|
+
mask = "";
|
|
7655
|
+
quoteOpens = [];
|
|
7656
|
+
started = false;
|
|
7657
|
+
};
|
|
7658
|
+
const endSegment = (separator) => {
|
|
7659
|
+
endWord();
|
|
7660
|
+
if (words.length === 0) return separator === "newline";
|
|
7661
|
+
segments.push(words);
|
|
7662
|
+
words = [];
|
|
7663
|
+
separators.push(separator);
|
|
7664
|
+
return true;
|
|
7665
|
+
};
|
|
7666
|
+
const runLength = (index, char) => {
|
|
7667
|
+
let n = 0;
|
|
7668
|
+
while (script[index + n] === char) n++;
|
|
7669
|
+
return n;
|
|
7670
|
+
};
|
|
7671
|
+
const pushRedirection = (op) => {
|
|
7672
|
+
if (started && /^\d+$/.test(text) && !/[sd]/.test(mask) && quoteOpens.length === 0) {
|
|
7673
|
+
text = "";
|
|
7674
|
+
mask = "";
|
|
7675
|
+
started = false;
|
|
7676
|
+
}
|
|
7677
|
+
endWord();
|
|
7678
|
+
words.push({ text: op, mask: "n".repeat(op.length), quoteOpens: [], redirection: true });
|
|
7679
|
+
};
|
|
7680
|
+
let i = 0;
|
|
7681
|
+
while (i < script.length) {
|
|
7682
|
+
const c = script[i];
|
|
7683
|
+
if (c === "'") {
|
|
7684
|
+
const close = script.indexOf("'", i + 1);
|
|
7685
|
+
if (close === -1) return { ok: false, reason: "unterminated_quote" };
|
|
7686
|
+
const body = script.slice(i + 1, close);
|
|
7687
|
+
quoteOpens.push(text.length);
|
|
7688
|
+
text += body;
|
|
7689
|
+
mask += "s".repeat(body.length);
|
|
7690
|
+
started = true;
|
|
7691
|
+
i = close + 1;
|
|
7692
|
+
continue;
|
|
7693
|
+
}
|
|
7694
|
+
if (c === '"') {
|
|
7695
|
+
let j = i + 1;
|
|
7696
|
+
let closed = false;
|
|
7697
|
+
quoteOpens.push(text.length);
|
|
7698
|
+
while (j < script.length) {
|
|
7699
|
+
const d = script[j];
|
|
7700
|
+
if (d === '"') {
|
|
7701
|
+
closed = true;
|
|
7702
|
+
break;
|
|
7703
|
+
}
|
|
7704
|
+
if (d === "\\") return { ok: false, reason: "backslash_escape" };
|
|
7705
|
+
if (d === "`") return { ok: false, reason: "backtick" };
|
|
7706
|
+
if (d === "$" && script[j + 1] === "(")
|
|
7707
|
+
return { ok: false, reason: "command_substitution" };
|
|
7708
|
+
text += d;
|
|
7709
|
+
mask += "d";
|
|
7710
|
+
j++;
|
|
7711
|
+
}
|
|
7712
|
+
if (!closed) return { ok: false, reason: "unterminated_quote" };
|
|
7713
|
+
started = true;
|
|
7714
|
+
i = j + 1;
|
|
7715
|
+
continue;
|
|
7716
|
+
}
|
|
7717
|
+
if (c === "\\") return { ok: false, reason: "backslash_escape" };
|
|
7718
|
+
if (c === "`") return { ok: false, reason: "backtick" };
|
|
7719
|
+
if (c === "$" && script[i + 1] === "(") return { ok: false, reason: "command_substitution" };
|
|
7720
|
+
if (c === "$" && (script[i + 1] === "'" || script[i + 1] === '"')) {
|
|
7721
|
+
return { ok: false, reason: "dollar_quote" };
|
|
7722
|
+
}
|
|
7723
|
+
if (c === "(" || c === ")") return { ok: false, reason: "subshell" };
|
|
7724
|
+
if (c === "#" && !started) return { ok: false, reason: "comment" };
|
|
7725
|
+
if (c === "\r") return { ok: false, reason: "carriage_return" };
|
|
7726
|
+
if (c === "<") {
|
|
7727
|
+
if (runLength(i, "<") >= 2) return { ok: false, reason: "heredoc" };
|
|
7728
|
+
const next = script[i + 1];
|
|
7729
|
+
const op = next === "&" || next === ">" ? `<${next}` : "<";
|
|
7730
|
+
pushRedirection(op);
|
|
7731
|
+
i += op.length;
|
|
7732
|
+
continue;
|
|
7733
|
+
}
|
|
7734
|
+
if (c === ">") {
|
|
7735
|
+
const next = script[i + 1];
|
|
7736
|
+
const op = next === ">" || next === "&" || next === "|" ? `>${next}` : ">";
|
|
7737
|
+
pushRedirection(op);
|
|
7738
|
+
i += op.length;
|
|
7739
|
+
continue;
|
|
7740
|
+
}
|
|
7741
|
+
if (c === "&") {
|
|
7742
|
+
const n = runLength(i, "&");
|
|
7743
|
+
if (n === 2) {
|
|
7744
|
+
if (!endSegment("&&")) return { ok: false, reason: "shell_syntax" };
|
|
7745
|
+
i += 2;
|
|
7746
|
+
continue;
|
|
7747
|
+
}
|
|
7748
|
+
return { ok: false, reason: "background" };
|
|
7749
|
+
}
|
|
7750
|
+
if (c === "|") {
|
|
7751
|
+
if (runLength(i, "|") >= 2) return { ok: false, reason: "or_operator" };
|
|
7752
|
+
if (!endSegment("|")) return { ok: false, reason: "shell_syntax" };
|
|
7753
|
+
i++;
|
|
7754
|
+
continue;
|
|
7755
|
+
}
|
|
7756
|
+
if (c === ";") {
|
|
7757
|
+
if (!endSegment(";")) return { ok: false, reason: "shell_syntax" };
|
|
7758
|
+
i++;
|
|
7759
|
+
continue;
|
|
7760
|
+
}
|
|
7761
|
+
if (c === "\n") {
|
|
7762
|
+
if (!endSegment("newline")) return { ok: false, reason: "shell_syntax" };
|
|
7763
|
+
i++;
|
|
7764
|
+
continue;
|
|
7765
|
+
}
|
|
7766
|
+
if (c === " " || c === " ") {
|
|
7767
|
+
endWord();
|
|
7768
|
+
i++;
|
|
7769
|
+
continue;
|
|
7770
|
+
}
|
|
7771
|
+
text += c;
|
|
7772
|
+
mask += "n";
|
|
7773
|
+
started = true;
|
|
7774
|
+
i++;
|
|
7775
|
+
}
|
|
7776
|
+
endWord();
|
|
7777
|
+
const last = separators.at(-1);
|
|
7778
|
+
if (words.length === 0 && (last === "&&" || last === "|")) {
|
|
7779
|
+
return { ok: false, reason: "shell_syntax" };
|
|
7780
|
+
}
|
|
7781
|
+
segments.push(words);
|
|
7782
|
+
return { ok: true, segments, separators };
|
|
7783
|
+
}
|
|
7784
|
+
function stripRedirections(words) {
|
|
7785
|
+
const out = [];
|
|
7786
|
+
for (let i = 0; i < words.length; i++) {
|
|
7787
|
+
const word = words[i];
|
|
7788
|
+
if (word === void 0) continue;
|
|
7789
|
+
if (word.redirection !== true) {
|
|
7790
|
+
out.push(word);
|
|
7791
|
+
continue;
|
|
7792
|
+
}
|
|
7793
|
+
const target = words[i + 1];
|
|
7794
|
+
if (target === void 0 || target.redirection === true) {
|
|
7795
|
+
return { ok: false, reason: "shell_syntax" };
|
|
7796
|
+
}
|
|
7797
|
+
if (!isReadableRedirectionTarget(word.text, target)) {
|
|
7798
|
+
return { ok: false, reason: "unreadable_redirection" };
|
|
7799
|
+
}
|
|
7800
|
+
i++;
|
|
7801
|
+
}
|
|
7802
|
+
return { ok: true, words: out };
|
|
7803
|
+
}
|
|
7804
|
+
var FD_DUP_OPERATORS = /* @__PURE__ */ new Set([">&", "<&"]);
|
|
7805
|
+
var FD_DUP_TARGET = /^\d+$/;
|
|
7806
|
+
function isReadableRedirectionTarget(operator, target) {
|
|
7807
|
+
if (FD_DUP_OPERATORS.has(operator)) return FD_DUP_TARGET.test(target.text);
|
|
7808
|
+
if (target.text.length === 0) return false;
|
|
7809
|
+
return !hasUnquoted(target, /[$*?[{]/);
|
|
7810
|
+
}
|
|
7811
|
+
var ASSIGNMENT_PREFIX = /^([A-Za-z_][A-Za-z0-9_]*)=/;
|
|
7812
|
+
var AMBIGUOUS_ASSIGNMENT_START = /^[A-Za-z_][A-Za-z0-9_]*(\[|\+=)/;
|
|
7813
|
+
var PLAIN_COMMAND_HEAD = /^(?:~\/|\/|\.{1,2}\/)?[A-Za-z0-9_.+-]+(?:\/[A-Za-z0-9_.+-]+)*$/;
|
|
7814
|
+
function isPlainCommandHead(text) {
|
|
7815
|
+
if (!PLAIN_COMMAND_HEAD.test(text)) return false;
|
|
7816
|
+
if (text === ".") return true;
|
|
7817
|
+
const last = text.slice(text.lastIndexOf("/") + 1);
|
|
7818
|
+
return last !== "." && last !== "..";
|
|
7819
|
+
}
|
|
7820
|
+
function assignmentName(word) {
|
|
7821
|
+
if (word.redirection === true) return void 0;
|
|
7822
|
+
const match = ASSIGNMENT_PREFIX.exec(word.text);
|
|
7823
|
+
if (match?.[1] === void 0) return void 0;
|
|
7824
|
+
const upToEquals = match[0].length - 1;
|
|
7825
|
+
if (quotedBefore(word, upToEquals)) return void 0;
|
|
7826
|
+
return match[1];
|
|
7827
|
+
}
|
|
7828
|
+
function looksLikeAssignmentPrefix(word) {
|
|
7829
|
+
if (word.redirection === true) return false;
|
|
7830
|
+
const match = AMBIGUOUS_ASSIGNMENT_START.exec(word.text);
|
|
7831
|
+
if (match === null) return false;
|
|
7832
|
+
return !quotedBefore(word, match[0].length - 1);
|
|
7833
|
+
}
|
|
7834
|
+
function assignmentValueHasTildeUser(word, nameLength) {
|
|
7835
|
+
const at = nameLength + 1;
|
|
7836
|
+
if (word.text[at] !== "~" || word.mask[at] !== "n") return false;
|
|
7837
|
+
const next = word.text[at + 1];
|
|
7838
|
+
return next !== void 0 && next !== "/";
|
|
7839
|
+
}
|
|
7840
|
+
function quotedBefore(word, index) {
|
|
7841
|
+
if (/[sd]/.test(word.mask.slice(0, index))) return true;
|
|
7842
|
+
return word.quoteOpens.some((at) => at <= index);
|
|
7843
|
+
}
|
|
7844
|
+
function firstNonAssignment(words) {
|
|
7845
|
+
for (const [index, word] of words.entries()) {
|
|
7846
|
+
if (assignmentName(word) !== void 0) continue;
|
|
7847
|
+
return index;
|
|
7848
|
+
}
|
|
7849
|
+
return void 0;
|
|
7850
|
+
}
|
|
7851
|
+
function hasChdirOption(program, operands) {
|
|
7852
|
+
const options = CHDIR_OPTIONS.get(program);
|
|
7853
|
+
if (options === void 0) return false;
|
|
7854
|
+
return operands.some((word) => {
|
|
7855
|
+
if (word.redirection === true) return false;
|
|
7856
|
+
if (options.has(word.text)) return true;
|
|
7857
|
+
const eq = word.text.indexOf("=");
|
|
7858
|
+
if (eq > 0 && options.has(word.text.slice(0, eq))) return true;
|
|
7859
|
+
if (word.text.startsWith("--") || !word.text.startsWith("-")) return false;
|
|
7860
|
+
for (const option of options) {
|
|
7861
|
+
if (option.length !== 2 || option.startsWith("--")) continue;
|
|
7862
|
+
if (word.text.includes(option.slice(1), 1)) return true;
|
|
7863
|
+
}
|
|
7864
|
+
return false;
|
|
7865
|
+
});
|
|
7866
|
+
}
|
|
7867
|
+
function hasUnquoted(word, pattern) {
|
|
7868
|
+
for (let i = 0; i < word.text.length; i++) {
|
|
7869
|
+
if (word.mask[i] === "n" && pattern.test(word.text[i])) return true;
|
|
7870
|
+
}
|
|
7871
|
+
return false;
|
|
7872
|
+
}
|
|
7873
|
+
function hasLiveDollar(word) {
|
|
7874
|
+
for (let i = 0; i < word.text.length; i++) {
|
|
7875
|
+
if (word.text[i] === "$" && word.mask[i] !== "s") return true;
|
|
7876
|
+
}
|
|
7877
|
+
return false;
|
|
7878
|
+
}
|
|
7879
|
+
function readCdTarget(operands, cwd) {
|
|
7880
|
+
let operand;
|
|
7881
|
+
let afterEndOfOptions = false;
|
|
7882
|
+
if (operands.length === 1) operand = operands[0];
|
|
7883
|
+
else if (operands.length === 2 && operands[0]?.text === "--") {
|
|
7884
|
+
operand = operands[1];
|
|
7885
|
+
afterEndOfOptions = true;
|
|
7886
|
+
}
|
|
7887
|
+
if (operand === void 0 || operand.text.length === 0) {
|
|
7888
|
+
return ambiguous("unsupported_cd_form");
|
|
7889
|
+
}
|
|
7890
|
+
if (!afterEndOfOptions && operand.text.startsWith("-")) {
|
|
7891
|
+
return ambiguous("unsupported_cd_form");
|
|
7892
|
+
}
|
|
7893
|
+
let target = operand.text;
|
|
7894
|
+
if (hasLiveDollar(operand)) return ambiguous("unexpanded_variable");
|
|
7895
|
+
if (hasUnquoted(operand, /[*?[{]/)) return ambiguous("glob");
|
|
7896
|
+
if (target.startsWith("~")) {
|
|
7897
|
+
if (operand.mask[0] !== "n") return ambiguous("quoted_tilde");
|
|
7898
|
+
if (target === "~") target = homedir2();
|
|
7899
|
+
else if (target.startsWith("~/")) target = homedir2() + target.slice(1);
|
|
7900
|
+
else return ambiguous("tilde_user");
|
|
7901
|
+
}
|
|
7902
|
+
if (!isAbsolute2(target)) {
|
|
7903
|
+
if (!isAbsolute2(cwd)) return ambiguous("unresolvable_relative");
|
|
7904
|
+
target = resolve3(cwd, target);
|
|
7905
|
+
}
|
|
7906
|
+
return { kind: "target", path: target };
|
|
7907
|
+
}
|
|
7908
|
+
|
|
7909
|
+
// src/review/review-gaps.ts
|
|
7498
7910
|
function stripQuotes(s) {
|
|
7499
7911
|
if (s.length >= 2 && (s[0] === '"' && s.at(-1) === '"' || s[0] === "'" && s.at(-1) === "'")) {
|
|
7500
7912
|
return s.slice(1, -1);
|
|
@@ -7526,8 +7938,8 @@ function normalizeRepoPath(p) {
|
|
|
7526
7938
|
if (!p) return null;
|
|
7527
7939
|
let s = stripQuotes(p.trim()).replace(/\/+$/, "");
|
|
7528
7940
|
if (s.length === 0 || s === "~") return null;
|
|
7529
|
-
if (s.startsWith("~/")) s =
|
|
7530
|
-
if (
|
|
7941
|
+
if (s.startsWith("~/")) s = homedir3() + s.slice(1);
|
|
7942
|
+
if (isAbsolute3(s)) {
|
|
7531
7943
|
const real = resolveRealpath(s);
|
|
7532
7944
|
if (real !== null) {
|
|
7533
7945
|
return isRepoRoot(real) ? real : null;
|
|
@@ -7536,12 +7948,35 @@ function normalizeRepoPath(p) {
|
|
|
7536
7948
|
s = s.replace(/\/[^/]*-workspace\/([^/]+)/, "/$1");
|
|
7537
7949
|
const seg = s.split("/").filter((x) => x.length > 0).pop();
|
|
7538
7950
|
if (seg === void 0) return null;
|
|
7539
|
-
if (/-workspace$/.test(seg) ||
|
|
7951
|
+
if (/-workspace$/.test(seg) || s.includes("$")) return null;
|
|
7540
7952
|
return s;
|
|
7541
7953
|
}
|
|
7954
|
+
function recordRepoKey(p) {
|
|
7955
|
+
return resolveRepoRoot(p);
|
|
7956
|
+
}
|
|
7957
|
+
function resolveRepoRoot(p) {
|
|
7958
|
+
return classifyRepoPath(p).resolved;
|
|
7959
|
+
}
|
|
7960
|
+
function classifyRepoPath(p) {
|
|
7961
|
+
let s = stripQuotes((p ?? "").trim()).replace(/\/+$/, "");
|
|
7962
|
+
if (s.startsWith("~/")) s = homedir3() + s.slice(1);
|
|
7963
|
+
if (s.length === 0 || !isAbsolute3(s)) return { resolved: null, problem: "relative" };
|
|
7964
|
+
const real = resolveRealpath(s);
|
|
7965
|
+
if (real === null) return { resolved: null, problem: "absent" };
|
|
7966
|
+
if (!isRepoRoot(real)) return { resolved: null, problem: "not_a_repo_root" };
|
|
7967
|
+
return { resolved: real, problem: null };
|
|
7968
|
+
}
|
|
7969
|
+
function findUnbindableRepos(repos) {
|
|
7970
|
+
const out = [];
|
|
7971
|
+
repos.forEach((repo, index) => {
|
|
7972
|
+
const { problem } = classifyRepoPath(repo);
|
|
7973
|
+
if (problem !== null) out.push({ repo, index, problem });
|
|
7974
|
+
});
|
|
7975
|
+
return out;
|
|
7976
|
+
}
|
|
7542
7977
|
function normalizeRepoKey(p) {
|
|
7543
7978
|
const full = normalizeRepoPath(p);
|
|
7544
|
-
return full === null ? null :
|
|
7979
|
+
return full === null ? null : basename4(full);
|
|
7545
7980
|
}
|
|
7546
7981
|
function inspectCommand(args) {
|
|
7547
7982
|
const a = args.join(" ");
|
|
@@ -7555,15 +7990,23 @@ function inspectCommand(args) {
|
|
|
7555
7990
|
let m;
|
|
7556
7991
|
while ((m = re.exec(a)) !== null) {
|
|
7557
7992
|
const f = m[1];
|
|
7558
|
-
if (f !== void 0) files.add(
|
|
7993
|
+
if (f !== void 0) files.add(basename4(f));
|
|
7559
7994
|
}
|
|
7560
7995
|
}
|
|
7561
7996
|
return { files: [...files], examinedDiff };
|
|
7562
7997
|
}
|
|
7563
|
-
function
|
|
7564
|
-
const
|
|
7565
|
-
if (
|
|
7566
|
-
return normalizeRepoPath(
|
|
7998
|
+
function commandRepoWithProvenance(command, args, cwd) {
|
|
7999
|
+
const raw = commandRepoPath(command, args, cwd);
|
|
8000
|
+
if (raw === null) return { key: null, resolved: false };
|
|
8001
|
+
return { key: normalizeRepoPath(raw), resolved: resolveRepoRoot(raw) !== null };
|
|
8002
|
+
}
|
|
8003
|
+
function commandRepoPath(command, args, cwd) {
|
|
8004
|
+
const workdir = deriveCommandWorkdir(command, args, cwd);
|
|
8005
|
+
if (workdir.kind === "ambiguous") return null;
|
|
8006
|
+
return workdir.kind === "target" ? workdir.path : cwd;
|
|
8007
|
+
}
|
|
8008
|
+
function commandRepo(command, args, cwd) {
|
|
8009
|
+
return normalizeRepoPath(commandRepoPath(command, args, cwd));
|
|
7567
8010
|
}
|
|
7568
8011
|
function commandFailed(exitCode) {
|
|
7569
8012
|
return exitCode !== null && exitCode !== 0;
|
|
@@ -7572,7 +8015,7 @@ function commitFiles(args) {
|
|
|
7572
8015
|
const a = args.join(" ");
|
|
7573
8016
|
const add = a.match(/git add\s+([^&|;]+)/);
|
|
7574
8017
|
if (!add?.[1]) return [];
|
|
7575
|
-
return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) =>
|
|
8018
|
+
return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) => basename4(t));
|
|
7576
8019
|
}
|
|
7577
8020
|
var REVIEW_SOURCE = "codex-import";
|
|
7578
8021
|
var DEFAULT_WINDOW_HOURS = 24;
|
|
@@ -7585,6 +8028,9 @@ async function findReviewGaps(input) {
|
|
|
7585
8028
|
if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
|
|
7586
8029
|
const entries = await loadSessionEntries(input.paths, loadOpts);
|
|
7587
8030
|
const reviews = [];
|
|
8031
|
+
const selfReports = [];
|
|
8032
|
+
let noRepos = 0;
|
|
8033
|
+
let unresolvableRepo = 0;
|
|
7588
8034
|
const workUnits = /* @__PURE__ */ new Map();
|
|
7589
8035
|
const unknownCommits = /* @__PURE__ */ new Map();
|
|
7590
8036
|
for (const entry of entries) {
|
|
@@ -7596,11 +8042,33 @@ async function findReviewGaps(input) {
|
|
|
7596
8042
|
for await (const ev of replayEvents(sessionDir, {
|
|
7597
8043
|
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
7598
8044
|
})) {
|
|
8045
|
+
if (ev.type === "review_recorded") {
|
|
8046
|
+
const recordedAt = Date.parse(ev.occurred_at);
|
|
8047
|
+
const named = ev.repos_resolved !== void 0 && ev.repos_resolved.length > 0 ? ev.repos_resolved : ev.repos ?? [];
|
|
8048
|
+
const keys = named.map((r) => recordRepoKey(r));
|
|
8049
|
+
const repos2 = new Set(keys.filter((r) => r !== null));
|
|
8050
|
+
if (keys.some((k) => k === null) || repos2.size === 0 || Number.isNaN(recordedAt)) {
|
|
8051
|
+
if (named.length === 0) noRepos++;
|
|
8052
|
+
else unresolvableRepo++;
|
|
8053
|
+
continue;
|
|
8054
|
+
}
|
|
8055
|
+
selfReports.push({
|
|
8056
|
+
sessionId: entry.sessionId,
|
|
8057
|
+
eventId: ev.id,
|
|
8058
|
+
reviewer: ev.reviewer,
|
|
8059
|
+
target: ev.target,
|
|
8060
|
+
recordedAt: ev.occurred_at,
|
|
8061
|
+
commits: ev.commits ?? [],
|
|
8062
|
+
at: recordedAt,
|
|
8063
|
+
repos: repos2
|
|
8064
|
+
});
|
|
8065
|
+
continue;
|
|
8066
|
+
}
|
|
7599
8067
|
if (ev.type !== "command_executed") continue;
|
|
7600
8068
|
if (commandFailed(ev.exit_code)) continue;
|
|
7601
8069
|
const at = Date.parse(ev.occurred_at);
|
|
7602
8070
|
if (isReview) {
|
|
7603
|
-
const repo2 = commandRepo(ev.args, ev.cwd);
|
|
8071
|
+
const repo2 = commandRepo(ev.command, ev.args, ev.cwd);
|
|
7604
8072
|
if (repo2 === null) continue;
|
|
7605
8073
|
const ins = inspectCommand(ev.args);
|
|
7606
8074
|
const slot = reviewRepos.get(repo2) ?? { examinedDiff: false, files: /* @__PURE__ */ new Set() };
|
|
@@ -7611,7 +8079,11 @@ async function findReviewGaps(input) {
|
|
|
7611
8079
|
continue;
|
|
7612
8080
|
}
|
|
7613
8081
|
if (!ev.args.join(" ").includes("git commit")) continue;
|
|
7614
|
-
const repo =
|
|
8082
|
+
const { key: repo, resolved: keyResolved } = commandRepoWithProvenance(
|
|
8083
|
+
ev.command,
|
|
8084
|
+
ev.args,
|
|
8085
|
+
ev.cwd
|
|
8086
|
+
);
|
|
7615
8087
|
if (repo === null || Number.isNaN(at)) {
|
|
7616
8088
|
const list2 = unknownCommits.get(entry.sessionId) ?? [];
|
|
7617
8089
|
list2.push(Number.isNaN(at) ? null : at);
|
|
@@ -7620,7 +8092,7 @@ async function findReviewGaps(input) {
|
|
|
7620
8092
|
}
|
|
7621
8093
|
const byRepo = workUnits.get(entry.sessionId) ?? /* @__PURE__ */ new Map();
|
|
7622
8094
|
const list = byRepo.get(repo) ?? [];
|
|
7623
|
-
list.push({ repo, at, files: commitFiles(ev.args) });
|
|
8095
|
+
list.push({ repo, at, files: commitFiles(ev.args), keyResolved });
|
|
7624
8096
|
byRepo.set(repo, list);
|
|
7625
8097
|
workUnits.set(entry.sessionId, byRepo);
|
|
7626
8098
|
}
|
|
@@ -7635,19 +8107,33 @@ async function findReviewGaps(input) {
|
|
|
7635
8107
|
const windowMs = windowHours * 3600 * 1e3;
|
|
7636
8108
|
const units = [];
|
|
7637
8109
|
let newestCommit = null;
|
|
8110
|
+
const attachedSelfReports = /* @__PURE__ */ new Set();
|
|
8111
|
+
const refusedForUnit = /* @__PURE__ */ new Set();
|
|
8112
|
+
let refusedPairings = 0;
|
|
7638
8113
|
for (const [sessionId, byRepo] of workUnits) {
|
|
7639
8114
|
for (const [repoPath, commits] of byRepo) {
|
|
7640
|
-
const label =
|
|
7641
|
-
if (scope !== null && !scope.includes(label)) continue;
|
|
8115
|
+
const label = basename4(repoPath);
|
|
7642
8116
|
const times = commits.map((c) => c.at).sort((a, b) => a - b);
|
|
7643
8117
|
const first = times[0] ?? null;
|
|
7644
8118
|
const last = times[times.length - 1] ?? null;
|
|
8119
|
+
const earliest = first ?? last ?? 0;
|
|
8120
|
+
const latest = last ?? first ?? 0;
|
|
8121
|
+
const unitRepoIsHere = commits.every((c) => c.keyResolved);
|
|
8122
|
+
const inWindow = selfReports.filter(
|
|
8123
|
+
(r) => r.repos.has(repoPath) && r.at >= earliest - windowMs && r.at <= latest + windowMs
|
|
8124
|
+
);
|
|
8125
|
+
const selfBound = unitRepoIsHere ? inWindow : [];
|
|
8126
|
+
if (!unitRepoIsHere) {
|
|
8127
|
+
refusedPairings += inWindow.length;
|
|
8128
|
+
for (const r of inWindow) refusedForUnit.add(r.eventId);
|
|
8129
|
+
}
|
|
8130
|
+
for (const r of selfBound) attachedSelfReports.add(r.eventId);
|
|
8131
|
+
if (scope !== null && !scope.includes(label)) continue;
|
|
7645
8132
|
if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);
|
|
7646
8133
|
const changedFiles = new Set(commits.flatMap((c) => c.files));
|
|
7647
|
-
const before = first ?? last ?? 0;
|
|
7648
8134
|
const nearby = reviews.filter((r) => {
|
|
7649
8135
|
if (!r.repos.has(repoPath) || r.endedAt === null) return false;
|
|
7650
|
-
return r.endedAt <=
|
|
8136
|
+
return r.endedAt <= earliest && r.endedAt >= earliest - windowMs;
|
|
7651
8137
|
});
|
|
7652
8138
|
const bound = nearby.filter((r) => {
|
|
7653
8139
|
const touched = r.repos.get(repoPath);
|
|
@@ -7665,6 +8151,9 @@ async function findReviewGaps(input) {
|
|
|
7665
8151
|
firstCommitAt: first === null ? null : new Date(first).toISOString(),
|
|
7666
8152
|
lastCommitAt: last === null ? null : new Date(last).toISOString(),
|
|
7667
8153
|
verdict,
|
|
8154
|
+
// Attached after the verdict is computed, and deliberately not an input
|
|
8155
|
+
// to it: a record must never move a unit out of `gaps`.
|
|
8156
|
+
selfReports: selfBound.map((r) => toSelfReportedReview(r, r.at > earliest)),
|
|
7668
8157
|
reviews: cited.map((r) => ({
|
|
7669
8158
|
sessionId: r.sessionId,
|
|
7670
8159
|
examinedDiff: r.repos.get(repoPath)?.examinedDiff ?? false,
|
|
@@ -7674,34 +8163,41 @@ async function findReviewGaps(input) {
|
|
|
7674
8163
|
});
|
|
7675
8164
|
}
|
|
7676
8165
|
}
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
units.push({
|
|
7684
|
-
repo: "(unknown)",
|
|
7685
|
-
sessionId,
|
|
7686
|
-
commitCount: times.length,
|
|
7687
|
-
firstCommitAt: first === null ? null : new Date(first).toISOString(),
|
|
7688
|
-
lastCommitAt: last === null ? null : new Date(last).toISOString(),
|
|
7689
|
-
verdict: "unknown",
|
|
7690
|
-
reviews: []
|
|
7691
|
-
});
|
|
8166
|
+
for (const [sessionId, times] of unknownCommits) {
|
|
8167
|
+
const valid = times.filter((t) => t !== null).sort((a, b) => a - b);
|
|
8168
|
+
const first = valid[0] ?? null;
|
|
8169
|
+
const last = valid[valid.length - 1] ?? null;
|
|
8170
|
+
if (last !== null && scope === null) {
|
|
8171
|
+
newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);
|
|
7692
8172
|
}
|
|
8173
|
+
units.push({
|
|
8174
|
+
repo: "(unknown)",
|
|
8175
|
+
sessionId,
|
|
8176
|
+
commitCount: times.length,
|
|
8177
|
+
firstCommitAt: first === null ? null : new Date(first).toISOString(),
|
|
8178
|
+
lastCommitAt: last === null ? null : new Date(last).toISOString(),
|
|
8179
|
+
verdict: "unknown",
|
|
8180
|
+
reviews: [],
|
|
8181
|
+
// No repo key, so nothing a record's `repos` could bind to.
|
|
8182
|
+
selfReports: []
|
|
8183
|
+
});
|
|
7693
8184
|
}
|
|
8185
|
+
const missed = selfReports.filter((r) => !attachedSelfReports.has(r.eventId));
|
|
8186
|
+
const unverifiableUnit = missed.filter((r) => refusedForUnit.has(r.eventId)).length;
|
|
8187
|
+
const noMatchingUnit = missed.length - unverifiableUnit;
|
|
7694
8188
|
const recentFirst = (a, b) => (Date.parse(b.lastCommitAt ?? "") || 0) - (Date.parse(a.lastCommitAt ?? "") || 0);
|
|
7695
|
-
const
|
|
8189
|
+
const talliedUnits = scope === null ? units : units.filter((u) => u.verdict !== "unknown");
|
|
8190
|
+
const repoKeys = [...new Set(talliedUnits.map((u) => u.repo))].sort();
|
|
7696
8191
|
const repos = repoKeys.map((repo) => {
|
|
7697
|
-
const us =
|
|
8192
|
+
const us = talliedUnits.filter((u) => u.repo === repo);
|
|
7698
8193
|
return {
|
|
7699
8194
|
repo,
|
|
7700
8195
|
units: us.length,
|
|
7701
8196
|
omissionUnits: us.filter((u) => u.verdict === "omission").length,
|
|
7702
8197
|
nearUnboundUnits: us.filter((u) => u.verdict === "near_unbound").length,
|
|
7703
8198
|
candidateUnits: us.filter((u) => u.verdict === "candidate").length,
|
|
7704
|
-
unknownUnits: us.filter((u) => u.verdict === "unknown").length
|
|
8199
|
+
unknownUnits: us.filter((u) => u.verdict === "unknown").length,
|
|
8200
|
+
selfReportedGapUnits: us.filter((u) => isGap(u) && u.selfReports.length > 0).length
|
|
7705
8201
|
};
|
|
7706
8202
|
});
|
|
7707
8203
|
return {
|
|
@@ -7709,12 +8205,34 @@ async function findReviewGaps(input) {
|
|
|
7709
8205
|
windowHours,
|
|
7710
8206
|
scope,
|
|
7711
8207
|
repos,
|
|
7712
|
-
gaps: units.filter(
|
|
8208
|
+
gaps: units.filter(isGap).sort(recentFirst),
|
|
7713
8209
|
candidates: units.filter((u) => u.verdict === "candidate").sort(recentFirst),
|
|
7714
8210
|
unknowns: units.filter((u) => u.verdict === "unknown").sort(recentFirst),
|
|
8211
|
+
unattachedSelfReports: {
|
|
8212
|
+
total: noRepos + unresolvableRepo + noMatchingUnit + unverifiableUnit,
|
|
8213
|
+
noRepos,
|
|
8214
|
+
unresolvableRepo,
|
|
8215
|
+
noMatchingUnit,
|
|
8216
|
+
unverifiableUnit
|
|
8217
|
+
},
|
|
8218
|
+
refusedPairings,
|
|
7715
8219
|
newestCommitAt: newestCommit === null ? null : new Date(newestCommit).toISOString()
|
|
7716
8220
|
};
|
|
7717
8221
|
}
|
|
8222
|
+
function isGap(u) {
|
|
8223
|
+
return u.verdict === "omission" || u.verdict === "near_unbound";
|
|
8224
|
+
}
|
|
8225
|
+
function toSelfReportedReview(r, recordedAfterCommit) {
|
|
8226
|
+
return {
|
|
8227
|
+
sessionId: r.sessionId,
|
|
8228
|
+
eventId: r.eventId,
|
|
8229
|
+
reviewer: r.reviewer,
|
|
8230
|
+
target: r.target,
|
|
8231
|
+
recordedAt: r.recordedAt,
|
|
8232
|
+
commits: r.commits,
|
|
8233
|
+
recordedAfterCommit
|
|
8234
|
+
};
|
|
8235
|
+
}
|
|
7718
8236
|
|
|
7719
8237
|
// src/review/review-record.ts
|
|
7720
8238
|
var VALID_VERDICTS = /* @__PURE__ */ new Set(["pass", "needs-attention", "fail"]);
|
|
@@ -7723,6 +8241,8 @@ var VALID_BLOCK_REASONS = /* @__PURE__ */ new Set(["spec-deviation", "design-rev
|
|
|
7723
8241
|
var ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
7724
8242
|
"reviewer",
|
|
7725
8243
|
"target",
|
|
8244
|
+
"repos",
|
|
8245
|
+
"commits",
|
|
7726
8246
|
"verdict",
|
|
7727
8247
|
"findings",
|
|
7728
8248
|
"blocked"
|
|
@@ -7753,13 +8273,19 @@ function parseReviewRecordInput(raw) {
|
|
|
7753
8273
|
for (const key of Object.keys(obj)) {
|
|
7754
8274
|
if (!ALLOWED_KEYS.has(key)) {
|
|
7755
8275
|
throw new Error(
|
|
7756
|
-
`Unknown field '${key}'. Allowed: reviewer, target, verdict, findings, blocked.`
|
|
8276
|
+
`Unknown field '${key}'. Allowed: reviewer, target, repos, commits, verdict, findings, blocked.`
|
|
7757
8277
|
);
|
|
7758
8278
|
}
|
|
7759
8279
|
}
|
|
7760
8280
|
const reviewer = requireNonEmptyString(obj.reviewer, "reviewer");
|
|
7761
8281
|
const target = requireNonEmptyString(obj.target, "target");
|
|
7762
8282
|
const out = { reviewer, target };
|
|
8283
|
+
if (obj.repos !== void 0) {
|
|
8284
|
+
out.repos = parseStringArray(obj.repos, "repos");
|
|
8285
|
+
}
|
|
8286
|
+
if (obj.commits !== void 0) {
|
|
8287
|
+
out.commits = parseStringArray(obj.commits, "commits");
|
|
8288
|
+
}
|
|
7763
8289
|
if (obj.verdict !== void 0) {
|
|
7764
8290
|
if (typeof obj.verdict !== "string" || !VALID_VERDICTS.has(obj.verdict)) {
|
|
7765
8291
|
throw new Error(`verdict must be one of pass, needs-attention, fail, got '${obj.verdict}'.`);
|
|
@@ -7774,6 +8300,12 @@ function parseReviewRecordInput(raw) {
|
|
|
7774
8300
|
}
|
|
7775
8301
|
return out;
|
|
7776
8302
|
}
|
|
8303
|
+
function parseStringArray(value, field) {
|
|
8304
|
+
if (!Array.isArray(value)) {
|
|
8305
|
+
throw new Error(`${field} must be an array of strings.`);
|
|
8306
|
+
}
|
|
8307
|
+
return value.map((item, i) => requireNonEmptyString(item, `${field}[${i}]`));
|
|
8308
|
+
}
|
|
7777
8309
|
function parseFindings(value) {
|
|
7778
8310
|
if (!Array.isArray(value)) {
|
|
7779
8311
|
throw new Error("findings must be an array of objects.");
|
|
@@ -7856,6 +8388,9 @@ function buildReviewRecordedEvent(input) {
|
|
|
7856
8388
|
type: "review_recorded",
|
|
7857
8389
|
reviewer: review.reviewer,
|
|
7858
8390
|
target: review.target,
|
|
8391
|
+
...review.repos !== void 0 ? { repos: review.repos } : {},
|
|
8392
|
+
...input.reposResolved !== void 0 && input.reposResolved.length > 0 ? { repos_resolved: input.reposResolved } : {},
|
|
8393
|
+
...review.commits !== void 0 ? { commits: review.commits } : {},
|
|
7859
8394
|
...review.verdict !== void 0 ? { verdict: review.verdict } : {},
|
|
7860
8395
|
...review.findings !== void 0 ? { findings: review.findings } : {},
|
|
7861
8396
|
...review.blocked !== void 0 ? { blocked: review.blocked } : {}
|
|
@@ -7949,7 +8484,7 @@ var ChildProcessRunner = class {
|
|
|
7949
8484
|
if (killTimer !== null) clearTimeout(killTimer);
|
|
7950
8485
|
options.signal?.removeEventListener("abort", onAbort);
|
|
7951
8486
|
};
|
|
7952
|
-
return new Promise((
|
|
8487
|
+
return new Promise((resolve4, reject) => {
|
|
7953
8488
|
child.once("error", (error) => {
|
|
7954
8489
|
if (settled) return;
|
|
7955
8490
|
settled = true;
|
|
@@ -7961,7 +8496,7 @@ var ChildProcessRunner = class {
|
|
|
7961
8496
|
settled = true;
|
|
7962
8497
|
cleanup();
|
|
7963
8498
|
const ended_at = /* @__PURE__ */ new Date();
|
|
7964
|
-
|
|
8499
|
+
resolve4({
|
|
7965
8500
|
command: snapshotCommand,
|
|
7966
8501
|
args: snapshotArgs,
|
|
7967
8502
|
cwd: snapshotCwd,
|
|
@@ -8400,7 +8935,7 @@ function hasErrorCode6(error) {
|
|
|
8400
8935
|
|
|
8401
8936
|
// src/storage/session-import.ts
|
|
8402
8937
|
import { mkdir as mkdir5, readFile as readFile10, rm as rm2 } from "fs/promises";
|
|
8403
|
-
import { homedir as
|
|
8938
|
+
import { homedir as homedir4 } from "os";
|
|
8404
8939
|
import { join as join21 } from "path";
|
|
8405
8940
|
async function importSessionFromJson(paths, manifest, payload, options) {
|
|
8406
8941
|
if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
|
|
@@ -8509,7 +9044,7 @@ function withIntegrity(record, chainResult) {
|
|
|
8509
9044
|
};
|
|
8510
9045
|
}
|
|
8511
9046
|
function buildSessionRecord(input, manifest, newSessionId, options) {
|
|
8512
|
-
const home =
|
|
9047
|
+
const home = homedir4();
|
|
8513
9048
|
const workingDirectoryRaw = input.working_directory;
|
|
8514
9049
|
const workingDirectorySanitized = sanitizeWorkingDirectory(workingDirectoryRaw, {
|
|
8515
9050
|
homedir: home
|
|
@@ -8866,6 +9401,7 @@ export {
|
|
|
8866
9401
|
findBasouStopHookCommand,
|
|
8867
9402
|
findErrorCode,
|
|
8868
9403
|
findReviewGaps,
|
|
9404
|
+
findUnbindableRepos,
|
|
8869
9405
|
formatDurationMs,
|
|
8870
9406
|
genesisHash,
|
|
8871
9407
|
getDiff,
|
|
@@ -8929,6 +9465,7 @@ export {
|
|
|
8929
9465
|
resolveClaudeCodeCommand,
|
|
8930
9466
|
resolveCodexCommand,
|
|
8931
9467
|
resolveRepoContentLanguage,
|
|
9468
|
+
resolveRepoRoot,
|
|
8932
9469
|
resolveRepositoryRoot,
|
|
8933
9470
|
resolveSessionId,
|
|
8934
9471
|
resolveTaskId,
|