@basou/core 0.37.0 → 0.39.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 +35 -24
- package/dist/index.js +134 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/event.schema.json +16 -2
- package/schemas/manifest.schema.json +20 -0
- package/schemas/session-import.schema.json +16 -2
package/dist/index.d.ts
CHANGED
|
@@ -107,21 +107,12 @@ declare function removeStopHook(settings: unknown): StopHookRemoval;
|
|
|
107
107
|
declare function findBasouStopHookCommand(settings: unknown): string | null;
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* Every object here is `looseObject` (NOT the default strip), so unknown keys
|
|
117
|
-
* at every level survive parse. The manifest is the declarative source of truth
|
|
118
|
-
* and is git-tracked and read-modify-written by `basou project` commands; with
|
|
119
|
-
* the default strip, a field this basou does not recognize — a newer version's
|
|
120
|
-
* additive field, a future adapter under `adapters`, a hand-added key — would be
|
|
121
|
-
* silently dropped on the next write. Preserving them keeps basou from destroying
|
|
122
|
-
* config it does not understand (forward-compatible), while known fields are still
|
|
123
|
-
* fully type-checked and validated. {@link unknownManifestKeys} surfaces the
|
|
124
|
-
* unrecognized top-level keys so preservation is not silent.
|
|
110
|
+
* The manifest, plus one guard the loose object cannot express: a TOP-LEVEL
|
|
111
|
+
* `confidential` is refused. It belongs under `policies`, and because the
|
|
112
|
+
* object is loose, a stray top-level spelling would otherwise parse fine and be
|
|
113
|
+
* silently ignored — an operator would believe the workspace is protected while
|
|
114
|
+
* the face is still written. A safety key that is not honoured must fail
|
|
115
|
+
* loudly, not quietly.
|
|
125
116
|
*/
|
|
126
117
|
declare const ManifestSchema: z.ZodObject<{
|
|
127
118
|
schema_version: z.ZodString;
|
|
@@ -197,6 +188,12 @@ declare const ManifestSchema: z.ZodObject<{
|
|
|
197
188
|
self: "self";
|
|
198
189
|
}>>;
|
|
199
190
|
}, z.core.$loose>>>;
|
|
191
|
+
channels: z.ZodOptional<z.ZodObject<{
|
|
192
|
+
codex: z.ZodOptional<z.ZodBoolean>;
|
|
193
|
+
}, z.core.$loose>>;
|
|
194
|
+
policies: z.ZodOptional<z.ZodObject<{
|
|
195
|
+
confidential: z.ZodOptional<z.ZodBoolean>;
|
|
196
|
+
}, z.core.$loose>>;
|
|
200
197
|
}, z.core.$loose>;
|
|
201
198
|
/** Inferred runtime type for {@link ManifestSchema}. */
|
|
202
199
|
type Manifest = z.infer<typeof ManifestSchema>;
|
|
@@ -422,9 +419,9 @@ declare const SessionImportPayloadSchema: z.ZodObject<{
|
|
|
422
419
|
source: z.ZodString;
|
|
423
420
|
prev_hash: z.ZodOptional<z.ZodString>;
|
|
424
421
|
type: z.ZodLiteral<"command_executed">;
|
|
425
|
-
command: z.ZodString
|
|
422
|
+
command: z.ZodNullable<z.ZodString>;
|
|
426
423
|
args: z.ZodArray<z.ZodString>;
|
|
427
|
-
cwd: z.ZodString
|
|
424
|
+
cwd: z.ZodNullable<z.ZodString>;
|
|
428
425
|
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
429
426
|
signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
430
427
|
received_signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -669,8 +666,10 @@ type ClaudeTranscriptToPayloadOptions = {
|
|
|
669
666
|
* than mapping one-to-one:
|
|
670
667
|
*
|
|
671
668
|
* - `session_started` / `session_ended` from the first / last timestamped record.
|
|
672
|
-
* - `command_executed` from each `Bash` tool use, recorded as `
|
|
673
|
-
*
|
|
669
|
+
* - `command_executed` from each `Bash` tool use, recorded as `["-c", <cmd>]`
|
|
670
|
+
* with a NULL executor and a NULL outcome: the transcript carries the shell
|
|
671
|
+
* line and nothing about what ran it or how it ended (#191). Only `<cmd>`
|
|
672
|
+
* was observed; the `-c` is basou's framing of "this is a shell program".
|
|
674
673
|
* - `file_changed` from each `Edit` / `Write` / `NotebookEdit` tool use.
|
|
675
674
|
* - `decision_recorded` from each `AskUserQuestion` tool use, but ONLY when the
|
|
676
675
|
* recorded answer is a confirmed SELECTION — it exactly matches an option the
|
|
@@ -860,7 +859,10 @@ type CodexRolloutToPayloadOptions = {
|
|
|
860
859
|
* provenance-level events from the rollout's message-level records:
|
|
861
860
|
*
|
|
862
861
|
* - `session_started` / `session_ended` from the first / last timestamped record.
|
|
863
|
-
* - `command_executed` from each shell execution, recorded as `
|
|
862
|
+
* - `command_executed` from each shell execution, recorded as `["-c", <cmd>]`
|
|
863
|
+
* with a NULL executor: the rollout carries the shell line, never what ran
|
|
864
|
+
* it, and codex in fact runs through `/bin/zsh -lc` (#191). The `-c` is
|
|
865
|
+
* basou's framing of "this is a shell program"; only `<cmd>` was observed.
|
|
864
866
|
* Codex has written these two different ways, and BOTH are read (an operator's
|
|
865
867
|
* `~/.codex/sessions` holds a mix across a CLI upgrade):
|
|
866
868
|
* - one `function_call` named `exec_command` per command, with JSON
|
|
@@ -1153,9 +1155,9 @@ declare const CommandExecutedEventSchema: z.ZodObject<{
|
|
|
1153
1155
|
source: z.ZodString;
|
|
1154
1156
|
prev_hash: z.ZodOptional<z.ZodString>;
|
|
1155
1157
|
type: z.ZodLiteral<"command_executed">;
|
|
1156
|
-
command: z.ZodString
|
|
1158
|
+
command: z.ZodNullable<z.ZodString>;
|
|
1157
1159
|
args: z.ZodArray<z.ZodString>;
|
|
1158
|
-
cwd: z.ZodString
|
|
1160
|
+
cwd: z.ZodNullable<z.ZodString>;
|
|
1159
1161
|
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
1160
1162
|
signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1161
1163
|
received_signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -1463,9 +1465,9 @@ declare const EventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1463
1465
|
source: z.ZodString;
|
|
1464
1466
|
prev_hash: z.ZodOptional<z.ZodString>;
|
|
1465
1467
|
type: z.ZodLiteral<"command_executed">;
|
|
1466
|
-
command: z.ZodString
|
|
1468
|
+
command: z.ZodNullable<z.ZodString>;
|
|
1467
1469
|
args: z.ZodArray<z.ZodString>;
|
|
1468
|
-
cwd: z.ZodString
|
|
1470
|
+
cwd: z.ZodNullable<z.ZodString>;
|
|
1469
1471
|
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
1470
1472
|
signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1471
1473
|
received_signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -6051,6 +6053,15 @@ type ReviewGapUnit = {
|
|
|
6051
6053
|
* a self-reported gap is still a gap.
|
|
6052
6054
|
*/
|
|
6053
6055
|
selfReports: SelfReportedReview[];
|
|
6056
|
+
/**
|
|
6057
|
+
* How many of this unit's commits ran with no recorded exit code, so that the
|
|
6058
|
+
* commit landing is assumed rather than observed.
|
|
6059
|
+
*
|
|
6060
|
+
* A caveat, never an input to `verdict`: lowering the count would mean an
|
|
6061
|
+
* unverifiable commit could quietly leave the report, which is the same hole
|
|
6062
|
+
* a self-report must not open. `commitCount` still counts every commit.
|
|
6063
|
+
*/
|
|
6064
|
+
commitsWithUnobservedOutcome: number;
|
|
6054
6065
|
};
|
|
6055
6066
|
/** Recorded reviews that reached no unit of work, broken down by cause. */
|
|
6056
6067
|
type UnattachedSelfReports = {
|
package/dist/index.js
CHANGED
|
@@ -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(),
|
|
@@ -1733,7 +1782,35 @@ var WorkspaceMetaSchema = z4.looseObject({
|
|
|
1733
1782
|
*/
|
|
1734
1783
|
view: SourceRootSchema.optional()
|
|
1735
1784
|
});
|
|
1736
|
-
var
|
|
1785
|
+
var ChannelsSchema = z4.looseObject({
|
|
1786
|
+
/**
|
|
1787
|
+
* Render this workspace's orientation into `~/.codex/AGENTS.md` on `basou
|
|
1788
|
+
* refresh` / `basou run codex`. A boolean today; if a face ever needs
|
|
1789
|
+
* per-block control this widens to `boolean | object`, which existing
|
|
1790
|
+
* manifests keep parsing — so the boolean does not foreclose that.
|
|
1791
|
+
*/
|
|
1792
|
+
codex: z4.boolean().optional().meta({
|
|
1793
|
+
description: "Opt in to rendering this workspace's orientation into the user-global ~/.codex/AGENTS.md on `basou refresh` and `basou run codex`. Off when absent: that file is auto-loaded by Codex for every project on the machine, so nothing is written there unless the workspace says so."
|
|
1794
|
+
})
|
|
1795
|
+
});
|
|
1796
|
+
var PoliciesSchema = z4.looseObject({
|
|
1797
|
+
/**
|
|
1798
|
+
* This workspace's provenance must not persist where another workspace's
|
|
1799
|
+
* tool reads it. That is the GOAL the key states; what it gates today is one
|
|
1800
|
+
* thing: rendering the orientation into the user-global `~/.codex/AGENTS.md`
|
|
1801
|
+
* (`basou refresh` / `basou run codex`) — never, regardless of `channels`, so
|
|
1802
|
+
* a confidential workspace cannot be re-enabled by a second declaration in
|
|
1803
|
+
* the same file. It does not yet govern `basou protocol sync` (a global
|
|
1804
|
+
* render, not a per-workspace one), and it gates writing only: it cannot keep
|
|
1805
|
+
* another workspace's block out of this workspace's tool (see `basou channel
|
|
1806
|
+
* clear`). Stated as a goal rather than as "never write" so that a transient
|
|
1807
|
+
* render removed before anyone else can read it remains permissible.
|
|
1808
|
+
*/
|
|
1809
|
+
confidential: z4.boolean().optional().meta({
|
|
1810
|
+
description: "This workspace's provenance must not persist where another workspace's tool reads it. Today this means its orientation is never rendered into the user-global ~/.codex/AGENTS.md by `basou refresh` or `basou run codex`, regardless of `channels`. It does not yet govern `basou protocol sync`, and it gates writing only \u2014 it cannot keep another workspace's block out of this workspace's tool."
|
|
1811
|
+
})
|
|
1812
|
+
});
|
|
1813
|
+
var ManifestObjectSchema = z4.looseObject({
|
|
1737
1814
|
schema_version: SchemaVersionSchema,
|
|
1738
1815
|
// Same forward-compatible format gate as schema_version (accept 0.x.y, gate a
|
|
1739
1816
|
// higher major with an upgrade error) rather than a hard literal. `basou_version`
|
|
@@ -1747,9 +1824,20 @@ var ManifestSchema = z4.looseObject({
|
|
|
1747
1824
|
adapters: AdaptersSchema,
|
|
1748
1825
|
git: GitConfigSchema,
|
|
1749
1826
|
import: ImportConfigSchema.optional(),
|
|
1750
|
-
repos: z4.array(RepoEntrySchema).min(1).optional()
|
|
1827
|
+
repos: z4.array(RepoEntrySchema).min(1).optional(),
|
|
1828
|
+
channels: ChannelsSchema.optional(),
|
|
1829
|
+
policies: PoliciesSchema.optional()
|
|
1751
1830
|
});
|
|
1752
|
-
var
|
|
1831
|
+
var ManifestSchema = ManifestObjectSchema.superRefine((manifest, ctx) => {
|
|
1832
|
+
if ("confidential" in manifest) {
|
|
1833
|
+
ctx.addIssue({
|
|
1834
|
+
code: "custom",
|
|
1835
|
+
path: ["confidential"],
|
|
1836
|
+
message: "`confidential` is declared under `policies` (policies.confidential: true). A top-level `confidential` is not honoured, so it is rejected rather than ignored."
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1840
|
+
var KNOWN_TOP_LEVEL_KEYS = new Set(Object.keys(ManifestObjectSchema.shape));
|
|
1753
1841
|
function unknownManifestKeys(manifest) {
|
|
1754
1842
|
return Object.keys(manifest).filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k)).sort();
|
|
1755
1843
|
}
|
|
@@ -2484,8 +2572,10 @@ var SessionSourceSchema = z5.looseObject({
|
|
|
2484
2572
|
var InvocationSchema = z5.looseObject({
|
|
2485
2573
|
command: z5.string().min(1),
|
|
2486
2574
|
args: z5.array(z5.string()).default([]),
|
|
2487
|
-
// Nullable
|
|
2488
|
-
// code
|
|
2575
|
+
// Nullable because the outcome may be UNKNOWN: a signal-terminated run has no
|
|
2576
|
+
// exit code, and an imported session's source may never record one. Unknown
|
|
2577
|
+
// is not success. The same nullability and the same meaning are mirrored in
|
|
2578
|
+
// CommandExecutedEventSchema.
|
|
2489
2579
|
exit_code: z5.number().int().nullable()
|
|
2490
2580
|
});
|
|
2491
2581
|
var SessionMetricsSchema = z5.looseObject({
|
|
@@ -7577,9 +7667,13 @@ var CHDIR_OPTIONS = /* @__PURE__ */ new Map([
|
|
|
7577
7667
|
["just", /* @__PURE__ */ new Set(["-d", "--working-directory"])]
|
|
7578
7668
|
]);
|
|
7579
7669
|
function deriveCommandWorkdir(command, args, cwd) {
|
|
7670
|
+
const script = args.length === 2 && args[0] === "-c" ? args[1] : void 0;
|
|
7671
|
+
if (command === null) {
|
|
7672
|
+
if (script === void 0) return ambiguous("unsupported_invocation");
|
|
7673
|
+
return readShellProgram(script, cwd);
|
|
7674
|
+
}
|
|
7580
7675
|
const program = basename3(command);
|
|
7581
7676
|
if (SHELLS.has(program)) {
|
|
7582
|
-
const script = args.length === 2 && args[0] === "-c" ? args[1] : void 0;
|
|
7583
7677
|
if (script === void 0) return ambiguous("unsupported_invocation");
|
|
7584
7678
|
return readShellProgram(script, cwd);
|
|
7585
7679
|
}
|
|
@@ -7900,7 +7994,7 @@ function readCdTarget(operands, cwd) {
|
|
|
7900
7994
|
else return ambiguous("tilde_user");
|
|
7901
7995
|
}
|
|
7902
7996
|
if (!isAbsolute2(target)) {
|
|
7903
|
-
if (!isAbsolute2(cwd)) return ambiguous("unresolvable_relative");
|
|
7997
|
+
if (cwd === null || !isAbsolute2(cwd)) return ambiguous("unresolvable_relative");
|
|
7904
7998
|
target = resolve3(cwd, target);
|
|
7905
7999
|
}
|
|
7906
8000
|
return { kind: "target", path: target };
|
|
@@ -8008,8 +8102,9 @@ function commandRepoPath(command, args, cwd) {
|
|
|
8008
8102
|
function commandRepo(command, args, cwd) {
|
|
8009
8103
|
return normalizeRepoPath(commandRepoPath(command, args, cwd));
|
|
8010
8104
|
}
|
|
8011
|
-
function
|
|
8012
|
-
|
|
8105
|
+
function commandOutcome(exitCode) {
|
|
8106
|
+
if (exitCode === null) return "unknown";
|
|
8107
|
+
return exitCode === 0 ? "succeeded" : "failed";
|
|
8013
8108
|
}
|
|
8014
8109
|
function commitFiles(args) {
|
|
8015
8110
|
const a = args.join(" ");
|
|
@@ -8065,7 +8160,8 @@ async function findReviewGaps(input) {
|
|
|
8065
8160
|
continue;
|
|
8066
8161
|
}
|
|
8067
8162
|
if (ev.type !== "command_executed") continue;
|
|
8068
|
-
|
|
8163
|
+
const outcome = commandOutcome(ev.exit_code);
|
|
8164
|
+
if (outcome === "failed") continue;
|
|
8069
8165
|
const at = Date.parse(ev.occurred_at);
|
|
8070
8166
|
if (isReview) {
|
|
8071
8167
|
const repo2 = commandRepo(ev.command, ev.args, ev.cwd);
|
|
@@ -8092,7 +8188,13 @@ async function findReviewGaps(input) {
|
|
|
8092
8188
|
}
|
|
8093
8189
|
const byRepo = workUnits.get(entry.sessionId) ?? /* @__PURE__ */ new Map();
|
|
8094
8190
|
const list = byRepo.get(repo) ?? [];
|
|
8095
|
-
list.push({
|
|
8191
|
+
list.push({
|
|
8192
|
+
repo,
|
|
8193
|
+
at,
|
|
8194
|
+
files: commitFiles(ev.args),
|
|
8195
|
+
keyResolved,
|
|
8196
|
+
outcomeObserved: outcome === "succeeded"
|
|
8197
|
+
});
|
|
8096
8198
|
byRepo.set(repo, list);
|
|
8097
8199
|
workUnits.set(entry.sessionId, byRepo);
|
|
8098
8200
|
}
|
|
@@ -8154,6 +8256,7 @@ async function findReviewGaps(input) {
|
|
|
8154
8256
|
// Attached after the verdict is computed, and deliberately not an input
|
|
8155
8257
|
// to it: a record must never move a unit out of `gaps`.
|
|
8156
8258
|
selfReports: selfBound.map((r) => toSelfReportedReview(r, r.at > earliest)),
|
|
8259
|
+
commitsWithUnobservedOutcome: commits.filter((c) => !c.outcomeObserved).length,
|
|
8157
8260
|
reviews: cited.map((r) => ({
|
|
8158
8261
|
sessionId: r.sessionId,
|
|
8159
8262
|
examinedDiff: r.repos.get(repoPath)?.examinedDiff ?? false,
|
|
@@ -8179,7 +8282,10 @@ async function findReviewGaps(input) {
|
|
|
8179
8282
|
verdict: "unknown",
|
|
8180
8283
|
reviews: [],
|
|
8181
8284
|
// No repo key, so nothing a record's `repos` could bind to.
|
|
8182
|
-
selfReports: []
|
|
8285
|
+
selfReports: [],
|
|
8286
|
+
// These commits were never placed in a repository, so the per-commit
|
|
8287
|
+
// outcome is not tracked for them; the unit is already a caveat.
|
|
8288
|
+
commitsWithUnobservedOutcome: 0
|
|
8183
8289
|
});
|
|
8184
8290
|
}
|
|
8185
8291
|
const missed = selfReports.filter((r) => !attachedSelfReports.has(r.eventId));
|
|
@@ -9088,7 +9194,7 @@ function derivedEventContentKey(event) {
|
|
|
9088
9194
|
const base = `${event.type}\0${event.occurred_at}`;
|
|
9089
9195
|
switch (event.type) {
|
|
9090
9196
|
case "command_executed":
|
|
9091
|
-
return `${base}
|
|
9197
|
+
return `${base} ${event.args.join("")} ${event.cwd}`;
|
|
9092
9198
|
case "file_changed":
|
|
9093
9199
|
return `${base}\0${event.path}\0${event.change_type}`;
|
|
9094
9200
|
case "decision_recorded":
|