immune-brain 3.6.9 → 4.0.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/package.json +3 -2
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +18 -2
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +76 -121
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +106 -600
- package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +1 -0
- package/plugins/immune-brain/.pi-extension/pi-canary-verification.ts +3 -3
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +17 -43
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +7587 -5049
- package/plugins/immune-brain/dist/docs/reference/planning-artifact-retention.md +11 -12
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-loop.md +27 -25
- package/plugins/immune-brain/dist/imm-planner.md +34 -27
- package/plugins/immune-brain/dist/imm-review-retro.md +2 -2
- package/plugins/immune-brain/dist/role-prompts/code-review.md +3 -1
- package/plugins/immune-brain/dist/role-prompts/executor.md +4 -4
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +183 -40
- package/plugins/immune-brain/runtime/assurance/delivery_workspace.ts +240 -0
- package/plugins/immune-brain/runtime/assurance/qa.ts +132 -58
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +15 -7
- package/plugins/immune-brain/runtime/assurance/verification.ts +246 -206
- package/plugins/immune-brain/runtime/authorization_operation.ts +20 -0
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +288 -721
- package/plugins/immune-brain/runtime/commands/kernel.ts +158 -67
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +1 -1
- package/plugins/immune-brain/runtime/kernel/actor_identity.ts +33 -0
- package/plugins/immune-brain/runtime/kernel/application.ts +22 -6
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +94 -5
- package/plugins/immune-brain/runtime/kernel/authority_port.ts +27 -6
- package/plugins/immune-brain/runtime/kernel/backend_claim.ts +43 -16
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +10 -6
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +50 -63
- package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +13 -4
- package/plugins/immune-brain/runtime/kernel/completion.ts +5 -14
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +124 -34
- package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +13 -5
- package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
- package/plugins/immune-brain/runtime/kernel/intent.ts +7 -11
- package/plugins/immune-brain/runtime/kernel/legacy_audit.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/legacy_task_record.ts +323 -0
- package/plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts +10 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +32 -31
- package/plugins/immune-brain/runtime/kernel/run_identity.ts +121 -0
- package/plugins/immune-brain/runtime/kernel/spec_binding.ts +100 -0
- package/plugins/immune-brain/runtime/kernel/sqlite_migration.ts +950 -0
- package/plugins/immune-brain/runtime/kernel/sqlite_store.ts +1193 -0
- package/plugins/immune-brain/runtime/kernel/storage.ts +1254 -1206
- package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +129 -755
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +419 -46
- package/plugins/immune-brain/runtime/kernel/types.ts +12 -43
- package/plugins/immune-brain/runtime/kernel/validation.ts +60 -274
- package/plugins/immune-brain/runtime/managed_task_routing_policy.ts +0 -1
- package/plugins/immune-brain/runtime/plan_core.ts +27 -65
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +3 -1
- package/plugins/immune-brain/runtime/prompts/executor.md +4 -4
- package/plugins/immune-brain/runtime/staged_intent.ts +58 -0
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +37 -7
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +42 -2
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +771 -0
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +189 -0
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +35 -0
- package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +33 -0
- package/plugins/immune-brain/runtime/unattended/types.ts +14 -1
- package/plugins/immune-brain/runtime/v4_runtime.ts +19 -23
- package/plugins/immune-brain/runtime/verification_descriptor.ts +92 -136
- package/plugins/immune-brain/runtime/workspace_scope.ts +98 -13
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +3 -3
- package/plugins/immune-brain/bin/imm-retire-stale-wrapper +0 -4
- package/plugins/immune-brain/bin/imm-retired +0 -4
- package/plugins/immune-brain/runtime/authority_commit_receipts.ts +0 -716
- package/plugins/immune-brain/runtime/kernel/automatic_observations.ts +0 -451
- package/plugins/immune-brain/runtime/kernel/legacy.ts +0 -299
- package/plugins/immune-brain/runtime/kernel/observation.ts +0 -397
- package/plugins/immune-brain/runtime/kernel/readiness.ts +0 -282
- package/plugins/immune-brain/runtime/kernel/readiness_evidence.ts +0 -132
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { snapshotDigest, type SnapshotDescriptor, type AssuranceVerdict } from "./coordinator";
|
|
2
|
-
import {
|
|
3
|
+
import { assertDeliveryClean, materializeDeliveryWorkspace, removeTaskOwnedTree, writeDeliveryTree } from "./delivery_workspace";
|
|
4
|
+
import { runFixedVerification, resolveVerificationCommand, assertCommandIdentity, verificationPath, VerificationAbortedError, VerificationCleanupError, VerificationLaunchError, type FrozenCommand, type VerificationDescriptor, type VerificationCommand } from "./verification";
|
|
5
|
+
import { mkdtempSync, realpathSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join, sep } from "node:path";
|
|
3
8
|
import { qaFindingId } from "./qa_findings";
|
|
9
|
+
import { captureGitTaskRevisionSnapshot } from "../workspace_scope";
|
|
10
|
+
import { readTaskRecord } from "../kernel/storage";
|
|
4
11
|
|
|
5
12
|
export interface QaVerificationProgressInput {
|
|
6
13
|
index: number;
|
|
@@ -10,76 +17,143 @@ export interface QaVerificationProgressInput {
|
|
|
10
17
|
elapsed_ms: number;
|
|
11
18
|
}
|
|
12
19
|
|
|
20
|
+
function deliveryTreeForSnapshot(
|
|
21
|
+
snapshot: SnapshotDescriptor,
|
|
22
|
+
writeTree: typeof writeDeliveryTree,
|
|
23
|
+
): string {
|
|
24
|
+
if (snapshot.review_revision?.review_tree) return snapshot.review_revision.review_tree;
|
|
25
|
+
const record = readTaskRecord(snapshot.root, snapshot.task_id).record;
|
|
26
|
+
if (!record || record.contract !== "assurance_kernel/task_record/v4" || !record.git_base_head)
|
|
27
|
+
throw new Error("QA delivery requires a TaskRecord v4 git_base_head");
|
|
28
|
+
const captured = captureGitTaskRevisionSnapshot(
|
|
29
|
+
snapshot.root,
|
|
30
|
+
record.intent_snapshot.scope_hint,
|
|
31
|
+
record.git_base_head,
|
|
32
|
+
snapshot.task_id,
|
|
33
|
+
);
|
|
34
|
+
const digest = `sha256:${createHash("sha256").update(JSON.stringify(captured)).digest("hex")}`;
|
|
35
|
+
if (digest !== snapshot.diff_hash)
|
|
36
|
+
throw new Error("QA delivery identity does not match the frozen snapshot");
|
|
37
|
+
return writeDeliveryTree(snapshot.root, captured);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class QaPreparationError extends Error {
|
|
41
|
+
constructor(public readonly stage: "resolution" | "prepare" | "integrity", public readonly acceptance_ids: string[], public readonly reason: string) {
|
|
42
|
+
super(`QA ${stage} failed (${reason}); affected checks=${acceptance_ids.join(",")}`);
|
|
43
|
+
this.name = "QaPreparationError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
13
47
|
export async function runDeterministicQa(
|
|
14
48
|
snapshot: SnapshotDescriptor,
|
|
15
49
|
descriptors: Map<string, VerificationDescriptor>,
|
|
16
|
-
runner: FrozenRunner,
|
|
17
50
|
options: {
|
|
18
51
|
signal?: AbortSignal;
|
|
19
52
|
onProgress?: (progress: QaVerificationProgressInput) => void;
|
|
20
|
-
|
|
53
|
+
// Coordination tests inject a fake executor without creating a real delivery.
|
|
54
|
+
_runFixedVerification?: typeof runFixedVerification;
|
|
55
|
+
_resolveVerificationCommand?: typeof resolveVerificationCommand;
|
|
56
|
+
_materializeDeliveryWorkspace?: typeof materializeDeliveryWorkspace;
|
|
57
|
+
_writeDeliveryTree?: typeof writeDeliveryTree;
|
|
21
58
|
} = {},
|
|
22
59
|
): Promise<AssuranceVerdict> {
|
|
23
60
|
if (snapshot.role !== "qa") throw new Error("deterministic QA requires qa role");
|
|
24
|
-
if (options.signal?.aborted) throw new VerificationAbortedError();
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
for (const [
|
|
28
|
-
if (options.signal?.aborted) throw new VerificationAbortedError();
|
|
61
|
+
const live = () => { if (options.signal?.aborted) throw new VerificationAbortedError(); };
|
|
62
|
+
live();
|
|
63
|
+
const groups = new Map<string, Array<{ id: string; index: number; descriptor: VerificationDescriptor }>>();
|
|
64
|
+
for (const [index, item] of snapshot.acceptance.entries()) {
|
|
29
65
|
const descriptor = descriptors.get(item.id);
|
|
30
66
|
if (!descriptor) throw new Error(`verification descriptor missing for ${item.id}`);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
acceptance_id: item.id,
|
|
36
|
-
phase: "running",
|
|
37
|
-
elapsed_ms: 0,
|
|
38
|
-
});
|
|
39
|
-
const result = await runVerification(snapshot.root, descriptor, runner, {
|
|
40
|
-
signal: options.signal,
|
|
41
|
-
});
|
|
42
|
-
if (options.signal?.aborted) throw new VerificationAbortedError();
|
|
43
|
-
const failed = result.exit_code !== 0 || result.timed_out;
|
|
44
|
-
options.onProgress?.({
|
|
45
|
-
index: offset + 1,
|
|
46
|
-
total: snapshot.acceptance.length,
|
|
47
|
-
acceptance_id: item.id,
|
|
48
|
-
phase: failed ? "failed" : "passed",
|
|
49
|
-
elapsed_ms: Date.now() - startedAt,
|
|
50
|
-
});
|
|
51
|
-
if (failed) {
|
|
52
|
-
// Findings become durable authority records; never include verifier output.
|
|
53
|
-
findings.push({
|
|
54
|
-
id: qaFindingId(item.id, snapshotDigest(snapshot)),
|
|
55
|
-
kind: "blocking",
|
|
56
|
-
acceptance_id: item.id,
|
|
57
|
-
summary: `verification failed (exit ${result.exit_code}${result.timed_out ? ", timed out" : ""}) stdout=${Buffer.byteLength(result.stdout)}B stderr=${Buffer.byteLength(result.stderr)}B`,
|
|
58
|
-
findings_digest: "",
|
|
59
|
-
});
|
|
60
|
-
}
|
|
67
|
+
const key = JSON.stringify(descriptor.environment);
|
|
68
|
+
const group = groups.get(key) ?? [];
|
|
69
|
+
group.push({ id: item.id, index, descriptor });
|
|
70
|
+
groups.set(key, group);
|
|
61
71
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
const tree = snapshot.review_revision?.review_tree
|
|
73
|
+
?? (options._writeDeliveryTree ? "tree" : deliveryTreeForSnapshot(snapshot, writeDeliveryTree));
|
|
74
|
+
const path = verificationPath();
|
|
75
|
+
const findings: NonNullable<AssuranceVerdict["findings"]> = [];
|
|
76
|
+
const evidence: unknown[] = [];
|
|
77
|
+
const allCommands: FrozenCommand[] = [];
|
|
78
|
+
for (const [environmentKey, group] of groups) {
|
|
79
|
+
live();
|
|
80
|
+
const ids = group.map(item => item.id);
|
|
81
|
+
const environment = group[0]!.descriptor.environment;
|
|
82
|
+
const delivery = (options._materializeDeliveryWorkspace ?? materializeDeliveryWorkspace)(snapshot.root, tree);
|
|
83
|
+
const home = mkdtempSync(join(tmpdir(), "imm-qa-home-"));
|
|
84
|
+
const clean = () => {
|
|
85
|
+
if (options._materializeDeliveryWorkspace) return;
|
|
86
|
+
try { assertDeliveryClean(delivery.root, tree, delivery.seal, environment.writable_paths); }
|
|
87
|
+
catch { throw new QaPreparationError("integrity", ids, "protected_input_or_output_drift"); }
|
|
70
88
|
};
|
|
89
|
+
const groupCommands: FrozenCommand[] = [];
|
|
90
|
+
const deliveryPrefix = `${realpathSync(delivery.root)}${sep}`;
|
|
91
|
+
const execute = async (command: VerificationCommand, stage: "prepare" | "check") => {
|
|
92
|
+
live(); clean();
|
|
93
|
+
let frozen: FrozenCommand;
|
|
94
|
+
try { frozen = (options._resolveVerificationCommand ?? resolveVerificationCommand)(delivery.root, command, path); }
|
|
95
|
+
catch { throw new QaPreparationError("resolution", ids, "executable_or_cwd_unavailable"); }
|
|
96
|
+
let result;
|
|
97
|
+
try { result = await (options._runFixedVerification ?? runFixedVerification)(delivery.root, command, frozen, { signal: options.signal, home, path }); }
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error instanceof VerificationLaunchError) throw new QaPreparationError("resolution", ids, "process_launch_failed");
|
|
100
|
+
if (error instanceof VerificationCleanupError) throw new QaPreparationError("resolution", ids, "process_cleanup_failed");
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
live(); clean();
|
|
104
|
+
groupCommands.push(frozen);
|
|
105
|
+
if (frozen?.entry) {
|
|
106
|
+
if (!command.executable.startsWith("./")) allCommands.push(frozen);
|
|
107
|
+
else if (frozen.interpreter && !frozen.interpreter.path.startsWith(deliveryPrefix))
|
|
108
|
+
allCommands.push({ entry: frozen.interpreter, interpreter: null, interpreter_args: [] });
|
|
109
|
+
}
|
|
110
|
+
// Paths and process output are not persisted. Identity hashes describe
|
|
111
|
+
// observed entries, not a claim about every transitive dependency.
|
|
112
|
+
evidence.push({ stage, environment: createHash("sha256").update(environmentKey).digest("hex"),
|
|
113
|
+
command: createHash("sha256").update(JSON.stringify(command)).digest("hex"),
|
|
114
|
+
entry: frozen?.entry?.content_hash ?? null, interpreter: frozen?.interpreter?.content_hash ?? null,
|
|
115
|
+
exit_code: result.exit_code, timed_out: result.timed_out, output_limited: result.output_limited });
|
|
116
|
+
if (Buffer.byteLength(JSON.stringify(evidence)) > 16_384)
|
|
117
|
+
throw new QaPreparationError("prepare", ids, "execution_metadata_limit_exceeded");
|
|
118
|
+
return result;
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
clean();
|
|
122
|
+
if (environment.prepare) {
|
|
123
|
+
const result = await execute(environment.prepare, "prepare");
|
|
124
|
+
if (result.exit_code !== 0 || result.timed_out || result.output_limited)
|
|
125
|
+
throw new QaPreparationError("prepare", ids, result.timed_out ? "timeout" : result.output_limited ? "output_limit" : "nonzero_exit");
|
|
126
|
+
}
|
|
127
|
+
for (const item of group) {
|
|
128
|
+
const started = Date.now();
|
|
129
|
+
const progress = (phase: QaVerificationProgressInput["phase"]) => options.onProgress?.({
|
|
130
|
+
index: item.index + 1, total: snapshot.acceptance.length, acceptance_id: item.id, phase, elapsed_ms: Date.now() - started,
|
|
131
|
+
});
|
|
132
|
+
progress("running");
|
|
133
|
+
const result = await execute(item.descriptor.command, "check");
|
|
134
|
+
const failed = result.exit_code !== 0 || result.timed_out || result.output_limited;
|
|
135
|
+
progress(failed ? "failed" : "passed");
|
|
136
|
+
if (failed) findings.push({ id: qaFindingId(item.id, snapshotDigest(snapshot)), kind: "blocking", acceptance_id: item.id,
|
|
137
|
+
summary: `verification failed (exit ${result.exit_code}${result.timed_out ? ", timed out" : ""}${result.output_limited ? ", output limit" : ""}) stdout=${Buffer.byteLength(result.stdout)}B stderr=${Buffer.byteLength(result.stderr)}B`, findings_digest: "" });
|
|
138
|
+
}
|
|
139
|
+
// Recheck while project entries still exist; cleanup removes them.
|
|
140
|
+
for (const frozen of groupCommands) {
|
|
141
|
+
if (frozen?.entry) assertCommandIdentity(frozen);
|
|
142
|
+
}
|
|
143
|
+
} finally { try { delivery.cleanup(); } finally { removeTaskOwnedTree(home); } }
|
|
144
|
+
}
|
|
145
|
+
live();
|
|
146
|
+
// Host entries survive materialization cleanup and must still match before approval.
|
|
147
|
+
for (const frozen of allCommands) {
|
|
148
|
+
if (frozen?.entry) assertCommandIdentity(frozen);
|
|
71
149
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
authority_role: "qa",
|
|
81
|
-
summary: `all ${snapshot.acceptance.length} fixed verification descriptor(s) passed`,
|
|
82
|
-
},
|
|
83
|
-
};
|
|
150
|
+
const base = { contract: "assurance_kernel/assurance_verdict/v2" as const, role: "qa" as const,
|
|
151
|
+
task_id: snapshot.task_id, snapshot_digest: snapshotDigest(snapshot) };
|
|
152
|
+
if (findings.length) return { ...base, decision: "rework", findings };
|
|
153
|
+
const executionDigest = createHash("sha256").update(JSON.stringify({
|
|
154
|
+
tree, platform: process.platform, arch: process.arch, observations: evidence,
|
|
155
|
+
})).digest("hex");
|
|
156
|
+
return { ...base, decision: "pass", approval: { kind: "qa", authority_role: "qa",
|
|
157
|
+
summary: `all ${snapshot.acceptance.length} project verification check(s) passed; execution=sha256:${executionDigest}` } };
|
|
84
158
|
}
|
|
85
159
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type GitTaskIndexEntry,
|
|
22
22
|
type GitTaskRevisionSnapshot,
|
|
23
23
|
} from "../workspace_scope";
|
|
24
|
+
import { currentRunId } from "../kernel/storage";
|
|
24
25
|
|
|
25
26
|
const MAX_REVIEW_BUNDLE_BYTES = 2 * 1024 * 1024;
|
|
26
27
|
|
|
@@ -346,10 +347,16 @@ function taskIdFromReviewRefSegment(segment: string): string {
|
|
|
346
347
|
return taskId;
|
|
347
348
|
}
|
|
348
349
|
|
|
349
|
-
function
|
|
350
|
+
function workspaceRefSegment(root: string): string {
|
|
351
|
+
return createHash("sha256").update(realpathSync(root)).digest("hex").slice(0, 16);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function reviewRef(root: string, taskId: string, reviewCommit: string): string {
|
|
350
355
|
const taskSegment = reviewRefTaskSegment(taskId);
|
|
351
356
|
if (!GIT_COMMIT_ID.test(reviewCommit)) throw new Error("review commit has invalid identity");
|
|
352
|
-
|
|
357
|
+
const runId = currentRunId(root, taskId) ?? "none";
|
|
358
|
+
if (!REVIEW_TASK_ID.test(runId) && runId !== "none") throw new Error("review run id has invalid identity");
|
|
359
|
+
return `${REVIEW_REF_NAMESPACE}/${workspaceRefSegment(root)}/${runId}/${taskSegment}/${reviewCommit}`;
|
|
353
360
|
}
|
|
354
361
|
|
|
355
362
|
/** The scoped delta of one captured revision, as base->tree paths. */
|
|
@@ -406,7 +413,7 @@ export function publishReviewRevision(
|
|
|
406
413
|
// Determinism proof: the synthetic commit's tree-to-tree delta against the
|
|
407
414
|
// Enrollment base must be exactly the captured scoped delta, nothing more.
|
|
408
415
|
if (!GIT_COMMIT_ID.test(reviewCommit)) throw new Error("review synthetic commit write failed");
|
|
409
|
-
const ref = reviewRef(taskId, reviewCommit);
|
|
416
|
+
const ref = reviewRef(root, taskId, reviewCommit);
|
|
410
417
|
const expected = revisionDelta(snapshot);
|
|
411
418
|
const actual = decodeNullPaths(
|
|
412
419
|
gitEvidenceBytes(root, ["diff", "--no-renames", "--name-only", "-z", snapshot.base_head, reviewCommit]),
|
|
@@ -452,7 +459,7 @@ function publishInput(
|
|
|
452
459
|
throw new Error("review requires a TaskRecord v4 git_base_head");
|
|
453
460
|
if (!REVISION_DIFF_HASH.test(input.expectedDiffHash))
|
|
454
461
|
throw new Error("review task revision hash has invalid identity");
|
|
455
|
-
const snapshot = captureGitTaskRevisionSnapshot(root, input.scopeHint, input.baseHead);
|
|
462
|
+
const snapshot = captureGitTaskRevisionSnapshot(root, input.scopeHint, input.baseHead, input.taskId);
|
|
456
463
|
const recomputed = `sha256:${createHash("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
|
|
457
464
|
if (recomputed !== input.expectedDiffHash)
|
|
458
465
|
throw new Error("review task revision does not match assurance snapshot");
|
|
@@ -505,14 +512,15 @@ export function captureReviewManifest(
|
|
|
505
512
|
}
|
|
506
513
|
|
|
507
514
|
export function listReviewRefs(root: string): Array<{ ref: string; commit: string; taskId: string }> {
|
|
508
|
-
const output = gitEvidence(root, ["for-each-ref", "--format=%(refname) %(objectname)", `${REVIEW_REF_NAMESPACE}/`]);
|
|
515
|
+
const output = gitEvidence(root, ["for-each-ref", "--format=%(refname) %(objectname)", `${REVIEW_REF_NAMESPACE}/${workspaceRefSegment(root)}/`]);
|
|
509
516
|
const refs: Array<{ ref: string; commit: string; taskId: string }> = [];
|
|
510
517
|
for (const line of output.split("\n").filter(Boolean)) {
|
|
511
518
|
const [ref, commit] = line.split(" ");
|
|
512
|
-
const
|
|
519
|
+
const parts = ref?.slice(REVIEW_REF_NAMESPACE.length + 1).split("/") ?? [];
|
|
520
|
+
const segment = parts.length >= 4 ? parts[parts.length - 2] : parts[0] ?? "";
|
|
513
521
|
let taskId: string;
|
|
514
522
|
try {
|
|
515
|
-
taskId = taskIdFromReviewRefSegment(segment);
|
|
523
|
+
taskId = taskIdFromReviewRefSegment(segment ?? "");
|
|
516
524
|
} catch {
|
|
517
525
|
continue;
|
|
518
526
|
}
|