@basou/core 0.36.0 → 0.38.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 +23 -9
- package/dist/index.js +542 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/event.schema.json +16 -2
- package/schemas/session-import.schema.json +16 -2
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
|
|
|
@@ -471,7 +471,7 @@ function claudeTranscriptToImportPayload(records, options) {
|
|
|
471
471
|
cachedInputTokens += readNonNegInt(usage.cache_read_input_tokens);
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
|
-
const cwd = readString3(record.cwd) ?? workingDir ??
|
|
474
|
+
const cwd = readString3(record.cwd) ?? workingDir ?? null;
|
|
475
475
|
for (const item of toolUses(record)) {
|
|
476
476
|
const name = readString3(item.name);
|
|
477
477
|
const input = isObject3(item.input) ? item.input : void 0;
|
|
@@ -581,9 +581,20 @@ function commandExecutedEvent(occurredAt, sessionId, command, cwd) {
|
|
|
581
581
|
return {
|
|
582
582
|
...baseEvent(occurredAt, sessionId),
|
|
583
583
|
type: "command_executed",
|
|
584
|
-
command
|
|
584
|
+
// The transcript records the Bash tool's command STRING and nothing about
|
|
585
|
+
// what ran it. `command: "bash"` used to be written here, which put an
|
|
586
|
+
// unobserved executor inside the hash chain; null says what is true. `-c`
|
|
587
|
+
// is kept so the line still reads as a shell program rather than an argv,
|
|
588
|
+
// which is the one bit readers need (#191).
|
|
589
|
+
command: null,
|
|
585
590
|
args: ["-c", command],
|
|
586
591
|
cwd,
|
|
592
|
+
// UNKNOWN rather than a signal termination or a success. NOT because the
|
|
593
|
+
// transcript is silent about the outcome -- it is not. A failed Bash tool
|
|
594
|
+
// result carries `is_error: true` and its text begins `Exit code N`, so the
|
|
595
|
+
// code is recoverable for the commands that failed (measured: 66 of 2,636
|
|
596
|
+
// tool results across twelve transcripts). Reading it is its own change and
|
|
597
|
+
// has not been made, so nothing is claimed here yet.
|
|
587
598
|
exit_code: null,
|
|
588
599
|
duration_ms: 0
|
|
589
600
|
};
|
|
@@ -717,7 +728,7 @@ function codexRolloutToImportPayload(records, options) {
|
|
|
717
728
|
ts,
|
|
718
729
|
placeholderSessionId,
|
|
719
730
|
command2.cmd,
|
|
720
|
-
command2.workdir
|
|
731
|
+
scriptedCommandCwd(command2.workdir, workingDir),
|
|
721
732
|
{
|
|
722
733
|
exitCode: null,
|
|
723
734
|
durationMs
|
|
@@ -731,7 +742,7 @@ function codexRolloutToImportPayload(records, options) {
|
|
|
731
742
|
if (readString4(payload2.name) !== "exec_command") continue;
|
|
732
743
|
const command = readExecCommand(payload2.arguments);
|
|
733
744
|
if (command === void 0) continue;
|
|
734
|
-
const cwd = command.workdir
|
|
745
|
+
const cwd = scriptedCommandCwd(command.workdir, workingDir);
|
|
735
746
|
const output = readCallId(payload2.call_id, outputsByCallId);
|
|
736
747
|
const execTsMs = Date.parse(ts);
|
|
737
748
|
if (Number.isFinite(execTsMs)) engagementTsMs.push(execTsMs);
|
|
@@ -832,7 +843,14 @@ function commandExecutedEvent2(occurredAt, sessionId, command, cwd, outcome) {
|
|
|
832
843
|
return {
|
|
833
844
|
...baseEvent2(occurredAt, sessionId),
|
|
834
845
|
type: "command_executed",
|
|
835
|
-
command: "bash",
|
|
846
|
+
// `command: "bash"` used to be written here and was WRONG, not merely
|
|
847
|
+
// unobserved: codex runs its commands through `/bin/zsh -lc`. The rollout
|
|
848
|
+
// carries no per-call shell to read, and the environment block's
|
|
849
|
+
// `environments.local.shell` describes the environment rather than what ran
|
|
850
|
+
// a given command — so writing `zsh` would be a better-informed guess, not
|
|
851
|
+
// an observation. null is what was actually observed about the executor
|
|
852
|
+
// (#191). `-c` is kept so the line still reads as a shell program.
|
|
853
|
+
command: null,
|
|
836
854
|
args: ["-c", command],
|
|
837
855
|
cwd,
|
|
838
856
|
exit_code: outcome.exitCode,
|
|
@@ -990,6 +1008,9 @@ function findObjectEnd(script, start) {
|
|
|
990
1008
|
}
|
|
991
1009
|
function readExecArguments(literal) {
|
|
992
1010
|
const values = /* @__PURE__ */ new Map();
|
|
1011
|
+
let workdirUnreadable = false;
|
|
1012
|
+
const survivesSpread = { cmd: false, workdir: false };
|
|
1013
|
+
let sawSpread = false;
|
|
993
1014
|
let i = 1;
|
|
994
1015
|
while (i < literal.length) {
|
|
995
1016
|
const at = skipWhitespaceAndComments(literal, i);
|
|
@@ -1000,7 +1021,15 @@ function readExecArguments(literal) {
|
|
|
1000
1021
|
i = at + 1;
|
|
1001
1022
|
continue;
|
|
1002
1023
|
}
|
|
1003
|
-
if (literal.startsWith("...", at))
|
|
1024
|
+
if (literal.startsWith("...", at)) {
|
|
1025
|
+
sawSpread = true;
|
|
1026
|
+
survivesSpread.cmd = false;
|
|
1027
|
+
survivesSpread.workdir = false;
|
|
1028
|
+
const next2 = skipToPropertyEnd(literal, at);
|
|
1029
|
+
if (next2 === -1) return void 0;
|
|
1030
|
+
i = next2;
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1004
1033
|
let key;
|
|
1005
1034
|
let afterKey;
|
|
1006
1035
|
if (ch === '"' || ch === "'") {
|
|
@@ -1016,7 +1045,8 @@ function readExecArguments(literal) {
|
|
|
1016
1045
|
const colon = skipWhitespaceAndComments(literal, afterKey);
|
|
1017
1046
|
if (colon === -1) return void 0;
|
|
1018
1047
|
if (literal[colon] !== ":") {
|
|
1019
|
-
if (key === "cmd"
|
|
1048
|
+
if (key === "cmd") return void 0;
|
|
1049
|
+
if (key === "workdir") workdirUnreadable = true;
|
|
1020
1050
|
i = colon;
|
|
1021
1051
|
const next2 = skipToPropertyEnd(literal, colon);
|
|
1022
1052
|
if (next2 === -1) return void 0;
|
|
@@ -1039,23 +1069,42 @@ function readExecArguments(literal) {
|
|
|
1039
1069
|
if (terminator === "," || terminator === "}") {
|
|
1040
1070
|
value = decoded.length > 0 ? decoded : void 0;
|
|
1041
1071
|
afterValue = after;
|
|
1042
|
-
} else if (key === "cmd"
|
|
1072
|
+
} else if (key === "cmd") {
|
|
1043
1073
|
return void 0;
|
|
1074
|
+
} else if (key === "workdir") {
|
|
1075
|
+
workdirUnreadable = true;
|
|
1044
1076
|
}
|
|
1045
|
-
} else if (key === "cmd"
|
|
1077
|
+
} else if (key === "cmd") {
|
|
1046
1078
|
return void 0;
|
|
1079
|
+
} else if (key === "workdir") {
|
|
1080
|
+
workdirUnreadable = true;
|
|
1047
1081
|
}
|
|
1048
1082
|
if (key === "cmd" || key === "workdir") {
|
|
1049
|
-
if (values.has(key))
|
|
1050
|
-
|
|
1083
|
+
if (values.has(key)) {
|
|
1084
|
+
if (key === "cmd") return void 0;
|
|
1085
|
+
workdirUnreadable = true;
|
|
1086
|
+
} else {
|
|
1087
|
+
values.set(key, value);
|
|
1088
|
+
if (value !== void 0) survivesSpread[key] = true;
|
|
1089
|
+
}
|
|
1051
1090
|
}
|
|
1052
1091
|
const next = skipToPropertyEnd(literal, afterValue);
|
|
1053
1092
|
if (next === -1) return void 0;
|
|
1054
1093
|
i = next;
|
|
1055
1094
|
}
|
|
1056
1095
|
const cmd = values.get("cmd");
|
|
1057
|
-
if (cmd === void 0) return void 0;
|
|
1058
|
-
return { cmd, workdir:
|
|
1096
|
+
if (cmd === void 0 || !survivesSpread.cmd) return void 0;
|
|
1097
|
+
return { cmd, workdir: resolveScriptedWorkdir() };
|
|
1098
|
+
function resolveScriptedWorkdir() {
|
|
1099
|
+
if (workdirUnreadable) return null;
|
|
1100
|
+
if (values.has("workdir") && survivesSpread.workdir) return values.get("workdir");
|
|
1101
|
+
if (sawSpread) return null;
|
|
1102
|
+
return void 0;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function scriptedCommandCwd(workdir, sessionCwd) {
|
|
1106
|
+
if (workdir === null) return null;
|
|
1107
|
+
return workdir ?? sessionCwd ?? null;
|
|
1059
1108
|
}
|
|
1060
1109
|
function skipToPropertyEnd(literal, at) {
|
|
1061
1110
|
let i = at;
|
|
@@ -1422,9 +1471,9 @@ var ApprovalExpiredEventSchema = BaseEventSchema.extend({
|
|
|
1422
1471
|
});
|
|
1423
1472
|
var CommandExecutedEventSchema = BaseEventSchema.extend({
|
|
1424
1473
|
type: z3.literal("command_executed"),
|
|
1425
|
-
command: z3.string(),
|
|
1474
|
+
command: z3.string().nullable(),
|
|
1426
1475
|
args: z3.array(z3.string()),
|
|
1427
|
-
cwd: z3.string(),
|
|
1476
|
+
cwd: z3.string().nullable(),
|
|
1428
1477
|
exit_code: z3.number().int().nullable(),
|
|
1429
1478
|
signal: z3.string().nullable().optional(),
|
|
1430
1479
|
received_signal: z3.string().nullable().optional(),
|
|
@@ -2484,8 +2533,10 @@ var SessionSourceSchema = z5.looseObject({
|
|
|
2484
2533
|
var InvocationSchema = z5.looseObject({
|
|
2485
2534
|
command: z5.string().min(1),
|
|
2486
2535
|
args: z5.array(z5.string()).default([]),
|
|
2487
|
-
// Nullable
|
|
2488
|
-
// code
|
|
2536
|
+
// Nullable because the outcome may be UNKNOWN: a signal-terminated run has no
|
|
2537
|
+
// exit code, and an imported session's source may never record one. Unknown
|
|
2538
|
+
// is not success. The same nullability and the same meaning are mirrored in
|
|
2539
|
+
// CommandExecutedEventSchema.
|
|
2489
2540
|
exit_code: z5.number().int().nullable()
|
|
2490
2541
|
});
|
|
2491
2542
|
var SessionMetricsSchema = z5.looseObject({
|
|
@@ -5551,13 +5602,13 @@ async function realpathBestEffort(absPath) {
|
|
|
5551
5602
|
}
|
|
5552
5603
|
return normalize(absPath);
|
|
5553
5604
|
}
|
|
5554
|
-
function expandTilde(p,
|
|
5555
|
-
if (p === "~") return
|
|
5556
|
-
if (p.startsWith("~/")) return join14(
|
|
5605
|
+
function expandTilde(p, homedir5) {
|
|
5606
|
+
if (p === "~") return homedir5;
|
|
5607
|
+
if (p.startsWith("~/")) return join14(homedir5, p.slice(2));
|
|
5557
5608
|
return p;
|
|
5558
5609
|
}
|
|
5559
|
-
function toAbsolute(p, workingDirAbs,
|
|
5560
|
-
const expanded = expandTilde(p,
|
|
5610
|
+
function toAbsolute(p, workingDirAbs, homedir5) {
|
|
5611
|
+
const expanded = expandTilde(p, homedir5);
|
|
5561
5612
|
if (isAbsolute(expanded)) return normalize(expanded);
|
|
5562
5613
|
return normalize(resolve2(workingDirAbs, expanded));
|
|
5563
5614
|
}
|
|
@@ -5570,18 +5621,18 @@ async function classifyFilesBySourceRoot(input) {
|
|
|
5570
5621
|
const inRoot = [];
|
|
5571
5622
|
const outOfRoot = [];
|
|
5572
5623
|
if (input.files.length === 0) return { inRoot, outOfRoot };
|
|
5573
|
-
const
|
|
5574
|
-
const workingDirAbs = toAbsolute(input.workingDirectory,
|
|
5624
|
+
const homedir5 = input.homedir ?? osHomedir();
|
|
5625
|
+
const workingDirAbs = toAbsolute(input.workingDirectory, homedir5, homedir5);
|
|
5575
5626
|
const declared = input.sourceRoots && input.sourceRoots.length > 0 ? [...input.sourceRoots] : ["."];
|
|
5576
5627
|
const rootsAbs = [];
|
|
5577
5628
|
for (const r of declared) {
|
|
5578
|
-
const expanded = expandTilde(r,
|
|
5629
|
+
const expanded = expandTilde(r, homedir5);
|
|
5579
5630
|
const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(input.masterRoot, expanded));
|
|
5580
5631
|
rootsAbs.push(await realpathBestEffort(abs));
|
|
5581
5632
|
}
|
|
5582
5633
|
for (const e of input.extraInRoot ?? []) {
|
|
5583
|
-
const expanded = expandTilde(e,
|
|
5584
|
-
const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(
|
|
5634
|
+
const expanded = expandTilde(e, homedir5);
|
|
5635
|
+
const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(homedir5, expanded));
|
|
5585
5636
|
rootsAbs.push(await realpathBestEffort(abs));
|
|
5586
5637
|
}
|
|
5587
5638
|
if (rootsAbs.length === 0) {
|
|
@@ -5589,7 +5640,7 @@ async function classifyFilesBySourceRoot(input) {
|
|
|
5589
5640
|
}
|
|
5590
5641
|
for (const file of input.files) {
|
|
5591
5642
|
try {
|
|
5592
|
-
const abs = toAbsolute(file, workingDirAbs,
|
|
5643
|
+
const abs = toAbsolute(file, workingDirAbs, homedir5);
|
|
5593
5644
|
const real = await realpathBestEffort(abs);
|
|
5594
5645
|
const within = rootsAbs.some((root) => isUnder(real, root));
|
|
5595
5646
|
(within ? inRoot : outOfRoot).push(file);
|
|
@@ -7496,8 +7547,421 @@ function formatInt(n) {
|
|
|
7496
7547
|
|
|
7497
7548
|
// src/review/review-gaps.ts
|
|
7498
7549
|
import { existsSync, realpathSync } from "fs";
|
|
7550
|
+
import { homedir as homedir3 } from "os";
|
|
7551
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join18 } from "path";
|
|
7552
|
+
|
|
7553
|
+
// src/review/command-workdir.ts
|
|
7499
7554
|
import { homedir as homedir2 } from "os";
|
|
7500
|
-
import { basename as basename3, isAbsolute as isAbsolute2,
|
|
7555
|
+
import { basename as basename3, isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
|
|
7556
|
+
function ambiguous(reason) {
|
|
7557
|
+
return { kind: "ambiguous", reason };
|
|
7558
|
+
}
|
|
7559
|
+
var SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "ksh", "mksh"]);
|
|
7560
|
+
var INDIRECT_EXECUTORS = /* @__PURE__ */ new Set([
|
|
7561
|
+
".",
|
|
7562
|
+
"alias",
|
|
7563
|
+
"bash",
|
|
7564
|
+
"builtin",
|
|
7565
|
+
"chroot",
|
|
7566
|
+
"command",
|
|
7567
|
+
"dash",
|
|
7568
|
+
"doas",
|
|
7569
|
+
"env",
|
|
7570
|
+
"eval",
|
|
7571
|
+
"exec",
|
|
7572
|
+
"ksh",
|
|
7573
|
+
"mksh",
|
|
7574
|
+
"nice",
|
|
7575
|
+
"nohup",
|
|
7576
|
+
"sh",
|
|
7577
|
+
"source",
|
|
7578
|
+
"ssh",
|
|
7579
|
+
"stdbuf",
|
|
7580
|
+
"su",
|
|
7581
|
+
"sudo",
|
|
7582
|
+
"timeout",
|
|
7583
|
+
"trap",
|
|
7584
|
+
"xargs",
|
|
7585
|
+
"zsh"
|
|
7586
|
+
]);
|
|
7587
|
+
var DIRECTORY_STACK = /* @__PURE__ */ new Set(["pushd", "popd"]);
|
|
7588
|
+
var SHELL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
7589
|
+
"!",
|
|
7590
|
+
"case",
|
|
7591
|
+
"coproc",
|
|
7592
|
+
"do",
|
|
7593
|
+
"done",
|
|
7594
|
+
"elif",
|
|
7595
|
+
"else",
|
|
7596
|
+
"esac",
|
|
7597
|
+
"fi",
|
|
7598
|
+
"for",
|
|
7599
|
+
"function",
|
|
7600
|
+
"if",
|
|
7601
|
+
"in",
|
|
7602
|
+
"select",
|
|
7603
|
+
"then",
|
|
7604
|
+
"time",
|
|
7605
|
+
"until",
|
|
7606
|
+
"while",
|
|
7607
|
+
"{",
|
|
7608
|
+
"}"
|
|
7609
|
+
]);
|
|
7610
|
+
var RELOCATING_VARIABLES = /* @__PURE__ */ new Set([
|
|
7611
|
+
"CDPATH",
|
|
7612
|
+
"GIT_COMMON_DIR",
|
|
7613
|
+
"GIT_DIR",
|
|
7614
|
+
"GIT_WORK_TREE",
|
|
7615
|
+
"HOME",
|
|
7616
|
+
"OLDPWD",
|
|
7617
|
+
"PWD"
|
|
7618
|
+
]);
|
|
7619
|
+
var CHDIR_OPTIONS = /* @__PURE__ */ new Map([
|
|
7620
|
+
["git", /* @__PURE__ */ new Set(["-C", "--git-dir", "--work-tree"])],
|
|
7621
|
+
["make", /* @__PURE__ */ new Set(["-C", "--directory"])],
|
|
7622
|
+
["gmake", /* @__PURE__ */ new Set(["-C", "--directory"])],
|
|
7623
|
+
["env", /* @__PURE__ */ new Set(["-C", "--chdir"])],
|
|
7624
|
+
["tar", /* @__PURE__ */ new Set(["-C", "--directory"])],
|
|
7625
|
+
["pnpm", /* @__PURE__ */ new Set(["-C", "--dir", "--workspace-root", "-w"])],
|
|
7626
|
+
["npm", /* @__PURE__ */ new Set(["-C", "--prefix"])],
|
|
7627
|
+
["yarn", /* @__PURE__ */ new Set(["--cwd"])],
|
|
7628
|
+
["just", /* @__PURE__ */ new Set(["-d", "--working-directory"])]
|
|
7629
|
+
]);
|
|
7630
|
+
function deriveCommandWorkdir(command, args, cwd) {
|
|
7631
|
+
const script = args.length === 2 && args[0] === "-c" ? args[1] : void 0;
|
|
7632
|
+
if (command === null) {
|
|
7633
|
+
if (script === void 0) return ambiguous("unsupported_invocation");
|
|
7634
|
+
return readShellProgram(script, cwd);
|
|
7635
|
+
}
|
|
7636
|
+
const program = basename3(command);
|
|
7637
|
+
if (SHELLS.has(program)) {
|
|
7638
|
+
if (script === void 0) return ambiguous("unsupported_invocation");
|
|
7639
|
+
return readShellProgram(script, cwd);
|
|
7640
|
+
}
|
|
7641
|
+
if (!isPlainCommandHead(command)) return ambiguous("unrecognized_command_word");
|
|
7642
|
+
if (INDIRECT_EXECUTORS.has(program)) return ambiguous("indirect_execution");
|
|
7643
|
+
const words = args.map((text) => ({
|
|
7644
|
+
text,
|
|
7645
|
+
mask: "n".repeat(text.length),
|
|
7646
|
+
quoteOpens: []
|
|
7647
|
+
}));
|
|
7648
|
+
if (hasChdirOption(program, words)) return ambiguous("chdir_option");
|
|
7649
|
+
return { kind: "cwd" };
|
|
7650
|
+
}
|
|
7651
|
+
function readShellProgram(script, cwd) {
|
|
7652
|
+
const scan = scanProgram(script);
|
|
7653
|
+
if (!scan.ok) return ambiguous(scan.reason);
|
|
7654
|
+
const cds = [];
|
|
7655
|
+
let rank = 0;
|
|
7656
|
+
for (const [index, segment] of scan.segments.entries()) {
|
|
7657
|
+
const stripped = stripRedirections(segment);
|
|
7658
|
+
if (!stripped.ok) return ambiguous(stripped.reason);
|
|
7659
|
+
const words = stripped.words;
|
|
7660
|
+
const start = firstNonAssignment(words);
|
|
7661
|
+
for (const word of words.slice(0, start ?? words.length)) {
|
|
7662
|
+
const name2 = assignmentName(word);
|
|
7663
|
+
if (name2 === void 0) continue;
|
|
7664
|
+
if (RELOCATING_VARIABLES.has(name2)) return ambiguous("shell_state_assignment");
|
|
7665
|
+
if (hasLiveDollar(word)) return ambiguous("unreadable_assignment_value");
|
|
7666
|
+
if (assignmentValueHasTildeUser(word, name2.length)) return ambiguous("tilde_user");
|
|
7667
|
+
}
|
|
7668
|
+
if (start === void 0) continue;
|
|
7669
|
+
const head = words[start];
|
|
7670
|
+
if (head === void 0) continue;
|
|
7671
|
+
if (looksLikeAssignmentPrefix(head)) return ambiguous("assignment_or_command");
|
|
7672
|
+
if (head.text === "cd") {
|
|
7673
|
+
cds.push({ index, rank, operands: words.slice(start + 1) });
|
|
7674
|
+
rank++;
|
|
7675
|
+
continue;
|
|
7676
|
+
}
|
|
7677
|
+
rank++;
|
|
7678
|
+
if (hasLiveDollar(head)) return ambiguous("unexpanded_variable");
|
|
7679
|
+
if (SHELL_KEYWORDS.has(head.text)) return ambiguous("compound_command");
|
|
7680
|
+
if (!isPlainCommandHead(head.text)) return ambiguous("unrecognized_command_word");
|
|
7681
|
+
const name = basename3(head.text);
|
|
7682
|
+
if (DIRECTORY_STACK.has(name)) return ambiguous("directory_stack");
|
|
7683
|
+
if (INDIRECT_EXECUTORS.has(name)) return ambiguous("indirect_execution");
|
|
7684
|
+
const operands = words.slice(start + 1);
|
|
7685
|
+
if (hasChdirOption(name, operands)) return ambiguous("chdir_option");
|
|
7686
|
+
if (CHDIR_OPTIONS.has(name) && operands.some((w) => hasUnquoted(w, /\$/))) {
|
|
7687
|
+
return ambiguous("unexpanded_variable");
|
|
7688
|
+
}
|
|
7689
|
+
}
|
|
7690
|
+
if (cds.length === 0) return { kind: "cwd" };
|
|
7691
|
+
if (cds.length > 1) return ambiguous("multiple_cd");
|
|
7692
|
+
const cd = cds[0];
|
|
7693
|
+
if (cd === void 0) return ambiguous("unsupported_cd_form");
|
|
7694
|
+
if (cd.rank !== 0) return ambiguous("cd_not_first");
|
|
7695
|
+
if (scan.separators[cd.index] === "|") return ambiguous("cd_in_pipeline");
|
|
7696
|
+
return readCdTarget(cd.operands, cwd);
|
|
7697
|
+
}
|
|
7698
|
+
function scanProgram(script) {
|
|
7699
|
+
const segments = [];
|
|
7700
|
+
const separators = [];
|
|
7701
|
+
let words = [];
|
|
7702
|
+
let text = "";
|
|
7703
|
+
let mask = "";
|
|
7704
|
+
let quoteOpens = [];
|
|
7705
|
+
let started = false;
|
|
7706
|
+
const endWord = () => {
|
|
7707
|
+
if (started) words.push({ text, mask, quoteOpens });
|
|
7708
|
+
text = "";
|
|
7709
|
+
mask = "";
|
|
7710
|
+
quoteOpens = [];
|
|
7711
|
+
started = false;
|
|
7712
|
+
};
|
|
7713
|
+
const endSegment = (separator) => {
|
|
7714
|
+
endWord();
|
|
7715
|
+
if (words.length === 0) return separator === "newline";
|
|
7716
|
+
segments.push(words);
|
|
7717
|
+
words = [];
|
|
7718
|
+
separators.push(separator);
|
|
7719
|
+
return true;
|
|
7720
|
+
};
|
|
7721
|
+
const runLength = (index, char) => {
|
|
7722
|
+
let n = 0;
|
|
7723
|
+
while (script[index + n] === char) n++;
|
|
7724
|
+
return n;
|
|
7725
|
+
};
|
|
7726
|
+
const pushRedirection = (op) => {
|
|
7727
|
+
if (started && /^\d+$/.test(text) && !/[sd]/.test(mask) && quoteOpens.length === 0) {
|
|
7728
|
+
text = "";
|
|
7729
|
+
mask = "";
|
|
7730
|
+
started = false;
|
|
7731
|
+
}
|
|
7732
|
+
endWord();
|
|
7733
|
+
words.push({ text: op, mask: "n".repeat(op.length), quoteOpens: [], redirection: true });
|
|
7734
|
+
};
|
|
7735
|
+
let i = 0;
|
|
7736
|
+
while (i < script.length) {
|
|
7737
|
+
const c = script[i];
|
|
7738
|
+
if (c === "'") {
|
|
7739
|
+
const close = script.indexOf("'", i + 1);
|
|
7740
|
+
if (close === -1) return { ok: false, reason: "unterminated_quote" };
|
|
7741
|
+
const body = script.slice(i + 1, close);
|
|
7742
|
+
quoteOpens.push(text.length);
|
|
7743
|
+
text += body;
|
|
7744
|
+
mask += "s".repeat(body.length);
|
|
7745
|
+
started = true;
|
|
7746
|
+
i = close + 1;
|
|
7747
|
+
continue;
|
|
7748
|
+
}
|
|
7749
|
+
if (c === '"') {
|
|
7750
|
+
let j = i + 1;
|
|
7751
|
+
let closed = false;
|
|
7752
|
+
quoteOpens.push(text.length);
|
|
7753
|
+
while (j < script.length) {
|
|
7754
|
+
const d = script[j];
|
|
7755
|
+
if (d === '"') {
|
|
7756
|
+
closed = true;
|
|
7757
|
+
break;
|
|
7758
|
+
}
|
|
7759
|
+
if (d === "\\") return { ok: false, reason: "backslash_escape" };
|
|
7760
|
+
if (d === "`") return { ok: false, reason: "backtick" };
|
|
7761
|
+
if (d === "$" && script[j + 1] === "(")
|
|
7762
|
+
return { ok: false, reason: "command_substitution" };
|
|
7763
|
+
text += d;
|
|
7764
|
+
mask += "d";
|
|
7765
|
+
j++;
|
|
7766
|
+
}
|
|
7767
|
+
if (!closed) return { ok: false, reason: "unterminated_quote" };
|
|
7768
|
+
started = true;
|
|
7769
|
+
i = j + 1;
|
|
7770
|
+
continue;
|
|
7771
|
+
}
|
|
7772
|
+
if (c === "\\") return { ok: false, reason: "backslash_escape" };
|
|
7773
|
+
if (c === "`") return { ok: false, reason: "backtick" };
|
|
7774
|
+
if (c === "$" && script[i + 1] === "(") return { ok: false, reason: "command_substitution" };
|
|
7775
|
+
if (c === "$" && (script[i + 1] === "'" || script[i + 1] === '"')) {
|
|
7776
|
+
return { ok: false, reason: "dollar_quote" };
|
|
7777
|
+
}
|
|
7778
|
+
if (c === "(" || c === ")") return { ok: false, reason: "subshell" };
|
|
7779
|
+
if (c === "#" && !started) return { ok: false, reason: "comment" };
|
|
7780
|
+
if (c === "\r") return { ok: false, reason: "carriage_return" };
|
|
7781
|
+
if (c === "<") {
|
|
7782
|
+
if (runLength(i, "<") >= 2) return { ok: false, reason: "heredoc" };
|
|
7783
|
+
const next = script[i + 1];
|
|
7784
|
+
const op = next === "&" || next === ">" ? `<${next}` : "<";
|
|
7785
|
+
pushRedirection(op);
|
|
7786
|
+
i += op.length;
|
|
7787
|
+
continue;
|
|
7788
|
+
}
|
|
7789
|
+
if (c === ">") {
|
|
7790
|
+
const next = script[i + 1];
|
|
7791
|
+
const op = next === ">" || next === "&" || next === "|" ? `>${next}` : ">";
|
|
7792
|
+
pushRedirection(op);
|
|
7793
|
+
i += op.length;
|
|
7794
|
+
continue;
|
|
7795
|
+
}
|
|
7796
|
+
if (c === "&") {
|
|
7797
|
+
const n = runLength(i, "&");
|
|
7798
|
+
if (n === 2) {
|
|
7799
|
+
if (!endSegment("&&")) return { ok: false, reason: "shell_syntax" };
|
|
7800
|
+
i += 2;
|
|
7801
|
+
continue;
|
|
7802
|
+
}
|
|
7803
|
+
return { ok: false, reason: "background" };
|
|
7804
|
+
}
|
|
7805
|
+
if (c === "|") {
|
|
7806
|
+
if (runLength(i, "|") >= 2) return { ok: false, reason: "or_operator" };
|
|
7807
|
+
if (!endSegment("|")) return { ok: false, reason: "shell_syntax" };
|
|
7808
|
+
i++;
|
|
7809
|
+
continue;
|
|
7810
|
+
}
|
|
7811
|
+
if (c === ";") {
|
|
7812
|
+
if (!endSegment(";")) return { ok: false, reason: "shell_syntax" };
|
|
7813
|
+
i++;
|
|
7814
|
+
continue;
|
|
7815
|
+
}
|
|
7816
|
+
if (c === "\n") {
|
|
7817
|
+
if (!endSegment("newline")) return { ok: false, reason: "shell_syntax" };
|
|
7818
|
+
i++;
|
|
7819
|
+
continue;
|
|
7820
|
+
}
|
|
7821
|
+
if (c === " " || c === " ") {
|
|
7822
|
+
endWord();
|
|
7823
|
+
i++;
|
|
7824
|
+
continue;
|
|
7825
|
+
}
|
|
7826
|
+
text += c;
|
|
7827
|
+
mask += "n";
|
|
7828
|
+
started = true;
|
|
7829
|
+
i++;
|
|
7830
|
+
}
|
|
7831
|
+
endWord();
|
|
7832
|
+
const last = separators.at(-1);
|
|
7833
|
+
if (words.length === 0 && (last === "&&" || last === "|")) {
|
|
7834
|
+
return { ok: false, reason: "shell_syntax" };
|
|
7835
|
+
}
|
|
7836
|
+
segments.push(words);
|
|
7837
|
+
return { ok: true, segments, separators };
|
|
7838
|
+
}
|
|
7839
|
+
function stripRedirections(words) {
|
|
7840
|
+
const out = [];
|
|
7841
|
+
for (let i = 0; i < words.length; i++) {
|
|
7842
|
+
const word = words[i];
|
|
7843
|
+
if (word === void 0) continue;
|
|
7844
|
+
if (word.redirection !== true) {
|
|
7845
|
+
out.push(word);
|
|
7846
|
+
continue;
|
|
7847
|
+
}
|
|
7848
|
+
const target = words[i + 1];
|
|
7849
|
+
if (target === void 0 || target.redirection === true) {
|
|
7850
|
+
return { ok: false, reason: "shell_syntax" };
|
|
7851
|
+
}
|
|
7852
|
+
if (!isReadableRedirectionTarget(word.text, target)) {
|
|
7853
|
+
return { ok: false, reason: "unreadable_redirection" };
|
|
7854
|
+
}
|
|
7855
|
+
i++;
|
|
7856
|
+
}
|
|
7857
|
+
return { ok: true, words: out };
|
|
7858
|
+
}
|
|
7859
|
+
var FD_DUP_OPERATORS = /* @__PURE__ */ new Set([">&", "<&"]);
|
|
7860
|
+
var FD_DUP_TARGET = /^\d+$/;
|
|
7861
|
+
function isReadableRedirectionTarget(operator, target) {
|
|
7862
|
+
if (FD_DUP_OPERATORS.has(operator)) return FD_DUP_TARGET.test(target.text);
|
|
7863
|
+
if (target.text.length === 0) return false;
|
|
7864
|
+
return !hasUnquoted(target, /[$*?[{]/);
|
|
7865
|
+
}
|
|
7866
|
+
var ASSIGNMENT_PREFIX = /^([A-Za-z_][A-Za-z0-9_]*)=/;
|
|
7867
|
+
var AMBIGUOUS_ASSIGNMENT_START = /^[A-Za-z_][A-Za-z0-9_]*(\[|\+=)/;
|
|
7868
|
+
var PLAIN_COMMAND_HEAD = /^(?:~\/|\/|\.{1,2}\/)?[A-Za-z0-9_.+-]+(?:\/[A-Za-z0-9_.+-]+)*$/;
|
|
7869
|
+
function isPlainCommandHead(text) {
|
|
7870
|
+
if (!PLAIN_COMMAND_HEAD.test(text)) return false;
|
|
7871
|
+
if (text === ".") return true;
|
|
7872
|
+
const last = text.slice(text.lastIndexOf("/") + 1);
|
|
7873
|
+
return last !== "." && last !== "..";
|
|
7874
|
+
}
|
|
7875
|
+
function assignmentName(word) {
|
|
7876
|
+
if (word.redirection === true) return void 0;
|
|
7877
|
+
const match = ASSIGNMENT_PREFIX.exec(word.text);
|
|
7878
|
+
if (match?.[1] === void 0) return void 0;
|
|
7879
|
+
const upToEquals = match[0].length - 1;
|
|
7880
|
+
if (quotedBefore(word, upToEquals)) return void 0;
|
|
7881
|
+
return match[1];
|
|
7882
|
+
}
|
|
7883
|
+
function looksLikeAssignmentPrefix(word) {
|
|
7884
|
+
if (word.redirection === true) return false;
|
|
7885
|
+
const match = AMBIGUOUS_ASSIGNMENT_START.exec(word.text);
|
|
7886
|
+
if (match === null) return false;
|
|
7887
|
+
return !quotedBefore(word, match[0].length - 1);
|
|
7888
|
+
}
|
|
7889
|
+
function assignmentValueHasTildeUser(word, nameLength) {
|
|
7890
|
+
const at = nameLength + 1;
|
|
7891
|
+
if (word.text[at] !== "~" || word.mask[at] !== "n") return false;
|
|
7892
|
+
const next = word.text[at + 1];
|
|
7893
|
+
return next !== void 0 && next !== "/";
|
|
7894
|
+
}
|
|
7895
|
+
function quotedBefore(word, index) {
|
|
7896
|
+
if (/[sd]/.test(word.mask.slice(0, index))) return true;
|
|
7897
|
+
return word.quoteOpens.some((at) => at <= index);
|
|
7898
|
+
}
|
|
7899
|
+
function firstNonAssignment(words) {
|
|
7900
|
+
for (const [index, word] of words.entries()) {
|
|
7901
|
+
if (assignmentName(word) !== void 0) continue;
|
|
7902
|
+
return index;
|
|
7903
|
+
}
|
|
7904
|
+
return void 0;
|
|
7905
|
+
}
|
|
7906
|
+
function hasChdirOption(program, operands) {
|
|
7907
|
+
const options = CHDIR_OPTIONS.get(program);
|
|
7908
|
+
if (options === void 0) return false;
|
|
7909
|
+
return operands.some((word) => {
|
|
7910
|
+
if (word.redirection === true) return false;
|
|
7911
|
+
if (options.has(word.text)) return true;
|
|
7912
|
+
const eq = word.text.indexOf("=");
|
|
7913
|
+
if (eq > 0 && options.has(word.text.slice(0, eq))) return true;
|
|
7914
|
+
if (word.text.startsWith("--") || !word.text.startsWith("-")) return false;
|
|
7915
|
+
for (const option of options) {
|
|
7916
|
+
if (option.length !== 2 || option.startsWith("--")) continue;
|
|
7917
|
+
if (word.text.includes(option.slice(1), 1)) return true;
|
|
7918
|
+
}
|
|
7919
|
+
return false;
|
|
7920
|
+
});
|
|
7921
|
+
}
|
|
7922
|
+
function hasUnquoted(word, pattern) {
|
|
7923
|
+
for (let i = 0; i < word.text.length; i++) {
|
|
7924
|
+
if (word.mask[i] === "n" && pattern.test(word.text[i])) return true;
|
|
7925
|
+
}
|
|
7926
|
+
return false;
|
|
7927
|
+
}
|
|
7928
|
+
function hasLiveDollar(word) {
|
|
7929
|
+
for (let i = 0; i < word.text.length; i++) {
|
|
7930
|
+
if (word.text[i] === "$" && word.mask[i] !== "s") return true;
|
|
7931
|
+
}
|
|
7932
|
+
return false;
|
|
7933
|
+
}
|
|
7934
|
+
function readCdTarget(operands, cwd) {
|
|
7935
|
+
let operand;
|
|
7936
|
+
let afterEndOfOptions = false;
|
|
7937
|
+
if (operands.length === 1) operand = operands[0];
|
|
7938
|
+
else if (operands.length === 2 && operands[0]?.text === "--") {
|
|
7939
|
+
operand = operands[1];
|
|
7940
|
+
afterEndOfOptions = true;
|
|
7941
|
+
}
|
|
7942
|
+
if (operand === void 0 || operand.text.length === 0) {
|
|
7943
|
+
return ambiguous("unsupported_cd_form");
|
|
7944
|
+
}
|
|
7945
|
+
if (!afterEndOfOptions && operand.text.startsWith("-")) {
|
|
7946
|
+
return ambiguous("unsupported_cd_form");
|
|
7947
|
+
}
|
|
7948
|
+
let target = operand.text;
|
|
7949
|
+
if (hasLiveDollar(operand)) return ambiguous("unexpanded_variable");
|
|
7950
|
+
if (hasUnquoted(operand, /[*?[{]/)) return ambiguous("glob");
|
|
7951
|
+
if (target.startsWith("~")) {
|
|
7952
|
+
if (operand.mask[0] !== "n") return ambiguous("quoted_tilde");
|
|
7953
|
+
if (target === "~") target = homedir2();
|
|
7954
|
+
else if (target.startsWith("~/")) target = homedir2() + target.slice(1);
|
|
7955
|
+
else return ambiguous("tilde_user");
|
|
7956
|
+
}
|
|
7957
|
+
if (!isAbsolute2(target)) {
|
|
7958
|
+
if (cwd === null || !isAbsolute2(cwd)) return ambiguous("unresolvable_relative");
|
|
7959
|
+
target = resolve3(cwd, target);
|
|
7960
|
+
}
|
|
7961
|
+
return { kind: "target", path: target };
|
|
7962
|
+
}
|
|
7963
|
+
|
|
7964
|
+
// src/review/review-gaps.ts
|
|
7501
7965
|
function stripQuotes(s) {
|
|
7502
7966
|
if (s.length >= 2 && (s[0] === '"' && s.at(-1) === '"' || s[0] === "'" && s.at(-1) === "'")) {
|
|
7503
7967
|
return s.slice(1, -1);
|
|
@@ -7529,8 +7993,8 @@ function normalizeRepoPath(p) {
|
|
|
7529
7993
|
if (!p) return null;
|
|
7530
7994
|
let s = stripQuotes(p.trim()).replace(/\/+$/, "");
|
|
7531
7995
|
if (s.length === 0 || s === "~") return null;
|
|
7532
|
-
if (s.startsWith("~/")) s =
|
|
7533
|
-
if (
|
|
7996
|
+
if (s.startsWith("~/")) s = homedir3() + s.slice(1);
|
|
7997
|
+
if (isAbsolute3(s)) {
|
|
7534
7998
|
const real = resolveRealpath(s);
|
|
7535
7999
|
if (real !== null) {
|
|
7536
8000
|
return isRepoRoot(real) ? real : null;
|
|
@@ -7539,7 +8003,7 @@ function normalizeRepoPath(p) {
|
|
|
7539
8003
|
s = s.replace(/\/[^/]*-workspace\/([^/]+)/, "/$1");
|
|
7540
8004
|
const seg = s.split("/").filter((x) => x.length > 0).pop();
|
|
7541
8005
|
if (seg === void 0) return null;
|
|
7542
|
-
if (/-workspace$/.test(seg) ||
|
|
8006
|
+
if (/-workspace$/.test(seg) || s.includes("$")) return null;
|
|
7543
8007
|
return s;
|
|
7544
8008
|
}
|
|
7545
8009
|
function recordRepoKey(p) {
|
|
@@ -7550,8 +8014,8 @@ function resolveRepoRoot(p) {
|
|
|
7550
8014
|
}
|
|
7551
8015
|
function classifyRepoPath(p) {
|
|
7552
8016
|
let s = stripQuotes((p ?? "").trim()).replace(/\/+$/, "");
|
|
7553
|
-
if (s.startsWith("~/")) s =
|
|
7554
|
-
if (s.length === 0 || !
|
|
8017
|
+
if (s.startsWith("~/")) s = homedir3() + s.slice(1);
|
|
8018
|
+
if (s.length === 0 || !isAbsolute3(s)) return { resolved: null, problem: "relative" };
|
|
7555
8019
|
const real = resolveRealpath(s);
|
|
7556
8020
|
if (real === null) return { resolved: null, problem: "absent" };
|
|
7557
8021
|
if (!isRepoRoot(real)) return { resolved: null, problem: "not_a_repo_root" };
|
|
@@ -7567,7 +8031,7 @@ function findUnbindableRepos(repos) {
|
|
|
7567
8031
|
}
|
|
7568
8032
|
function normalizeRepoKey(p) {
|
|
7569
8033
|
const full = normalizeRepoPath(p);
|
|
7570
|
-
return full === null ? null :
|
|
8034
|
+
return full === null ? null : basename4(full);
|
|
7571
8035
|
}
|
|
7572
8036
|
function inspectCommand(args) {
|
|
7573
8037
|
const a = args.join(" ");
|
|
@@ -7581,30 +8045,33 @@ function inspectCommand(args) {
|
|
|
7581
8045
|
let m;
|
|
7582
8046
|
while ((m = re.exec(a)) !== null) {
|
|
7583
8047
|
const f = m[1];
|
|
7584
|
-
if (f !== void 0) files.add(
|
|
8048
|
+
if (f !== void 0) files.add(basename4(f));
|
|
7585
8049
|
}
|
|
7586
8050
|
}
|
|
7587
8051
|
return { files: [...files], examinedDiff };
|
|
7588
8052
|
}
|
|
7589
|
-
function commandRepoWithProvenance(args, cwd) {
|
|
7590
|
-
const raw = commandRepoPath(args, cwd);
|
|
8053
|
+
function commandRepoWithProvenance(command, args, cwd) {
|
|
8054
|
+
const raw = commandRepoPath(command, args, cwd);
|
|
8055
|
+
if (raw === null) return { key: null, resolved: false };
|
|
7591
8056
|
return { key: normalizeRepoPath(raw), resolved: resolveRepoRoot(raw) !== null };
|
|
7592
8057
|
}
|
|
7593
|
-
function commandRepoPath(args, cwd) {
|
|
7594
|
-
const
|
|
7595
|
-
|
|
8058
|
+
function commandRepoPath(command, args, cwd) {
|
|
8059
|
+
const workdir = deriveCommandWorkdir(command, args, cwd);
|
|
8060
|
+
if (workdir.kind === "ambiguous") return null;
|
|
8061
|
+
return workdir.kind === "target" ? workdir.path : cwd;
|
|
7596
8062
|
}
|
|
7597
|
-
function commandRepo(args, cwd) {
|
|
7598
|
-
return normalizeRepoPath(commandRepoPath(args, cwd));
|
|
8063
|
+
function commandRepo(command, args, cwd) {
|
|
8064
|
+
return normalizeRepoPath(commandRepoPath(command, args, cwd));
|
|
7599
8065
|
}
|
|
7600
|
-
function
|
|
7601
|
-
|
|
8066
|
+
function commandOutcome(exitCode) {
|
|
8067
|
+
if (exitCode === null) return "unknown";
|
|
8068
|
+
return exitCode === 0 ? "succeeded" : "failed";
|
|
7602
8069
|
}
|
|
7603
8070
|
function commitFiles(args) {
|
|
7604
8071
|
const a = args.join(" ");
|
|
7605
8072
|
const add = a.match(/git add\s+([^&|;]+)/);
|
|
7606
8073
|
if (!add?.[1]) return [];
|
|
7607
|
-
return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) =>
|
|
8074
|
+
return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) => basename4(t));
|
|
7608
8075
|
}
|
|
7609
8076
|
var REVIEW_SOURCE = "codex-import";
|
|
7610
8077
|
var DEFAULT_WINDOW_HOURS = 24;
|
|
@@ -7654,10 +8121,11 @@ async function findReviewGaps(input) {
|
|
|
7654
8121
|
continue;
|
|
7655
8122
|
}
|
|
7656
8123
|
if (ev.type !== "command_executed") continue;
|
|
7657
|
-
|
|
8124
|
+
const outcome = commandOutcome(ev.exit_code);
|
|
8125
|
+
if (outcome === "failed") continue;
|
|
7658
8126
|
const at = Date.parse(ev.occurred_at);
|
|
7659
8127
|
if (isReview) {
|
|
7660
|
-
const repo2 = commandRepo(ev.args, ev.cwd);
|
|
8128
|
+
const repo2 = commandRepo(ev.command, ev.args, ev.cwd);
|
|
7661
8129
|
if (repo2 === null) continue;
|
|
7662
8130
|
const ins = inspectCommand(ev.args);
|
|
7663
8131
|
const slot = reviewRepos.get(repo2) ?? { examinedDiff: false, files: /* @__PURE__ */ new Set() };
|
|
@@ -7668,7 +8136,11 @@ async function findReviewGaps(input) {
|
|
|
7668
8136
|
continue;
|
|
7669
8137
|
}
|
|
7670
8138
|
if (!ev.args.join(" ").includes("git commit")) continue;
|
|
7671
|
-
const { key: repo, resolved: keyResolved } = commandRepoWithProvenance(
|
|
8139
|
+
const { key: repo, resolved: keyResolved } = commandRepoWithProvenance(
|
|
8140
|
+
ev.command,
|
|
8141
|
+
ev.args,
|
|
8142
|
+
ev.cwd
|
|
8143
|
+
);
|
|
7672
8144
|
if (repo === null || Number.isNaN(at)) {
|
|
7673
8145
|
const list2 = unknownCommits.get(entry.sessionId) ?? [];
|
|
7674
8146
|
list2.push(Number.isNaN(at) ? null : at);
|
|
@@ -7677,7 +8149,13 @@ async function findReviewGaps(input) {
|
|
|
7677
8149
|
}
|
|
7678
8150
|
const byRepo = workUnits.get(entry.sessionId) ?? /* @__PURE__ */ new Map();
|
|
7679
8151
|
const list = byRepo.get(repo) ?? [];
|
|
7680
|
-
list.push({
|
|
8152
|
+
list.push({
|
|
8153
|
+
repo,
|
|
8154
|
+
at,
|
|
8155
|
+
files: commitFiles(ev.args),
|
|
8156
|
+
keyResolved,
|
|
8157
|
+
outcomeObserved: outcome === "succeeded"
|
|
8158
|
+
});
|
|
7681
8159
|
byRepo.set(repo, list);
|
|
7682
8160
|
workUnits.set(entry.sessionId, byRepo);
|
|
7683
8161
|
}
|
|
@@ -7697,7 +8175,7 @@ async function findReviewGaps(input) {
|
|
|
7697
8175
|
let refusedPairings = 0;
|
|
7698
8176
|
for (const [sessionId, byRepo] of workUnits) {
|
|
7699
8177
|
for (const [repoPath, commits] of byRepo) {
|
|
7700
|
-
const label =
|
|
8178
|
+
const label = basename4(repoPath);
|
|
7701
8179
|
const times = commits.map((c) => c.at).sort((a, b) => a - b);
|
|
7702
8180
|
const first = times[0] ?? null;
|
|
7703
8181
|
const last = times[times.length - 1] ?? null;
|
|
@@ -7739,6 +8217,7 @@ async function findReviewGaps(input) {
|
|
|
7739
8217
|
// Attached after the verdict is computed, and deliberately not an input
|
|
7740
8218
|
// to it: a record must never move a unit out of `gaps`.
|
|
7741
8219
|
selfReports: selfBound.map((r) => toSelfReportedReview(r, r.at > earliest)),
|
|
8220
|
+
commitsWithUnobservedOutcome: commits.filter((c) => !c.outcomeObserved).length,
|
|
7742
8221
|
reviews: cited.map((r) => ({
|
|
7743
8222
|
sessionId: r.sessionId,
|
|
7744
8223
|
examinedDiff: r.repos.get(repoPath)?.examinedDiff ?? false,
|
|
@@ -7764,7 +8243,10 @@ async function findReviewGaps(input) {
|
|
|
7764
8243
|
verdict: "unknown",
|
|
7765
8244
|
reviews: [],
|
|
7766
8245
|
// No repo key, so nothing a record's `repos` could bind to.
|
|
7767
|
-
selfReports: []
|
|
8246
|
+
selfReports: [],
|
|
8247
|
+
// These commits were never placed in a repository, so the per-commit
|
|
8248
|
+
// outcome is not tracked for them; the unit is already a caveat.
|
|
8249
|
+
commitsWithUnobservedOutcome: 0
|
|
7768
8250
|
});
|
|
7769
8251
|
}
|
|
7770
8252
|
const missed = selfReports.filter((r) => !attachedSelfReports.has(r.eventId));
|
|
@@ -8069,7 +8551,7 @@ var ChildProcessRunner = class {
|
|
|
8069
8551
|
if (killTimer !== null) clearTimeout(killTimer);
|
|
8070
8552
|
options.signal?.removeEventListener("abort", onAbort);
|
|
8071
8553
|
};
|
|
8072
|
-
return new Promise((
|
|
8554
|
+
return new Promise((resolve4, reject) => {
|
|
8073
8555
|
child.once("error", (error) => {
|
|
8074
8556
|
if (settled) return;
|
|
8075
8557
|
settled = true;
|
|
@@ -8081,7 +8563,7 @@ var ChildProcessRunner = class {
|
|
|
8081
8563
|
settled = true;
|
|
8082
8564
|
cleanup();
|
|
8083
8565
|
const ended_at = /* @__PURE__ */ new Date();
|
|
8084
|
-
|
|
8566
|
+
resolve4({
|
|
8085
8567
|
command: snapshotCommand,
|
|
8086
8568
|
args: snapshotArgs,
|
|
8087
8569
|
cwd: snapshotCwd,
|
|
@@ -8520,7 +9002,7 @@ function hasErrorCode6(error) {
|
|
|
8520
9002
|
|
|
8521
9003
|
// src/storage/session-import.ts
|
|
8522
9004
|
import { mkdir as mkdir5, readFile as readFile10, rm as rm2 } from "fs/promises";
|
|
8523
|
-
import { homedir as
|
|
9005
|
+
import { homedir as homedir4 } from "os";
|
|
8524
9006
|
import { join as join21 } from "path";
|
|
8525
9007
|
async function importSessionFromJson(paths, manifest, payload, options) {
|
|
8526
9008
|
if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
|
|
@@ -8629,7 +9111,7 @@ function withIntegrity(record, chainResult) {
|
|
|
8629
9111
|
};
|
|
8630
9112
|
}
|
|
8631
9113
|
function buildSessionRecord(input, manifest, newSessionId, options) {
|
|
8632
|
-
const home =
|
|
9114
|
+
const home = homedir4();
|
|
8633
9115
|
const workingDirectoryRaw = input.working_directory;
|
|
8634
9116
|
const workingDirectorySanitized = sanitizeWorkingDirectory(workingDirectoryRaw, {
|
|
8635
9117
|
homedir: home
|
|
@@ -8673,7 +9155,7 @@ function derivedEventContentKey(event) {
|
|
|
8673
9155
|
const base = `${event.type}\0${event.occurred_at}`;
|
|
8674
9156
|
switch (event.type) {
|
|
8675
9157
|
case "command_executed":
|
|
8676
|
-
return `${base}
|
|
9158
|
+
return `${base} ${event.args.join("")} ${event.cwd}`;
|
|
8677
9159
|
case "file_changed":
|
|
8678
9160
|
return `${base}\0${event.path}\0${event.change_type}`;
|
|
8679
9161
|
case "decision_recorded":
|