sibyl-system 1.2.2 → 1.3.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.
Files changed (4) hide show
  1. package/README.md +32 -2
  2. package/dist/cli.js +356 -187
  3. package/dist/index.js +568 -174
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4904,9 +4904,9 @@ function cloneIssues(issues) {
4904
4904
  }
4905
4905
  var recursive = /* @__PURE__ */ new WeakMap();
4906
4906
  function isRecursive(inst, stack) {
4907
- const cached3 = recursive.get(inst);
4908
- if (cached3 !== void 0)
4909
- return cached3;
4907
+ const cached4 = recursive.get(inst);
4908
+ if (cached4 !== void 0)
4909
+ return cached4;
4910
4910
  if (stack.has(inst))
4911
4911
  return true;
4912
4912
  stack.add(inst);
@@ -18662,15 +18662,15 @@ function visit(schema, fnOrHandlers) {
18662
18662
  };
18663
18663
  const cache = /* @__PURE__ */ new Map();
18664
18664
  function run2(s) {
18665
- const cached3 = cache.get(s);
18666
- if (cached3 === RESOLVING) {
18665
+ const cached4 = cache.get(s);
18666
+ if (cached4 === RESOLVING) {
18667
18667
  return new $ZodLazy({
18668
18668
  type: "lazy",
18669
18669
  getter: () => cache.get(s)
18670
18670
  });
18671
18671
  }
18672
- if (cached3 !== void 0)
18673
- return cached3;
18672
+ if (cached4 !== void 0)
18673
+ return cached4;
18674
18674
  cache.set(s, RESOLVING);
18675
18675
  const inner = mapInner(s);
18676
18676
  const mapped = fn(inner, inner !== s);
@@ -18986,7 +18986,36 @@ import { dirname, join as join2 } from "node:path";
18986
18986
  var SIBYL_STATE_FILE_ENV = "SIBYL_STATE_FILE";
18987
18987
  var RUN_KINDS = ["consult", "swarm"];
18988
18988
  var RUN_STATUSES = ["running", "done", "failed", "suspended"];
18989
+ var INDEPENDENCE_STATUSES = ["INDEPENDENT", "NOT-INDEPENDENT", "UNVERIFIABLE"];
18990
+ function isIndependenceShape(v) {
18991
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
18992
+ const o = v;
18993
+ if (!INDEPENDENCE_STATUSES.includes(o["status"])) return false;
18994
+ if (!Array.isArray(o["convenerChain"]) || !o["convenerChain"].every((s) => typeof s === "string" && s.length > 0)) return false;
18995
+ return typeof o["evidence"] === "string";
18996
+ }
18997
+ function rebuildIndependence(v) {
18998
+ return { status: v.status, convenerChain: [...v.convenerChain], evidence: v.evidence };
18999
+ }
18989
19000
  var VERDICT_TAGS = ["APPROVE", "REJECT", "CANNOT_ANSWER"];
19001
+ var INSTRUMENT_NAME = /^[a-z0-9-]{1,40}$/;
19002
+ var HEX64 = /^[0-9a-f]{64}$/;
19003
+ function isInstrumentShape(v) {
19004
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
19005
+ const o = v;
19006
+ if (typeof o["rulesHash"] !== "string" || !HEX64.test(o["rulesHash"])) return false;
19007
+ const comps = o["components"];
19008
+ if (typeof comps !== "object" || comps === null || Array.isArray(comps)) return false;
19009
+ return Object.entries(comps).every(
19010
+ ([name, hash3]) => INSTRUMENT_NAME.test(name) && typeof hash3 === "string" && HEX64.test(hash3)
19011
+ );
19012
+ }
19013
+ function rebuildInstrument(v) {
19014
+ return {
19015
+ rulesHash: v.rulesHash,
19016
+ components: Object.fromEntries(Object.entries(v.components).map(([name, hash3]) => [name, hash3]))
19017
+ };
19018
+ }
18990
19019
  function oneOf(v, opts) {
18991
19020
  return opts.some((o) => o === v);
18992
19021
  }
@@ -19062,6 +19091,20 @@ function validateEntry(raw) {
19062
19091
  if (typeof notes !== "string") return { ok: false, reason: "notes must be a string when present" };
19063
19092
  record3.notes = notes;
19064
19093
  }
19094
+ const instrument = field(raw, "instrument");
19095
+ if (instrument !== void 0) {
19096
+ if (!isInstrumentShape(instrument)) {
19097
+ return { ok: false, reason: "instrument must be {rulesHash: 64-hex, components: name->64-hex} when present" };
19098
+ }
19099
+ record3.instrument = rebuildInstrument(instrument);
19100
+ }
19101
+ const independence = field(raw, "independence");
19102
+ if (independence !== void 0) {
19103
+ if (!isIndependenceShape(independence)) {
19104
+ return { ok: false, reason: "independence must be {status: INDEPENDENT|NOT-INDEPENDENT|UNVERIFIABLE, convenerChain: string[], evidence: string} when present" };
19105
+ }
19106
+ record3.independence = rebuildIndependence(independence);
19107
+ }
19065
19108
  return { ok: true, record: record3 };
19066
19109
  }
19067
19110
  function byCreatedAt(a, b) {
@@ -19190,8 +19233,8 @@ var RunStore = class {
19190
19233
  };
19191
19234
 
19192
19235
  // src/tools/consult.ts
19193
- import { join as join3 } from "node:path";
19194
- import { writeFile as writeFile2 } from "node:fs/promises";
19236
+ import { join as join5 } from "node:path";
19237
+ import { appendFile, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
19195
19238
 
19196
19239
  // node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
19197
19240
  var external_exports2 = {};
@@ -31895,6 +31938,123 @@ async function runPersona(opts) {
31895
31938
  }
31896
31939
  }
31897
31940
 
31941
+ // src/independence.ts
31942
+ var MAX_CHAIN_DEPTH = 16;
31943
+ var MIN_INLINE_MATCH = 64;
31944
+ var DRAFTING_TOOLS = ["write", "edit", "multiedit", "patch", "apply_patch"];
31945
+ var PATH_KEYS = ["filePath", "path"];
31946
+ var CONTENT_KEYS = ["content", "text", "oldString", "newString"];
31947
+ function isDraftingTool(name) {
31948
+ return name !== void 0 && DRAFTING_TOOLS.includes(name);
31949
+ }
31950
+ function pathTargets(input2, artifactPath) {
31951
+ if (input2 === void 0) return false;
31952
+ for (const key of PATH_KEYS) {
31953
+ const v = input2[key];
31954
+ if (typeof v === "string" && v.trim() === artifactPath) return true;
31955
+ }
31956
+ return false;
31957
+ }
31958
+ function contentMatches(input2, needle) {
31959
+ if (input2 === void 0) return false;
31960
+ for (const key of CONTENT_KEYS) {
31961
+ const v = input2[key];
31962
+ if (typeof v !== "string") continue;
31963
+ if (v.trim() === needle) return true;
31964
+ if (needle.length >= MIN_INLINE_MATCH && v.includes(needle)) return true;
31965
+ if (needle.length >= MIN_INLINE_MATCH && v.length >= MIN_INLINE_MATCH && needle.includes(v.trim()) && v.trim().length > 0) {
31966
+ return true;
31967
+ }
31968
+ }
31969
+ return false;
31970
+ }
31971
+ function scanMessages(rows, convenerIndex, sid, artifact) {
31972
+ for (const [mi, m] of rows.entries()) {
31973
+ for (const p of m.parts ?? []) {
31974
+ if (p.type === "tool" && isDraftingTool(p.tool)) {
31975
+ if (artifact.kind === "path" && pathTargets(p.input, artifact.source)) {
31976
+ return `drafting evidence: ${p.tool} on ${artifact.source} in convener-chain session ${sid} (chain position ${String(convenerIndex)}, message ${String(mi)})`;
31977
+ }
31978
+ if (artifact.kind === "inline" && contentMatches(p.input, artifact.text.trim())) {
31979
+ return `drafting evidence: ${p.tool} content matches the inline artifact in convener-chain session ${sid} (chain position ${String(convenerIndex)}, message ${String(mi)})`;
31980
+ }
31981
+ }
31982
+ if (artifact.kind === "inline" && m.info?.role === "assistant" && p.type === "text" && typeof p.text === "string" && artifact.text.trim().length >= MIN_INLINE_MATCH && p.text.includes(artifact.text.trim())) {
31983
+ return `drafting evidence: assistant text in convener-chain session ${sid} (chain position ${String(convenerIndex)}, message ${String(mi)}) contains the inline artifact`;
31984
+ }
31985
+ }
31986
+ }
31987
+ return null;
31988
+ }
31989
+ async function assessIndependence(client, directory, convenerSessionID, artifact) {
31990
+ const noChain = { status: "UNVERIFIABLE", convenerChain: [], evidence: "" };
31991
+ if (convenerSessionID.length === 0) {
31992
+ return { ...noChain, evidence: "headless invocation: no calling session to resolve the convener chain against" };
31993
+ }
31994
+ if (!artifact.ok) {
31995
+ return { ...noChain, evidence: "artifact unreadable at assessment time" };
31996
+ }
31997
+ if (client.session.get === void 0) {
31998
+ return { ...noChain, evidence: "engine client exposes no session.get seam (chain cannot be walked; not a verdict of independence)" };
31999
+ }
32000
+ if (client.session.messages === void 0) {
32001
+ return { ...noChain, convenerChain: [convenerSessionID], evidence: "engine client exposes no session.messages seam (drafting scan impossible)" };
32002
+ }
32003
+ const chain = [convenerSessionID];
32004
+ const seen = new Set(chain);
32005
+ let walkIncomplete = false;
32006
+ let walkReason = "";
32007
+ let current = convenerSessionID;
32008
+ for (let depth = 0; depth < MAX_CHAIN_DEPTH; depth += 1) {
32009
+ const res = await client.session.get({ path: { id: current } });
32010
+ if (res.error !== void 0 || res.data === void 0) {
32011
+ walkIncomplete = true;
32012
+ walkReason = `session.get(${current}) refused (${res.error === void 0 ? "no data" : JSON.stringify(res.error).slice(0, 120)})`;
32013
+ break;
32014
+ }
32015
+ const parent = res.data.parentID ?? "";
32016
+ if (parent.length === 0) break;
32017
+ if (seen.has(parent)) {
32018
+ walkReason = `cycle at ${parent} (store anomaly); walk stopped`;
32019
+ break;
32020
+ }
32021
+ chain.push(parent);
32022
+ seen.add(parent);
32023
+ current = parent;
32024
+ }
32025
+ let scanFailed = "";
32026
+ for (const [i, sid] of chain.entries()) {
32027
+ const res = await client.session.messages({ path: { id: sid }, query: { directory } });
32028
+ if (res.error !== void 0 || res.data === void 0) {
32029
+ scanFailed = scanFailed.length > 0 ? scanFailed : `messages(${sid}) refused`;
32030
+ continue;
32031
+ }
32032
+ const hit = scanMessages(res.data, i, sid, artifact);
32033
+ if (hit !== null) {
32034
+ return { status: "NOT-INDEPENDENT", convenerChain: chain, evidence: hit };
32035
+ }
32036
+ }
32037
+ if (scanFailed.length > 0 || walkIncomplete) {
32038
+ const why = [walkIncomplete ? walkReason : "", scanFailed].filter((s) => s.length > 0).join("; ");
32039
+ return { status: "UNVERIFIABLE", convenerChain: chain, evidence: `partial read: ${why} \u2014 independence was NOT established` };
32040
+ }
32041
+ const note = walkReason.length > 0 ? `; walk note: ${walkReason}` : "";
32042
+ return {
32043
+ status: "INDEPENDENT",
32044
+ convenerChain: chain,
32045
+ evidence: `scanned ${String(chain.length)} session(s) of the convener chain to root; no drafting evidence within read scope${note}`
32046
+ };
32047
+ }
32048
+ function independenceLabel(v) {
32049
+ return v.status === "INDEPENDENT" ? "independence=INDEPENDENT" : `independence=${v.status} \u2014 ${v.evidence}`;
32050
+ }
32051
+
32052
+ // src/instrument.ts
32053
+ import { createHash } from "node:crypto";
32054
+
32055
+ // src/chamber/protocol.ts
32056
+ import { join as join4 } from "node:path";
32057
+
31898
32058
  // src/verdict/index.ts
31899
32059
  var OPEN_BRACE = 123;
31900
32060
  var CLOSE_BRACE = 125;
@@ -32046,6 +32206,135 @@ async function parseVerdict(text, repair) {
32046
32206
  return rejectVerdict(`${first.why} + repair-invalid: ${second.why}`);
32047
32207
  }
32048
32208
 
32209
+ // src/state/chamber.ts
32210
+ import { mkdir as mkdir2, readFile as readFile2, readdir, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
32211
+ import { homedir as homedir3 } from "node:os";
32212
+ import { dirname as dirname2, isAbsolute, join as join3, resolve } from "node:path";
32213
+ var DEFAULT_CHAMBER_LEDGER = join3(homedir3(), ".sibyl", "chamber-ledger.jsonl");
32214
+ var TERMINAL_STATES = ["CONVERGED", "NEEDS_ROUND", "MEMBER_LOST", "TIMEOUT"];
32215
+ var RUN_PROFILES = ["review", "exam"];
32216
+ var RECORD_SCHEMA = 1;
32217
+ function asStringArray(v) {
32218
+ return Array.isArray(v) && v.every((x) => typeof x === "string") ? [...v] : null;
32219
+ }
32220
+ function isRosterRef(v) {
32221
+ if (typeof v !== "object" || v === null) return false;
32222
+ const o = v;
32223
+ const modelOk = o["policyCheck"] === "deny" ? typeof o["modelId"] === "string" : isNonEmptyString(o["modelId"]);
32224
+ return isNonEmptyString(o["role"]) && isNonEmptyString(o["slot"]) && modelOk && (o["policyCheck"] === "allow" || o["policyCheck"] === "deny") && (o["denyReason"] === void 0 || typeof o["denyReason"] === "string");
32225
+ }
32226
+ function isArtifactRef(v) {
32227
+ if (typeof v !== "object" || v === null) return false;
32228
+ const o = v;
32229
+ return isNonEmptyString(o["role"]) && typeof o["round"] === "number" && Number.isInteger(o["round"]) && o["round"] >= 0 && isNonEmptyString(o["phase"]) && isNonEmptyString(o["relPath"]) && typeof o["sha256"] === "string" && /^[0-9a-f]{64}$/.test(o["sha256"]) && typeof o["bytes"] === "number" && o["bytes"] >= 0 && isNonEmptyString(o["presentAt"]) && (o["rc"] === null || typeof o["rc"] === "number") && (o["signal"] === null || typeof o["signal"] === "string") && typeof o["timedOut"] === "boolean";
32230
+ }
32231
+ function validateChamberRecord(raw) {
32232
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { ok: false, reason: "record is not an object" };
32233
+ const o = raw;
32234
+ if (o["schema"] !== RECORD_SCHEMA) return { ok: false, reason: `schema must be ${RECORD_SCHEMA}` };
32235
+ if (!isNonEmptyString(o["runId"])) return { ok: false, reason: "runId must be a non-empty string" };
32236
+ if (typeof o["serial"] !== "number" || !Number.isInteger(o["serial"]) || o["serial"] < 1) return { ok: false, reason: "serial must be a positive integer" };
32237
+ if (!RUN_PROFILES.includes(o["profile"])) return { ok: false, reason: `profile must be one of ${RUN_PROFILES.join("|")}` };
32238
+ if (!isNonEmptyString(o["goal"])) return { ok: false, reason: "goal must be a non-empty string" };
32239
+ if (typeof o["target"] !== "string") return { ok: false, reason: "target must be a string" };
32240
+ if (!isNonEmptyString(o["runDir"]) || !isAbsolute(o["runDir"])) return { ok: false, reason: "runDir must be an absolute path" };
32241
+ if (!isNonEmptyString(o["createdAt"]) || Number.isNaN(Date.parse(o["createdAt"]))) return { ok: false, reason: "createdAt must be ISO" };
32242
+ if (!isNonEmptyString(o["updatedAt"]) || Number.isNaN(Date.parse(o["updatedAt"]))) return { ok: false, reason: "updatedAt must be ISO" };
32243
+ if (o["terminal"] !== null && !TERMINAL_STATES.includes(o["terminal"])) return { ok: false, reason: "terminal must be null or a TerminalState" };
32244
+ if (!Array.isArray(o["roster"]) || !o["roster"].every(isRosterRef)) return { ok: false, reason: "roster must be an array of RosterRef" };
32245
+ const judgePool = asStringArray(o["judgePool"]);
32246
+ if (judgePool === null) return { ok: false, reason: "judgePool must be a string array" };
32247
+ if (!isNonEmptyString(o["seed"])) return { ok: false, reason: "seed must be a non-empty string" };
32248
+ if (typeof o["drawCommit"] !== "string" || !/^[0-9a-f]{64}$/.test(o["drawCommit"])) return { ok: false, reason: "drawCommit must be 64-hex" };
32249
+ if (typeof o["judgeModelId"] !== "string") return { ok: false, reason: "judgeModelId must be a string" };
32250
+ if (typeof o["rounds"] !== "number" || !Number.isInteger(o["rounds"]) || o["rounds"] < 0) return { ok: false, reason: "rounds must be a non-negative integer" };
32251
+ if (!Array.isArray(o["artifacts"]) || !o["artifacts"].every(isArtifactRef)) return { ok: false, reason: "artifacts must be an array of ArtifactRef" };
32252
+ if (typeof o["evidenceRows"] !== "number" || !Number.isInteger(o["evidenceRows"]) || o["evidenceRows"] < 0) return { ok: false, reason: "evidenceRows must be a non-negative integer" };
32253
+ if (o["notes"] !== void 0 && typeof o["notes"] !== "string") return { ok: false, reason: "notes must be a string when present" };
32254
+ if (o["instrument"] !== void 0 && !isInstrumentShape(o["instrument"])) {
32255
+ return { ok: false, reason: "instrument must be {rulesHash: 64-hex, components: name->64-hex} when present" };
32256
+ }
32257
+ if (o["independence"] !== void 0 && !isIndependenceShape(o["independence"])) {
32258
+ return { ok: false, reason: "independence must be {status: INDEPENDENT|NOT-INDEPENDENT|UNVERIFIABLE, convenerChain: string[], evidence: string} when present" };
32259
+ }
32260
+ const record3 = {
32261
+ schema: RECORD_SCHEMA,
32262
+ runId: o["runId"],
32263
+ serial: o["serial"],
32264
+ profile: o["profile"],
32265
+ goal: o["goal"],
32266
+ target: o["target"],
32267
+ runDir: o["runDir"],
32268
+ createdAt: o["createdAt"],
32269
+ updatedAt: o["updatedAt"],
32270
+ terminal: o["terminal"],
32271
+ roster: [...o["roster"]],
32272
+ judgePool,
32273
+ seed: o["seed"],
32274
+ drawCommit: o["drawCommit"],
32275
+ judgeModelId: o["judgeModelId"],
32276
+ rounds: o["rounds"],
32277
+ artifacts: [...o["artifacts"]],
32278
+ evidenceRows: o["evidenceRows"]
32279
+ };
32280
+ if (typeof o["notes"] === "string") record3.notes = o["notes"];
32281
+ if (o["instrument"] !== void 0) record3.instrument = rebuildInstrument(o["instrument"]);
32282
+ if (o["independence"] !== void 0) record3.independence = rebuildIndependence(o["independence"]);
32283
+ return { ok: true, record: record3 };
32284
+ }
32285
+ function parseLedgerLine(line) {
32286
+ const trimmed = line.trim();
32287
+ if (trimmed.length === 0) return { ok: false, reason: "blank line" };
32288
+ let parsed;
32289
+ try {
32290
+ parsed = JSON.parse(trimmed);
32291
+ } catch (err) {
32292
+ const msg = err instanceof Error ? err.message : String(err);
32293
+ return { ok: false, reason: `not a single JSON document (merged-row guard): ${msg}` };
32294
+ }
32295
+ const v = validateChamberRecord(parsed);
32296
+ if (!v.ok) return { ok: false, reason: v.reason };
32297
+ return { ok: true, record: v.record };
32298
+ }
32299
+ async function loadLedger(ledgerPath) {
32300
+ let text;
32301
+ try {
32302
+ text = await readFile2(ledgerPath, "utf8");
32303
+ } catch {
32304
+ return { rows: [], dropped: [] };
32305
+ }
32306
+ const rows = [];
32307
+ const dropped = [];
32308
+ const lines = text.split("\n");
32309
+ for (const [i, line] of lines.entries()) {
32310
+ if (line.trim().length === 0) continue;
32311
+ const v = parseLedgerLine(line);
32312
+ if (v.ok) rows.push(v.record);
32313
+ else dropped.push({ line: i + 1, reason: v.reason });
32314
+ }
32315
+ return { rows, dropped };
32316
+ }
32317
+
32318
+ // src/chamber/protocol.ts
32319
+ var CONTRACT = (writeTo, sections) => `OUTPUT CONTRACT: as you work, WRITE your result incrementally to ${writeTo} (create parent dirs as needed). Required structure: ${sections}. The file on disk is the only receipt that counts; keep the chat reply short. Write ONLY inside your run directory.`;
32320
+ function evidencePrompt(cfg, abs) {
32321
+ return `You are the EVIDENCE GATHERER of a Sibyl review chamber. Goal: "${cfg.goal}". Artifact under review: ${cfg.targetPath}. Enumerate evidence sources BROADLY first (list directories, then sample candidates: the target, adjacent docs/notes, logs, prior reviews). Breadth over depth; no deep analysis. Append one JSON object per line to ${abs}: {"probe":"<command or path read>","found":"<path>","class":"<doc|log|ledger|transcript|other>","note":"<one line>"}. ` + CONTRACT(abs, "one JSON object per line");
32322
+ }
32323
+ function proPrompt(cfg, round, abs, charges) {
32324
+ const chargeBlock = charges.length > 0 ? ` Prior open charges you must answer: ${JSON.stringify(charges)}.` : "";
32325
+ return `You are PRO (round ${String(round)}) in a Sibyl adversarial chamber. Goal: "${cfg.goal}". Read ${cfg.targetPath} and the evidence ledger ${join4(cfg.runDir, "evidence/evidence.jsonl")}, then argue for the strongest DEFENSIBLE version of the artifact: real merits, each claim citing ledger rows/paths, no spin.` + chargeBlock + " " + CONTRACT(abs, "## Case (numbered claims C1..Cn) and ## Evidence (claim -> cited paths)");
32326
+ }
32327
+ function conPrompt(cfg, round, abs, charges) {
32328
+ const chargeBlock = charges.length > 0 ? ` Previously contested \u2014 sharpen or drop: ${JSON.stringify(charges)}.` : "";
32329
+ return `You are CON (round ${String(round)}) in a Sibyl adversarial chamber. Goal: "${cfg.goal}". Read ${cfg.targetPath} and the evidence ledger ${join4(cfg.runDir, "evidence/evidence.jsonl")}, then attack the artifact: REAL defects only (correctness, missing evidence, unactionable claims), every charge citing a path or quote.` + chargeBlock + " " + CONTRACT(abs, "## Charges (numbered N1..Nn, each with cited evidence)");
32330
+ }
32331
+ function rebuttalPrompt(cfg, role, round, otherAbs, abs) {
32332
+ return `You are ${role.toUpperCase()} in cross-critique (round ${String(round)}). Read your opponent's artifact at ${otherAbs} plus the evidence ledger under ${join4(cfg.runDir, "evidence")} (you may re-probe primary sources). Respond point by point: CONCEDE what is right, REBUT what is wrong, replies keyed to the opponent's numbering. Never edit the opponent's file; write only your own new file. ` + CONTRACT(abs, "## Replies (K1..Kn keyed to opponent numbering) and ## Still Open");
32333
+ }
32334
+ function judgePrompt(cfg, round, paths, abs) {
32335
+ return `You are the INDEPENDENT JUDGE (round ${String(round)}) of a Sibyl chamber. You wrote nothing here. FIRST ACTION (no analysis before it): write a valid interim JSON to ${abs} \u2014 {"convergence":"NEEDS_ROUND","conclusion":"NEEDS_HUMAN","confidence":0,"reasons":["interim - judging"],"must_fix":[],"charges":[]} \u2014 then read the materials and OVERWRITE it with your final verdict as your last action. Read ALL of: ${paths.join(" ; ")} and the original target ${cfg.targetPath}. Re-probe AT MOST the three most load-bearing citations (open the cited file; a citation to a non-existent path is an automatic charge) \u2014 time-box yourself, breadth of verification below 3 is enough. Decide if the debate has CONVERGED (no material open charges) and the single review conclusion. The file must end holding EXACTLY ONE JSON object: {"convergence":"CONVERGED"|"NEEDS_ROUND","conclusion":"APPROVE"|"REJECT"|"NEEDS_HUMAN","confidence":<0..1>,"reasons":[...],"must_fix":[...],"charges":[...open charges; empty when CONVERGED]}. Also print the same JSON in your reply. Partial or non-JSON output = failure.`;
32336
+ }
32337
+
32049
32338
  // src/personas.ts
32050
32339
  var ARCHITECT_OUTPUT_CONTRACT = 'Respond with EXACTLY one JSON object and nothing else - no prose, no markdown fences, no extra keys - matching: {"tasks":[{"id":<string>,"title":<string>,"instructions":<string>,"dependsOn":[<task id strings>]}],"concurrency":<integer >= 1>,"notes":<string>}. "tasks" are the units of work decomposing the goal, "dependsOn" lists task ids that must finish first, "concurrency" is the maximum number of tasks safe to run in parallel, "notes" carries planning remarks (empty string if none).';
32051
32340
  var ARCHITECT_SYSTEM = "You are ARCHITECT, the SIBYL planning persona. Decompose the given artifact-production goal into a dependency-ordered workflow of minimal, independently executable tasks. " + ARCHITECT_OUTPUT_CONTRACT;
@@ -32055,6 +32344,11 @@ var ARCHITECT_PERSONA = {
32055
32344
  system: ARCHITECT_SYSTEM,
32056
32345
  modelSlot: "architect"
32057
32346
  };
32347
+ function repairDemand(why) {
32348
+ return `Your previous reply was not a valid verdict JSON (${why}). Reply again with EXACTLY one JSON object and nothing else - no prose, no markdown fences: {"verdict":"APPROVE"|"REJECT","confidence":<number between 0 and 1>,"reasons":[<strings>],"must_fix":[<strings>]}`;
32349
+ }
32350
+ var SWARM_JUDGE_SYSTEM = "You are the SIBYL swarm judge. You rule on pipeline reports with exactly one word: APPROVE, REJECT, or EXHAUSTED.";
32351
+ var SWARM_JUDGE_WORD_CONTRACT = "Respond with EXACTLY one word and nothing else: APPROVE, REJECT, or EXHAUSTED.";
32058
32352
  var PERSONAS = {
32059
32353
  MELCHIOR: {
32060
32354
  id: "MELCHIOR",
@@ -32081,9 +32375,108 @@ var BY_ID = new Map(
32081
32375
  PERSONA_IDS.map((id) => [id, PERSONAS[id]])
32082
32376
  );
32083
32377
 
32378
+ // src/instrument.ts
32379
+ function sha256Hex(text) {
32380
+ return createHash("sha256").update(text, "utf8").digest("hex");
32381
+ }
32382
+ var REPAIR_SENTINEL = "<parse-failure-why>";
32383
+ var SENTINEL_CFG = {
32384
+ runId: "sibyl-sentinel",
32385
+ runDir: "/sentinel/run",
32386
+ goal: "<goal>",
32387
+ targetPath: "/sentinel/target.md",
32388
+ ledgerPath: "/sentinel/ledger.jsonl",
32389
+ seed: "<seed>",
32390
+ maxRounds: 1,
32391
+ pool: { default: { providerID: "", modelID: "" } },
32392
+ policy: null,
32393
+ slots: { evidence: "default", pro: "default", con: "default", judge: "default" },
32394
+ judgePoolIds: [],
32395
+ lane: null
32396
+ };
32397
+ function chamberRoleTexts() {
32398
+ return {
32399
+ "chamber-evidence-prompt": evidencePrompt(SENTINEL_CFG, "/sentinel/evidence.jsonl"),
32400
+ "chamber-pro-prompt": proPrompt(SENTINEL_CFG, 1, "/sentinel/pro.md", []),
32401
+ "chamber-con-prompt": conPrompt(SENTINEL_CFG, 1, "/sentinel/con.md", []),
32402
+ "chamber-rebuttal-prompt": rebuttalPrompt(SENTINEL_CFG, "pro", 1, "/sentinel/con.md", "/sentinel/pro-rebuttal.md"),
32403
+ "chamber-judge-prompt": judgePrompt(SENTINEL_CFG, 1, ["/sentinel/pro.md", "/sentinel/con.md"], "/sentinel/judge.json")
32404
+ };
32405
+ }
32406
+ function componentTexts() {
32407
+ const texts = {
32408
+ "repair-grammar": repairDemand(REPAIR_SENTINEL),
32409
+ "swarm-judge-system": SWARM_JUDGE_SYSTEM,
32410
+ "swarm-judge-word-contract": SWARM_JUDGE_WORD_CONTRACT,
32411
+ ...chamberRoleTexts()
32412
+ };
32413
+ for (const id of COUNCILORS) {
32414
+ texts[`councilor-${id.toLowerCase()}`] = COUNCILOR_PERSONAS[id].system;
32415
+ }
32416
+ texts["architect-system"] = ARCHITECT_SYSTEM;
32417
+ return texts;
32418
+ }
32419
+ var cached3 = null;
32420
+ function instrumentFace() {
32421
+ if (cached3 !== null) return cached3;
32422
+ const components = {};
32423
+ for (const [name, text] of Object.entries(componentTexts())) {
32424
+ components[name] = sha256Hex(text);
32425
+ }
32426
+ const canonical = JSON.stringify(
32427
+ Object.keys(components).sort().map((name) => [name, components[name]])
32428
+ );
32429
+ cached3 = { rulesHash: sha256Hex(canonical), components };
32430
+ return cached3;
32431
+ }
32432
+ function rulesLabel(face) {
32433
+ return `rules=${face.rulesHash.slice(0, 12)}`;
32434
+ }
32435
+
32436
+ // src/terminal.ts
32437
+ var TERMINAL_STATUSES = ["ok", "error", "timeout"];
32438
+ var TERMINAL_DETAIL_MAX = 240;
32439
+ function sanitizeDetail(text) {
32440
+ const flat = text.replace(/\s+/g, " ").trim();
32441
+ return flat.length <= TERMINAL_DETAIL_MAX ? flat : `${flat.slice(0, TERMINAL_DETAIL_MAX)}\u2026`;
32442
+ }
32443
+ function classifyFailure(error105) {
32444
+ return /timeout|timed out|deadline/i.test(error105) ? "timeout" : "error";
32445
+ }
32446
+ function formatTerminalRow(at, row) {
32447
+ return `ts=${at.toISOString()} task=${row.task} worker=${row.worker} status=${row.status} session=${row.session.length > 0 ? row.session : "-"} detail=${sanitizeDetail(row.detail)}`;
32448
+ }
32449
+ function parseTerminalRows(text) {
32450
+ const byTask = /* @__PURE__ */ new Map();
32451
+ const lines = text.split("\n");
32452
+ for (const [i, line] of lines.entries()) {
32453
+ if (line.trim().length === 0) continue;
32454
+ const task = /(?:^| )task=(\S+)/.exec(line)?.[1];
32455
+ const statusRaw = /(?:^| )status=(\S+)/.exec(line)?.[1];
32456
+ if (task === void 0 || statusRaw === void 0) continue;
32457
+ const status = TERMINAL_STATUSES.includes(statusRaw) ? statusRaw : void 0;
32458
+ if (status === void 0) continue;
32459
+ byTask.set(task, {
32460
+ task,
32461
+ worker: /(?:^| )worker=(\S+)/.exec(line)?.[1] ?? "-",
32462
+ status,
32463
+ session: /(?:^| )session=(\S+)/.exec(line)?.[1] ?? "-",
32464
+ detail: /(?:^| )detail=([\s\S]*)$/.exec(line)?.[1] ?? "",
32465
+ line: i + 1
32466
+ });
32467
+ }
32468
+ return byTask;
32469
+ }
32470
+ function deadWithoutRecord(declaredTaskIds, rows) {
32471
+ return declaredTaskIds.filter((id) => !rows.has(id));
32472
+ }
32473
+ function deadFaceCount(dead) {
32474
+ return `dead-without-record=${String(dead.length)}`;
32475
+ }
32476
+
32084
32477
  // src/tools/shared.ts
32085
- import { readFile as readFile2, stat } from "node:fs/promises";
32086
- import { isAbsolute, resolve } from "node:path";
32478
+ import { readFile as readFile3, stat } from "node:fs/promises";
32479
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
32087
32480
  var ARTIFACT_MAX_BYTES = 262144;
32088
32481
  var DEFAULT_SLOT2 = "default";
32089
32482
  function slotForModel(pool, overrideSlot, personaSlot) {
@@ -32108,7 +32501,7 @@ async function readArtifact(raw, cwd) {
32108
32501
  text: trimmed
32109
32502
  };
32110
32503
  }
32111
- const path = isAbsolute(trimmed) ? trimmed : resolve(cwd, trimmed);
32504
+ const path = isAbsolute2(trimmed) ? trimmed : resolve2(cwd, trimmed);
32112
32505
  let info;
32113
32506
  try {
32114
32507
  info = await stat(path);
@@ -32126,7 +32519,7 @@ async function readArtifact(raw, cwd) {
32126
32519
  }
32127
32520
  let text;
32128
32521
  try {
32129
- text = await readFile2(path, "utf8");
32522
+ text = await readFile3(path, "utf8");
32130
32523
  } catch (err) {
32131
32524
  return { ok: false, error: `artifact "${path}": ${errMessage(err)}` };
32132
32525
  }
@@ -32196,7 +32589,8 @@ function formatRunLine(record3) {
32196
32589
  const verdict = record3.verdict === void 0 ? "-" : `${record3.verdict.verdict}(A${String(record3.verdict.approvals)}/R${String(record3.verdict.rejects)}/E${String(record3.verdict.errors)}/M${String(record3.verdict.missing)})`;
32197
32590
  const rounds = record3.rounds === void 0 ? "" : ` rounds=${String(record3.rounds)}`;
32198
32591
  const goal = record3.goal === void 0 ? "" : ` goal="${record3.goal}"`;
32199
- return `${record3.runId} ${record3.kind} ${record3.status} ${verdict}${rounds}${goal} created=${record3.createdAt} updated=${record3.updatedAt} space=${record3.spaceDir}`;
32592
+ const rules = record3.instrument === void 0 ? "" : ` rules=${record3.instrument.rulesHash.slice(0, 12)}`;
32593
+ return `${record3.runId} ${record3.kind} ${record3.status} ${verdict}${rounds}${goal}${rules} created=${record3.createdAt} updated=${record3.updatedAt} space=${record3.spaceDir}`;
32200
32594
  }
32201
32595
 
32202
32596
  // src/tools/consult.ts
@@ -32210,9 +32604,6 @@ ${goal}
32210
32604
  ARTIFACT:
32211
32605
  ${artifactText}`;
32212
32606
  }
32213
- function buildRepairDemand(why) {
32214
- return `Your previous reply was not a valid verdict JSON (${why}). Reply again with EXACTLY one JSON object and nothing else - no prose, no markdown fences: {"verdict":"APPROVE"|"REJECT","confidence":<number between 0 and 1>,"reasons":[<strings>],"must_fix":[<strings>]}`;
32215
- }
32216
32607
  var REPLY_HEADER = "<!-- SIBYL consult voter reply (plan task 8). Full raw session text below; parse/repair metadata at the end. -->\n";
32217
32608
  function renderReply(voter, text, meta3) {
32218
32609
  return `${REPLY_HEADER}
@@ -32230,7 +32621,7 @@ async function castVote(job) {
32230
32621
  const { client, options } = deps;
32231
32622
  const persona = PERSONAS[id];
32232
32623
  const model = slotForModel(options.modelPool, options.voters[id], persona.modelSlot);
32233
- const replyPath = join3(spaceDir, `${id}.md`);
32624
+ const replyPath = join5(spaceDir, `${id}.md`);
32234
32625
  const result = await runPersona({
32235
32626
  client,
32236
32627
  directory,
@@ -32244,7 +32635,7 @@ async function castVote(job) {
32244
32635
  const error105 = `${result.stage ?? "engine"}: ${result.error ?? "unknown error"}`;
32245
32636
  const vote2 = { id, ok: false, error: error105 };
32246
32637
  const replyAt2 = await safeWrite(replyPath, renderReply(id, "(no reply \u2014 engine call failed)", `ballot: ERROR ${error105}`));
32247
- return { id, vote: vote2, replyPath: replyAt2, modelApplied: result.modelApplied, latencyMs: result.latencyMs };
32638
+ return { id, vote: vote2, replyPath: replyAt2, sessionId: result.sessionID, modelApplied: result.modelApplied, latencyMs: result.latencyMs };
32248
32639
  }
32249
32640
  const verdict = await parseVerdict(result.text, async (_badText, why) => {
32250
32641
  const followed = await runInSession(
@@ -32252,7 +32643,7 @@ async function castVote(job) {
32252
32643
  directory,
32253
32644
  result.sessionID,
32254
32645
  model,
32255
- buildRepairDemand(why),
32646
+ repairDemand(why),
32256
32647
  options.timeoutMs
32257
32648
  );
32258
32649
  return followed.ok ? followed.text : "";
@@ -32261,11 +32652,11 @@ async function castVote(job) {
32261
32652
  const vote = unparseable ? { id, ok: false, error: verdict.reasons[0] ?? "verdict-unparseable" } : { id, ok: true, verdict };
32262
32653
  const meta3 = unparseable ? `ballot: ERROR ${verdict.reasons[0] ?? "verdict-unparseable"} (reply did not parse)` : `ballot: ${verdict.verdict} (confidence ${String(verdict.confidence)}) \xB7 session ${result.sessionID} \xB7 model ${result.modelApplied ?? "host-picked"} \xB7 ${String(result.latencyMs)}ms`;
32263
32654
  const replyAt = await safeWrite(replyPath, renderReply(id, result.text, meta3));
32264
- return { id, vote, replyPath: replyAt, modelApplied: result.modelApplied, latencyMs: result.latencyMs };
32655
+ return { id, vote, replyPath: replyAt, sessionId: result.sessionID, modelApplied: result.modelApplied, latencyMs: result.latencyMs };
32265
32656
  }
32266
32657
  async function safeWrite(path, text) {
32267
32658
  try {
32268
- await writeFile2(path, text, "utf8");
32659
+ await writeFile3(path, text, "utf8");
32269
32660
  return path;
32270
32661
  } catch (err) {
32271
32662
  return `<${path} write failed: ${errMessage(err)}>`;
@@ -32292,6 +32683,7 @@ async function consultExecute(deps, args, context) {
32292
32683
  if (!read.ok) {
32293
32684
  return `SIBYL consult: ${read.error}`;
32294
32685
  }
32686
+ const independence = await assessIndependence(deps.client, context.directory, context.sessionID, read);
32295
32687
  const { record: record3 } = await deps.store.createRun({
32296
32688
  kind: "consult",
32297
32689
  artifact: read.source,
@@ -32303,25 +32695,51 @@ async function consultExecute(deps, args, context) {
32303
32695
  )
32304
32696
  );
32305
32697
  const tally = tallyVotes(outcomes.map((o) => o.vote));
32698
+ const terminalsPath = join5(record3.spaceDir, "TERMINALS.txt");
32699
+ await Promise.all(
32700
+ outcomes.map((o) => {
32701
+ const vote = o.vote;
32702
+ const status = "missing" in vote ? "error" : vote.ok ? "ok" : classifyFailure(vote.error);
32703
+ const detail = "missing" in vote || !vote.ok ? `ballot=${ballotTag(vote)}` : `ballot=${vote.verdict.verdict}`;
32704
+ const line = formatTerminalRow(/* @__PURE__ */ new Date(), {
32705
+ task: o.id,
32706
+ worker: o.id,
32707
+ status,
32708
+ session: o.sessionId,
32709
+ detail
32710
+ });
32711
+ return appendFile(terminalsPath, `${line}
32712
+ `, "utf8").catch((err) => {
32713
+ console.error(`[sibyl consult] terminal row for ${o.id} failed to write: ${errMessage(err)}`);
32714
+ });
32715
+ })
32716
+ );
32717
+ const rows = parseTerminalRows(await readFile4(terminalsPath, "utf8").catch(() => ""));
32718
+ const dead = deadWithoutRecord([...COUNCILORS], rows);
32719
+ const effectiveVerdict = dead.length > 0 ? "CANNOT_ANSWER" : tally.verdict;
32306
32720
  const finalRecord = {
32307
32721
  ...record3,
32308
32722
  status: "done",
32309
32723
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
32310
32724
  verdict: {
32311
- verdict: tally.verdict,
32725
+ verdict: effectiveVerdict,
32312
32726
  approvals: tally.approvals,
32313
32727
  rejects: tally.rejects,
32314
32728
  errors: tally.errors,
32315
32729
  missing: tally.missing
32316
32730
  },
32317
- notes: `policy=${tally.policy} ` + outcomes.map((o) => `${o.id}=${ballotTag(o.vote)} reply=${o.replyPath}`).join(" \xB7 ")
32731
+ instrument: instrumentFace(),
32732
+ independence,
32733
+ notes: `policy=${tally.policy} ${deadFaceCount(dead)}${dead.length > 0 ? ` (${dead.join(",")} left no terminal row: instrument wounded, not a verdict)` : ""} independence=${independence.status}${independence.status === "NOT-INDEPENDENT" ? " (self-convoked: ballot preserved, not in any effective path)" : ""} ` + outcomes.map((o) => `${o.id}=${ballotTag(o.vote)} reply=${o.replyPath}`).join(" \xB7 ")
32318
32734
  };
32319
32735
  await deps.store.appendOrUpdate(finalRecord);
32320
32736
  const lines = [
32321
- `SIBYL CONSULT: ${tally.verdict} (votes ${String(tally.approvals)}A/${String(tally.rejects)}R/${String(tally.errors)}E/${String(tally.missing)}M) run ${record3.runId}`,
32737
+ `SIBYL CONSULT: ${effectiveVerdict} (${deadFaceCount(dead)} votes ${String(tally.approvals)}A/${String(tally.rejects)}R/${String(tally.errors)}E/${String(tally.missing)}M) run ${record3.runId}`,
32738
+ ` ${independenceLabel(independence)}`,
32322
32739
  ...tally.reasons.map((r) => ` reason: ${r}`),
32323
32740
  ...tally.must_fix.map((m) => ` must_fix: ${m}`),
32324
32741
  ...outcomes.map((o) => ` reply: ${o.id} -> ${o.replyPath}`),
32742
+ ` ${rulesLabel(finalRecord.instrument ?? instrumentFace())} components=${String(Object.keys(instrumentFace().components).length)}`,
32325
32743
  ` store: ${deps.store.runsFile}`
32326
32744
  ];
32327
32745
  return lines.join("\n");
@@ -32335,20 +32753,20 @@ function ballotTag(vote) {
32335
32753
  import { spawn } from "node:child_process";
32336
32754
  import { randomBytes as randomBytes2 } from "node:crypto";
32337
32755
  import { existsSync as existsSync2 } from "node:fs";
32338
- import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
32339
- import { dirname as dirname2, join as join4 } from "node:path";
32756
+ import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
32757
+ import { dirname as dirname3, join as join6 } from "node:path";
32340
32758
  import { fileURLToPath } from "node:url";
32341
32759
  var REVIEW_TOOL_NAME = "sibyl_review";
32342
32760
  function nodeBinary() {
32343
32761
  return existsSync2("/usr/bin/node") ? "/usr/bin/node" : process.execPath;
32344
32762
  }
32345
32763
  function cliCommand(thisUrl, node2) {
32346
- const moduleDir = dirname2(fileURLToPath(thisUrl));
32347
- const repoRoot = dirname2(dirname2(moduleDir));
32348
- const distCli = join4(repoRoot, "dist", "cli.js");
32764
+ const moduleDir = dirname3(fileURLToPath(thisUrl));
32765
+ const repoRoot = dirname3(dirname3(moduleDir));
32766
+ const distCli = join6(repoRoot, "dist", "cli.js");
32349
32767
  if (existsSync2(distCli)) return { bin: node2, args: [distCli] };
32350
- const srcCli = join4(repoRoot, "src", "cli.ts");
32351
- const hooks = join4(repoRoot, "tools", "register-ts.mjs");
32768
+ const srcCli = join6(repoRoot, "src", "cli.ts");
32769
+ const hooks = join6(repoRoot, "tools", "register-ts.mjs");
32352
32770
  return { bin: node2, args: ["--import", hooks, srcCli] };
32353
32771
  }
32354
32772
  async function reviewExecute(deps, args, context) {
@@ -32357,29 +32775,38 @@ async function reviewExecute(deps, args, context) {
32357
32775
  const opts = deps.options;
32358
32776
  const now = /* @__PURE__ */ new Date();
32359
32777
  const runId = `sibyl-${compactIso(now)}-${randomBytes2(2).toString("hex")}`;
32360
- const runDir = join4(opts.lane.runRoot, `sibyl-run-${runId}`);
32361
- await mkdir2(runDir, { recursive: true });
32778
+ const runDir = join6(opts.lane.runRoot, `sibyl-run-${runId}`);
32779
+ await mkdir3(runDir, { recursive: true });
32362
32780
  const artifact = await readArtifact(args.target, context.directory);
32363
32781
  if (!artifact.ok) return `SIBYL review: ${artifact.error}`;
32364
32782
  let targetArg;
32365
32783
  if (artifact.kind === "inline") {
32366
- targetArg = join4(runDir, "target-inline.md");
32367
- await writeFile3(targetArg, artifact.text, "utf8");
32784
+ targetArg = join6(runDir, "target-inline.md");
32785
+ await writeFile4(targetArg, artifact.text, "utf8");
32368
32786
  } else {
32369
32787
  targetArg = artifact.source;
32370
32788
  }
32371
- await writeFile3(join4(runDir, "run-config.json"), `${JSON.stringify(opts, null, 2)}
32789
+ await writeFile4(join6(runDir, "run-config.json"), `${JSON.stringify(opts, null, 2)}
32372
32790
  `, "utf8");
32791
+ const independence = await assessIndependence(deps.client, context.directory, context.sessionID, artifact);
32792
+ await writeFile4(
32793
+ join6(runDir, "launch-context.json"),
32794
+ `${JSON.stringify({ convenerSessionID: context.sessionID, independence }, null, 2)}
32795
+ `,
32796
+ "utf8"
32797
+ );
32373
32798
  const cmd = cliCommand(import.meta.url, nodeBinary());
32374
32799
  const spawnArgs = [
32375
32800
  ...cmd.args,
32376
32801
  "run",
32802
+ "--run-id",
32803
+ runId,
32377
32804
  "--target",
32378
32805
  targetArg,
32379
32806
  "--goal",
32380
32807
  args.goal,
32381
32808
  "--config",
32382
- join4(runDir, "run-config.json"),
32809
+ join6(runDir, "run-config.json"),
32383
32810
  "--run-root",
32384
32811
  opts.lane.runRoot,
32385
32812
  "--seed",
@@ -32402,8 +32829,10 @@ async function reviewExecute(deps, args, context) {
32402
32829
  ` run: ${receipt.runId}`,
32403
32830
  ` dir: ${receipt.runDir}`,
32404
32831
  ` status: ${receipt.statusCmd}`,
32405
- ` voice at: ${join4(runDir, "run-record.json")} + chamber-ledger.jsonl (append-only)`,
32406
- ` verify: ${receipt.spotcheck}`
32832
+ ` voice at: ${join6(runDir, "run-record.json")} + chamber-ledger.jsonl (append-only)`,
32833
+ ` verify: ${receipt.spotcheck}`,
32834
+ ` ${rulesLabel(instrumentFace())} (the runner records its own full face into the run record)`,
32835
+ ` ${independenceLabel(independence)}`
32407
32836
  ].join("\n");
32408
32837
  } catch (err) {
32409
32838
  return internalError(REVIEW_TOOL_NAME, err);
@@ -32423,7 +32852,7 @@ function buildReviewTool(deps) {
32423
32852
  }
32424
32853
 
32425
32854
  // src/tools/anchor.ts
32426
- import { createHash } from "node:crypto";
32855
+ import { createHash as createHash2 } from "node:crypto";
32427
32856
  function canonicalView(rows) {
32428
32857
  return JSON.stringify(
32429
32858
  rows.map((r) => ({
@@ -32436,7 +32865,7 @@ function canonicalText(raw) {
32436
32865
  return raw.replace(/\s*<dcp-message-id[^>]*>[\s\S]*?<\/dcp-message-id>\s*$/g, "").trim();
32437
32866
  }
32438
32867
  function sha256(text) {
32439
- return createHash("sha256").update(text, "utf8").digest("hex");
32868
+ return createHash2("sha256").update(text, "utf8").digest("hex");
32440
32869
  }
32441
32870
  function anchorText(row) {
32442
32871
  return (row.parts ?? []).filter((p) => p.type === "text").map((p) => p.text ?? "").join("\n");
@@ -32633,110 +33062,9 @@ function buildAttributeTool(deps) {
32633
33062
  });
32634
33063
  }
32635
33064
 
32636
- // src/state/chamber.ts
32637
- import { mkdir as mkdir3, readFile as readFile3, readdir, rename as rename2, unlink as unlink2, writeFile as writeFile4 } from "node:fs/promises";
32638
- import { homedir as homedir3 } from "node:os";
32639
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join5, resolve as resolve2 } from "node:path";
32640
- var DEFAULT_CHAMBER_LEDGER = join5(homedir3(), ".sibyl", "chamber-ledger.jsonl");
32641
- var TERMINAL_STATES = ["CONVERGED", "NEEDS_ROUND", "MEMBER_LOST", "TIMEOUT"];
32642
- var RUN_PROFILES = ["review", "exam"];
32643
- var RECORD_SCHEMA = 1;
32644
- function asStringArray(v) {
32645
- return Array.isArray(v) && v.every((x) => typeof x === "string") ? [...v] : null;
32646
- }
32647
- function isRosterRef(v) {
32648
- if (typeof v !== "object" || v === null) return false;
32649
- const o = v;
32650
- const modelOk = o["policyCheck"] === "deny" ? typeof o["modelId"] === "string" : isNonEmptyString(o["modelId"]);
32651
- return isNonEmptyString(o["role"]) && isNonEmptyString(o["slot"]) && modelOk && (o["policyCheck"] === "allow" || o["policyCheck"] === "deny") && (o["denyReason"] === void 0 || typeof o["denyReason"] === "string");
32652
- }
32653
- function isArtifactRef(v) {
32654
- if (typeof v !== "object" || v === null) return false;
32655
- const o = v;
32656
- return isNonEmptyString(o["role"]) && typeof o["round"] === "number" && Number.isInteger(o["round"]) && o["round"] >= 0 && isNonEmptyString(o["phase"]) && isNonEmptyString(o["relPath"]) && typeof o["sha256"] === "string" && /^[0-9a-f]{64}$/.test(o["sha256"]) && typeof o["bytes"] === "number" && o["bytes"] >= 0 && isNonEmptyString(o["presentAt"]) && (o["rc"] === null || typeof o["rc"] === "number") && (o["signal"] === null || typeof o["signal"] === "string") && typeof o["timedOut"] === "boolean";
32657
- }
32658
- function validateChamberRecord(raw) {
32659
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { ok: false, reason: "record is not an object" };
32660
- const o = raw;
32661
- if (o["schema"] !== RECORD_SCHEMA) return { ok: false, reason: `schema must be ${RECORD_SCHEMA}` };
32662
- if (!isNonEmptyString(o["runId"])) return { ok: false, reason: "runId must be a non-empty string" };
32663
- if (typeof o["serial"] !== "number" || !Number.isInteger(o["serial"]) || o["serial"] < 1) return { ok: false, reason: "serial must be a positive integer" };
32664
- if (!RUN_PROFILES.includes(o["profile"])) return { ok: false, reason: `profile must be one of ${RUN_PROFILES.join("|")}` };
32665
- if (!isNonEmptyString(o["goal"])) return { ok: false, reason: "goal must be a non-empty string" };
32666
- if (typeof o["target"] !== "string") return { ok: false, reason: "target must be a string" };
32667
- if (!isNonEmptyString(o["runDir"]) || !isAbsolute2(o["runDir"])) return { ok: false, reason: "runDir must be an absolute path" };
32668
- if (!isNonEmptyString(o["createdAt"]) || Number.isNaN(Date.parse(o["createdAt"]))) return { ok: false, reason: "createdAt must be ISO" };
32669
- if (!isNonEmptyString(o["updatedAt"]) || Number.isNaN(Date.parse(o["updatedAt"]))) return { ok: false, reason: "updatedAt must be ISO" };
32670
- if (o["terminal"] !== null && !TERMINAL_STATES.includes(o["terminal"])) return { ok: false, reason: "terminal must be null or a TerminalState" };
32671
- if (!Array.isArray(o["roster"]) || !o["roster"].every(isRosterRef)) return { ok: false, reason: "roster must be an array of RosterRef" };
32672
- const judgePool = asStringArray(o["judgePool"]);
32673
- if (judgePool === null) return { ok: false, reason: "judgePool must be a string array" };
32674
- if (!isNonEmptyString(o["seed"])) return { ok: false, reason: "seed must be a non-empty string" };
32675
- if (typeof o["drawCommit"] !== "string" || !/^[0-9a-f]{64}$/.test(o["drawCommit"])) return { ok: false, reason: "drawCommit must be 64-hex" };
32676
- if (typeof o["judgeModelId"] !== "string") return { ok: false, reason: "judgeModelId must be a string" };
32677
- if (typeof o["rounds"] !== "number" || !Number.isInteger(o["rounds"]) || o["rounds"] < 0) return { ok: false, reason: "rounds must be a non-negative integer" };
32678
- if (!Array.isArray(o["artifacts"]) || !o["artifacts"].every(isArtifactRef)) return { ok: false, reason: "artifacts must be an array of ArtifactRef" };
32679
- if (typeof o["evidenceRows"] !== "number" || !Number.isInteger(o["evidenceRows"]) || o["evidenceRows"] < 0) return { ok: false, reason: "evidenceRows must be a non-negative integer" };
32680
- if (o["notes"] !== void 0 && typeof o["notes"] !== "string") return { ok: false, reason: "notes must be a string when present" };
32681
- const record3 = {
32682
- schema: RECORD_SCHEMA,
32683
- runId: o["runId"],
32684
- serial: o["serial"],
32685
- profile: o["profile"],
32686
- goal: o["goal"],
32687
- target: o["target"],
32688
- runDir: o["runDir"],
32689
- createdAt: o["createdAt"],
32690
- updatedAt: o["updatedAt"],
32691
- terminal: o["terminal"],
32692
- roster: [...o["roster"]],
32693
- judgePool,
32694
- seed: o["seed"],
32695
- drawCommit: o["drawCommit"],
32696
- judgeModelId: o["judgeModelId"],
32697
- rounds: o["rounds"],
32698
- artifacts: [...o["artifacts"]],
32699
- evidenceRows: o["evidenceRows"]
32700
- };
32701
- if (typeof o["notes"] === "string") record3.notes = o["notes"];
32702
- return { ok: true, record: record3 };
32703
- }
32704
- function parseLedgerLine(line) {
32705
- const trimmed = line.trim();
32706
- if (trimmed.length === 0) return { ok: false, reason: "blank line" };
32707
- let parsed;
32708
- try {
32709
- parsed = JSON.parse(trimmed);
32710
- } catch (err) {
32711
- const msg = err instanceof Error ? err.message : String(err);
32712
- return { ok: false, reason: `not a single JSON document (merged-row guard): ${msg}` };
32713
- }
32714
- const v = validateChamberRecord(parsed);
32715
- if (!v.ok) return { ok: false, reason: v.reason };
32716
- return { ok: true, record: v.record };
32717
- }
32718
- async function loadLedger(ledgerPath) {
32719
- let text;
32720
- try {
32721
- text = await readFile3(ledgerPath, "utf8");
32722
- } catch {
32723
- return { rows: [], dropped: [] };
32724
- }
32725
- const rows = [];
32726
- const dropped = [];
32727
- const lines = text.split("\n");
32728
- for (const [i, line] of lines.entries()) {
32729
- if (line.trim().length === 0) continue;
32730
- const v = parseLedgerLine(line);
32731
- if (v.ok) rows.push(v.record);
32732
- else dropped.push({ line: i + 1, reason: v.reason });
32733
- }
32734
- return { rows, dropped };
32735
- }
32736
-
32737
33065
  // src/tools/status.ts
32738
33066
  import { dirname as dirname4 } from "node:path";
32739
- import { join as join6 } from "node:path";
33067
+ import { join as join7 } from "node:path";
32740
33068
  var STATUS_TOOL_NAME = "sibyl_status";
32741
33069
  function buildStatusTool(deps) {
32742
33070
  return tool({
@@ -32757,7 +33085,7 @@ async function statusExecute(deps, args, _context) {
32757
33085
  return [`SIBYL RUN ${run2.runId}`, formatRunLine(run2)].join("\n");
32758
33086
  }
32759
33087
  const runs = await deps.store.load();
32760
- const ledger = await loadLedger(join6(dirname4(deps.store.runsFile), "chamber-ledger.jsonl"));
33088
+ const ledger = await loadLedger(join7(dirname4(deps.store.runsFile), "chamber-ledger.jsonl"));
32761
33089
  const chamberLines = ledger.rows.slice(-5).map(
32762
33090
  (r) => `${r.runId} chamber/${r.profile} ${r.terminal ?? "running"} rounds=${String(r.rounds)} artifacts=${String(r.artifacts.length)} judge=${r.judgeModelId} dir=${r.runDir}`
32763
33091
  );
@@ -32773,8 +33101,8 @@ async function statusExecute(deps, args, _context) {
32773
33101
  }
32774
33102
 
32775
33103
  // src/tools/swarm.ts
32776
- import { join as join7 } from "node:path";
32777
- import { writeFile as writeFile5 } from "node:fs/promises";
33104
+ import { join as join8 } from "node:path";
33105
+ import { appendFile as appendFile2, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
32778
33106
 
32779
33107
  // src/swarm/types.ts
32780
33108
  var SWARM_VERDICTS = {
@@ -32911,11 +33239,24 @@ function composeTaskPrompt(ctx, taskId) {
32911
33239
  Upstream conclusions (from completed dependencies):
32912
33240
  ${lines.join("\n")}`;
32913
33241
  }
33242
+ async function recordTerminal(ctx, taskId, workerId, status, session, detail) {
33243
+ if (ctx.writeTerminal === void 0) return true;
33244
+ try {
33245
+ await ctx.writeTerminal(
33246
+ formatTerminalRow(/* @__PURE__ */ new Date(), { task: taskId, worker: workerId, status, session, detail })
33247
+ );
33248
+ return true;
33249
+ } catch (e) {
33250
+ console.error(`[sibyl dispatch] terminal line for ${taskId} failed to write: ${describeFailure(e)}`);
33251
+ return false;
33252
+ }
33253
+ }
32914
33254
  async function runTask(ctx, task) {
32915
33255
  const worker = ctx.workerByTask.get(task.taskId);
32916
33256
  if (worker === void 0) {
32917
33257
  task.status = "failed";
32918
33258
  task.error = `no worker spec for task "${task.taskId}"`;
33259
+ await recordTerminal(ctx, task.taskId, "-", "error", "-", task.error);
32919
33260
  return;
32920
33261
  }
32921
33262
  for (let attempt = 1; attempt <= ctx.maxAttempts; attempt++) {
@@ -32936,30 +33277,45 @@ async function runTask(ctx, task) {
32936
33277
  } catch (e) {
32937
33278
  task.status = "failed";
32938
33279
  task.error = `runPersona threw: ${describeFailure(e)}`;
33280
+ await recordTerminal(ctx, task.taskId, worker.workerId, classifyFailure(task.error), "-", task.error);
32939
33281
  return;
32940
33282
  }
32941
33283
  if (result.ok) {
32942
- let ref;
32943
- try {
32944
- ref = await ctx.writeDraft({ taskId: task.taskId, workerId: worker.workerId, text: result.text });
32945
- } catch (e) {
32946
- task.status = "failed";
32947
- task.error = `draft write failed: ${describeFailure(e)}`;
33284
+ if (result.text.trim().length === 0) {
33285
+ task.error = "empty-reply: worker session ended with no content (absence is not a ballot)";
33286
+ } else {
33287
+ let ref;
33288
+ try {
33289
+ ref = await ctx.writeDraft({ taskId: task.taskId, workerId: worker.workerId, text: result.text });
33290
+ } catch (e) {
33291
+ task.status = "failed";
33292
+ task.error = `draft write failed: ${describeFailure(e)}`;
33293
+ await recordTerminal(ctx, task.taskId, worker.workerId, "error", result.sessionID, task.error);
33294
+ return;
33295
+ }
33296
+ const recorded = await recordTerminal(ctx, task.taskId, worker.workerId, "ok", result.sessionID, `draft=${ref}`);
33297
+ if (!recorded) {
33298
+ task.status = "failed";
33299
+ task.error = "terminal record write failed (W2: done without its record never stands)";
33300
+ return;
33301
+ }
33302
+ task.refs.push(ref);
33303
+ task.summary = summarizeTaskOutput(result.text, ctx.summaryMaxChars);
33304
+ if (result.sessionID !== "") task.sessionId = result.sessionID;
33305
+ task.status = "done";
32948
33306
  return;
32949
33307
  }
32950
- task.refs.push(ref);
32951
- task.summary = summarizeTaskOutput(result.text, ctx.summaryMaxChars);
32952
- if (result.sessionID !== "") task.sessionId = result.sessionID;
32953
- task.status = "done";
32954
- return;
33308
+ } else {
33309
+ task.error = result.error ?? "unknown engine error";
32955
33310
  }
32956
- task.error = result.error ?? "unknown engine error";
32957
33311
  if (isRateLimitError(task.error)) {
32958
33312
  task.status = "suspended";
33313
+ await recordTerminal(ctx, task.taskId, worker.workerId, "error", "-", `suspended: ${task.error}`);
32959
33314
  return;
32960
33315
  }
32961
33316
  }
32962
33317
  task.status = "failed";
33318
+ await recordTerminal(ctx, task.taskId, worker.workerId, classifyFailure(task.error ?? ""), "-", task.error ?? "attempts exhausted");
32963
33319
  }
32964
33320
  async function runWaves(ctx, state) {
32965
33321
  for (; ; ) {
@@ -32967,7 +33323,12 @@ async function runWaves(ctx, state) {
32967
33323
  if (schedulable.length === 0) break;
32968
33324
  if (state.rounds >= ctx.maxRounds) {
32969
33325
  state.exhausted = true;
32970
- for (const t of state.tasks) if (t.status === "pending") t.status = "suspended";
33326
+ for (const t of state.tasks) {
33327
+ if (t.status === "pending") {
33328
+ t.status = "suspended";
33329
+ await recordTerminal(ctx, t.taskId, ctx.workerByTask.get(t.taskId)?.workerId ?? "-", "error", "-", "exhausted: round budget spent before launch");
33330
+ }
33331
+ }
32971
33332
  break;
32972
33333
  }
32973
33334
  state.rounds += 1;
@@ -32978,9 +33339,12 @@ async function runWaves(ctx, state) {
32978
33339
  })());
32979
33340
  await Promise.all(launches);
32980
33341
  }
32981
- for (const t of state.tasks) if (t.status === "pending") {
32982
- t.status = "failed";
32983
- t.error = "blocked by non-done dependency";
33342
+ for (const t of state.tasks) {
33343
+ if (t.status === "pending") {
33344
+ t.status = "failed";
33345
+ t.error = "blocked by non-done dependency";
33346
+ await recordTerminal(ctx, t.taskId, ctx.workerByTask.get(t.taskId)?.workerId ?? "-", "error", "-", t.error);
33347
+ }
32984
33348
  }
32985
33349
  }
32986
33350
  function materialize(state) {
@@ -33028,7 +33392,8 @@ async function run(prior, roster, opts) {
33028
33392
  summaryMaxChars,
33029
33393
  maxRounds,
33030
33394
  clock: opts.clock ?? realClock(),
33031
- writeDraft: opts.writeDraft
33395
+ writeDraft: opts.writeDraft,
33396
+ writeTerminal: opts.writeTerminal
33032
33397
  };
33033
33398
  let state = prior ?? makeState(roster);
33034
33399
  if (prior !== void 0) {
@@ -33252,7 +33617,7 @@ function buildJudgeInput(report) {
33252
33617
  ...report.tasks.map((t) => `TASK ${t.id}: ${t.status}${t.summary === void 0 ? "" : ` \u2014 ${t.summary}`}`),
33253
33618
  ...report.artifacts.map((a) => `ARTIFACT: ${a}`),
33254
33619
  "",
33255
- "Respond with EXACTLY one word and nothing else: APPROVE, REJECT, or EXHAUSTED."
33620
+ SWARM_JUDGE_WORD_CONTRACT
33256
33621
  ];
33257
33622
  return lines.join("\n");
33258
33623
  }
@@ -33294,6 +33659,7 @@ async function swarmExecute(deps, args, context) {
33294
33659
  if (!read.ok) {
33295
33660
  return `SIBYL swarm: ${read.error}`;
33296
33661
  }
33662
+ const independence = await assessIndependence(client, directory, context.sessionID, read);
33297
33663
  const { record: record3 } = await store.createRun({ kind: "swarm", artifact: read.source, goal: args.goal });
33298
33664
  const architectModel = slotForModel(options.modelPool, void 0, PERSONAS.ARCHITECT.modelSlot);
33299
33665
  const plan = await planWorkflow({
@@ -33311,7 +33677,7 @@ async function swarmExecute(deps, args, context) {
33311
33677
  directory,
33312
33678
  persona: { system: PERSONAS.ARCHITECT.system },
33313
33679
  model: architectModel,
33314
- inputText: `${buildRepairDemand(why)} matching the workflow-schema contract: {"tasks":[{"id","title","instructions","dependsOn"}],"concurrency":<int >= 1>,"notes":<string>}`,
33680
+ inputText: `${repairDemand(why)} matching the workflow-schema contract: {"tasks":[{"id","title","instructions","dependsOn"}],"concurrency":<int >= 1>,"notes":<string>}`,
33315
33681
  timeoutMs: options.timeoutMs,
33316
33682
  parentID: context.sessionID
33317
33683
  });
@@ -33334,7 +33700,7 @@ async function swarmExecute(deps, args, context) {
33334
33700
  staggerMs: options.staggerMs,
33335
33701
  maxRounds: options.maxRounds,
33336
33702
  writeDraft: async ({ taskId, workerId, text }) => {
33337
- const path = join7(record3.spaceDir, `${workerId}.draft.md`);
33703
+ const path = join8(record3.spaceDir, `${workerId}.draft.md`);
33338
33704
  try {
33339
33705
  await writeFile5(path, `<!-- SIBYL swarm draft task=${taskId} worker=${workerId} -->
33340
33706
  ${text}
@@ -33343,7 +33709,9 @@ ${text}
33343
33709
  } catch (err) {
33344
33710
  return `<${path} write failed: ${errMessage(err)}>`;
33345
33711
  }
33346
- }
33712
+ },
33713
+ writeTerminal: (line) => appendFile2(join8(record3.spaceDir, "TERMINALS.txt"), `${line}
33714
+ `, "utf8")
33347
33715
  });
33348
33716
  if (!dispatch.ok) return failRun(deps, record3, { stage: "dispatch", error: dispatch.error });
33349
33717
  let report = buildReport({
@@ -33357,7 +33725,7 @@ ${text}
33357
33725
  const judged = await runPersona({
33358
33726
  client,
33359
33727
  directory,
33360
- persona: { system: "You are the SIBYL swarm judge. You rule on pipeline reports with exactly one word: APPROVE, REJECT, or EXHAUSTED." },
33728
+ persona: { system: SWARM_JUDGE_SYSTEM },
33361
33729
  model: slotForModel(options.modelPool, options.swarm.judge, void 0),
33362
33730
  inputText: buildJudgeInput(report),
33363
33731
  timeoutMs: options.timeoutMs,
@@ -33369,23 +33737,32 @@ ${text}
33369
33737
  }
33370
33738
  const counts = countTasks(report.tasks);
33371
33739
  const noSignal = counts.done === 0;
33372
- const tag = noSignal ? "CANNOT_ANSWER" : report.verdict === "APPROVE" ? "APPROVE" : "REJECT";
33740
+ const terminalsText = await readFile5(join8(record3.spaceDir, "TERMINALS.txt"), "utf8").catch(() => "");
33741
+ const dead = deadWithoutRecord(
33742
+ plan.schema.tasks.map((t) => t.id),
33743
+ parseTerminalRows(terminalsText)
33744
+ );
33745
+ const tag = dead.length > 0 || noSignal ? "CANNOT_ANSWER" : report.verdict === "APPROVE" ? "APPROVE" : "REJECT";
33373
33746
  await finishRun(deps, record3, {
33374
33747
  status: "done",
33375
33748
  rounds: report.rounds,
33749
+ instrument: instrumentFace(),
33750
+ independence,
33376
33751
  verdict: {
33377
33752
  verdict: tag,
33378
33753
  approvals: counts.done,
33379
- rejects: noSignal ? 0 : tag === "REJECT" ? 1 : 0,
33754
+ rejects: noSignal || dead.length > 0 ? 0 : tag === "REJECT" ? 1 : 0,
33380
33755
  errors: counts.failed,
33381
33756
  missing: counts.suspended
33382
33757
  },
33383
- notes: `${judgeNote}${noSignal ? " \xB7 zero drafts produced: instrument failure, not a verdict on the artifact" : ""} \xB7 ${report.tasks.map((t) => `${t.id}=${t.status}`).join(",")}`
33758
+ notes: `${judgeNote} \xB7 independence=${independence.status}${independence.status === "NOT-INDEPENDENT" ? " (self-convoked: report preserved, not in any effective path)" : ""} \xB7 ${deadFaceCount(dead)}${dead.length > 0 ? ` (${dead.join(",")} left no terminal row: instrument wounded, not a verdict on the artifact)` : ""}${noSignal ? " \xB7 zero drafts produced: instrument failure, not a verdict on the artifact" : ""} \xB7 ${report.tasks.map((t) => `${t.id}=${t.status}`).join(",")}`
33384
33759
  });
33385
33760
  const lines = [
33386
- `SIBYL SWARM: ${tag} run ${record3.runId} (rounds=${String(report.rounds)} done=${String(counts.done)} failed=${String(counts.failed)} suspended=${String(counts.suspended)} ${judgeNote})`,
33761
+ `SIBYL SWARM: ${tag} run ${record3.runId} (${deadFaceCount(dead)} rounds=${String(report.rounds)} done=${String(counts.done)} failed=${String(counts.failed)} suspended=${String(counts.suspended)} ${judgeNote})`,
33762
+ ` ${independenceLabel(independence)}`,
33387
33763
  ...report.tasks.map((t) => ` task: ${t.id} ${t.status}${t.sessionId === void 0 ? "" : ` session=${t.sessionId}`}`),
33388
33764
  ...report.artifacts.map((a) => ` artifact: ${a}`),
33765
+ ` ${rulesLabel(instrumentFace())} components=${String(Object.keys(instrumentFace().components).length)}`,
33389
33766
  ` space: ${record3.spaceDir}`,
33390
33767
  ` store: ${store.runsFile}`
33391
33768
  ];
@@ -33485,12 +33862,29 @@ function toEngineClient(sdk) {
33485
33862
  },
33486
33863
  parts: m.parts.map((p) => ({
33487
33864
  type: p.type,
33488
- ..."text" in p && p.text !== void 0 && { text: p.text }
33865
+ ..."text" in p && p.text !== void 0 && { text: p.text },
33866
+ // W1 drafting-evidence read: tool name + input bag of tool parts
33867
+ ...p.type === "tool" && {
33868
+ tool: p.tool,
33869
+ ...p.state.input !== void 0 && { input: p.state.input }
33870
+ }
33489
33871
  }))
33490
33872
  }))
33491
33873
  },
33492
33874
  ...r.error !== void 0 && { error: r.error }
33493
33875
  };
33876
+ },
33877
+ async get(args) {
33878
+ const r = await sdk.session.get(args);
33879
+ return {
33880
+ ...r.data !== void 0 && {
33881
+ data: {
33882
+ ...r.data.id !== void 0 && { id: r.data.id },
33883
+ ...r.data.parentID !== void 0 && { parentID: r.data.parentID }
33884
+ }
33885
+ },
33886
+ ...r.error !== void 0 && { error: r.error }
33887
+ };
33494
33888
  }
33495
33889
  }
33496
33890
  };