@brainervirus/workit-codex 8.13.0 → 8.14.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.
@@ -9789,7 +9789,7 @@ function acquireFileLockSync(targetPath, options) {
9789
9789
  }
9790
9790
  }
9791
9791
  // packages/workit-core/src/host-session.ts
9792
- import { createHash as createHash4 } from "node:crypto";
9792
+ import { createHash as createHash4, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
9793
9793
  import fs7 from "node:fs";
9794
9794
  import path7 from "node:path";
9795
9795
 
@@ -9881,9 +9881,10 @@ function hostSessionFromEnv(env) {
9881
9881
  return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
9882
9882
  }
9883
9883
  var ISSUED_PER_LEAD = 64;
9884
- var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/2";
9884
+ var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
9885
9885
  var hash = (value) => createHash4("sha256").update(value).digest("hex").slice(0, 32);
9886
9886
  var userIssuedRoot = () => path7.join(resolveStateDir(), "issued");
9887
+ var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
9887
9888
  function migrateRepoIssued(cwd) {
9888
9889
  if (cwd === undefined)
9889
9890
  return;
@@ -9906,7 +9907,12 @@ function migrateRepoIssued(cwd) {
9906
9907
  const to = path7.join(userIssuedRoot(), lead);
9907
9908
  fs7.mkdirSync(to, { recursive: true, mode: 448 });
9908
9909
  for (const id of fs7.readdirSync(from)) {
9909
- fs7.copyFileSync(path7.join(from, id), path7.join(to, id));
9910
+ try {
9911
+ fs7.copyFileSync(path7.join(from, id), path7.join(to, id), fs7.constants.COPYFILE_EXCL);
9912
+ } catch (error) {
9913
+ if (error.code !== "EEXIST")
9914
+ throw error;
9915
+ }
9910
9916
  fs7.rmSync(path7.join(from, id), { force: true });
9911
9917
  }
9912
9918
  removeIfEmpty(from);
@@ -9926,21 +9932,71 @@ var removeIfEmpty = (dir) => {
9926
9932
  fs7.rmdirSync(dir);
9927
9933
  } catch {}
9928
9934
  };
9935
+ var tokenHash = (token) => createHash4("sha256").update(token).digest("hex");
9929
9936
  function recordIssuedSession(cwd, lead, issued) {
9937
+ const token = randomBytes2(18).toString("base64url");
9930
9938
  try {
9931
9939
  migrateRepoIssued(cwd);
9940
+ const own = hash(issued);
9941
+ const tokens = path7.join(userTokenRoot(), hash(lead));
9942
+ fs7.mkdirSync(tokens, { recursive: true, mode: 448 });
9943
+ fs7.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
9944
+ `, { mode: 384, flag: "wx" });
9932
9945
  const dir = path7.join(userIssuedRoot(), hash(lead));
9933
9946
  fs7.mkdirSync(dir, { recursive: true, mode: 448 });
9934
- const own = hash(issued);
9935
- fs7.writeFileSync(path7.join(dir, own), ISSUED_SESSIONS_MARKER);
9947
+ fs7.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
9948
+ `, { mode: 384 });
9949
+ pruneIssued(dir, own, tokens);
9950
+ return token;
9951
+ } catch {
9952
+ return null;
9953
+ }
9954
+ }
9955
+ function pruneIssued(dir, own, tokens) {
9956
+ try {
9936
9957
  const entries = fs7.readdirSync(dir);
9937
9958
  if (entries.length <= ISSUED_PER_LEAD)
9938
9959
  return;
9939
9960
  const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs7.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
9940
- for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
9961
+ for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
9941
9962
  fs7.rmSync(path7.join(dir, name), { force: true });
9963
+ fs7.rmSync(path7.join(tokens, name), { force: true });
9964
+ }
9942
9965
  } catch {}
9943
9966
  }
9967
+ var issuedRecord = (cwd, claimed) => {
9968
+ const cut = claimed.lastIndexOf(":");
9969
+ if (cut <= 0)
9970
+ return null;
9971
+ migrateRepoIssued(cwd);
9972
+ try {
9973
+ return fs7.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
9974
+ } catch {
9975
+ return null;
9976
+ }
9977
+ };
9978
+ var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
9979
+ var issuedToken = (claimed) => {
9980
+ const cut = claimed.lastIndexOf(":");
9981
+ if (cut <= 0)
9982
+ return { stored: null, leadBound: false };
9983
+ const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
9984
+ try {
9985
+ const text = fs7.readFileSync(path7.join(dir, hash(claimed)), "utf8");
9986
+ return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
9987
+ } catch {
9988
+ return { stored: null, leadBound: safeList(dir).length > 0 };
9989
+ }
9990
+ };
9991
+ function issuedBinding(cwd, claimed) {
9992
+ const record = issuedRecord(cwd, claimed);
9993
+ const { stored, leadBound } = issuedToken(claimed);
9994
+ if (stored)
9995
+ return "token";
9996
+ if (record === null)
9997
+ return null;
9998
+ return leadBound ? "token" : "legacy";
9999
+ }
9944
10000
  var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
9945
10001
 
9946
10002
  // packages/workit-core/src/core/package-root.ts
@@ -9971,8 +10027,10 @@ var metadataLockSchema = object({
9971
10027
  var FOREIGN_LOCK_TTL_MS = 10 * 60000;
9972
10028
  var UNREADABLE_LOCK_TTL_MS = 30000;
9973
10029
  var RECLAIM_GUARD_TTL_MS = 30000;
9974
- var defaultLockTimeoutMs = 250;
10030
+ var IN_PROCESS_LOCK_TIMEOUT_MS = 250;
10031
+ var defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
9975
10032
  var defaultLockTimeout = () => defaultLockTimeoutMs;
10033
+ var hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS;
9976
10034
  var parseMetadataLock = (raw) => {
9977
10035
  let value;
9978
10036
  try {
@@ -12245,7 +12303,7 @@ import path15 from "node:path";
12245
12303
 
12246
12304
  // packages/workit-core/src/ledger.ts
12247
12305
  import { spawnSync as spawnSync9 } from "node:child_process";
12248
- import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
12306
+ import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
12249
12307
  import fs12 from "node:fs";
12250
12308
  import path14 from "node:path";
12251
12309
 
@@ -12614,6 +12672,8 @@ var workspaceConfigSchema = object({
12614
12672
  defaultEndpoint: unknown().optional(),
12615
12673
  verification: _enum(["self", "independent"]).optional(),
12616
12674
  trailers: boolean2().optional(),
12675
+ liveSessionGuard: boolean2().optional(),
12676
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
12617
12677
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
12618
12678
  kind,
12619
12679
  union([boolean2(), literal("verified")]).optional()
@@ -12931,24 +12991,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
12931
12991
  const matched = matchRuntimeWorkspace(cwd);
12932
12992
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
12933
12993
  };
12934
- var workspaceTrailers = (cwd, dir) => {
12994
+ var inheritedSetting = (cwd, dir, has) => {
12935
12995
  let matched;
12936
12996
  try {
12937
12997
  matched = matchRuntimeWorkspace(cwd, dir);
12938
12998
  } catch {
12939
- return { enabled: true, workspace: null, from: null };
12999
+ return null;
12940
13000
  }
12941
13001
  if (!matched)
12942
- return { enabled: true, workspace: null, from: null };
13002
+ return null;
12943
13003
  const { selected, matches } = matched;
12944
13004
  const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
12945
- const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
13005
+ return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
13006
+ };
13007
+ var workspaceTrailers = (cwd, dir) => {
13008
+ const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
12946
13009
  return {
12947
- enabled: source?.trailers !== false,
12948
- workspace: selected.name,
12949
- from: source?.name ?? null
13010
+ enabled: found?.source?.trailers !== false,
13011
+ workspace: found?.workspace ?? null,
13012
+ from: found?.source?.name ?? null
12950
13013
  };
12951
13014
  };
13015
+ var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
13016
+ var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
13017
+ var TIER_HOSTS = new Set([
13018
+ "claude_code",
13019
+ "opencode",
13020
+ "codex",
13021
+ "codex_cli",
13022
+ "codex_desktop",
13023
+ "cursor",
13024
+ "pi",
13025
+ "default"
13026
+ ]);
12952
13027
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
12953
13028
  const matched = matchRuntimeWorkspace(cwd);
12954
13029
  if (!matched)
@@ -13636,7 +13711,7 @@ function ledgerPath(cwd) {
13636
13711
  const root = storeRoot(cwd);
13637
13712
  return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
13638
13713
  }
13639
- var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
13714
+ var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
13640
13715
  var NETWORK_FS_TYPES = new Set([
13641
13716
  26985,
13642
13717
  20859,
@@ -13686,14 +13761,14 @@ var ledgerLock = {
13686
13761
  return null;
13687
13762
  throw error;
13688
13763
  }
13689
- const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
13764
+ const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
13690
13765
  fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
13691
13766
  return token;
13692
13767
  },
13693
13768
  release(lock, token) {
13694
13769
  if (lockToken(lock) !== token)
13695
13770
  return false;
13696
- const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
13771
+ const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
13697
13772
  try {
13698
13773
  fs12.renameSync(lock, tomb);
13699
13774
  } catch {
@@ -13710,7 +13785,7 @@ var ledgerLock = {
13710
13785
  },
13711
13786
  token: lockToken,
13712
13787
  reap(lock, observed) {
13713
- const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
13788
+ const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
13714
13789
  try {
13715
13790
  fs12.renameSync(lock, tomb);
13716
13791
  } catch {
@@ -13728,7 +13803,7 @@ var ledgerLock = {
13728
13803
  return false;
13729
13804
  }
13730
13805
  };
13731
- function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
13806
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
13732
13807
  const lock = `${file}.lock`;
13733
13808
  const deadline = Date.now() + waitMs;
13734
13809
  let token = null;
@@ -13786,10 +13861,18 @@ var lastLine = (fd, size) => {
13786
13861
  }
13787
13862
  return null;
13788
13863
  };
13864
+ var tighten = (fd) => {
13865
+ try {
13866
+ if ((fs12.fstatSync(fd).mode & 63) !== 0)
13867
+ fs12.fchmodSync(fd, 384);
13868
+ } catch {}
13869
+ };
13789
13870
  function appendLine(file, build, options) {
13790
13871
  const write = () => {
13791
- const fd = fs12.openSync(file, "a+");
13872
+ const fd = fs12.openSync(file, "a+", 384);
13792
13873
  try {
13874
+ if (process.platform !== "win32")
13875
+ tighten(fd);
13793
13876
  const size = fs12.fstatSync(fd).size;
13794
13877
  let fence = "";
13795
13878
  if (size > 0) {
@@ -13884,7 +13967,9 @@ function readLedger(cwd, options = {}) {
13884
13967
  if (!file.ok)
13885
13968
  return file;
13886
13969
  let text = "";
13970
+ let stat = null;
13887
13971
  try {
13972
+ stat = fs12.statSync(file.value);
13888
13973
  text = fs12.readFileSync(file.value, "utf8");
13889
13974
  } catch (error) {
13890
13975
  if (error.code === "ENOENT")
@@ -13904,6 +13989,7 @@ function readLedger(cwd, options = {}) {
13904
13989
  const integrity = { chained: 0, legacy: 0, unverified: [] };
13905
13990
  const earlier = new Set([null]);
13906
13991
  let chainStarted = false;
13992
+ const ids = new Set;
13907
13993
  for (const raw of text.split(`
13908
13994
  `)) {
13909
13995
  const trimmed = raw.trim();
@@ -13937,6 +14023,10 @@ function readLedger(cwd, options = {}) {
13937
14023
  reason = "unsigned";
13938
14024
  else
13939
14025
  rowIntegrity = "legacy";
14026
+ if (options.integrity && !reason && ids.has(parsed.id))
14027
+ reason = "duplicate_id";
14028
+ if (options.integrity)
14029
+ ids.add(parsed.id);
13940
14030
  if (reason)
13941
14031
  rowIntegrity = "unverified";
13942
14032
  const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
@@ -13960,7 +14050,8 @@ function readLedger(cwd, options = {}) {
13960
14050
  seq: rows.length + 1,
13961
14051
  superseded: false,
13962
14052
  supersedeIgnored: false,
13963
- ...rowIntegrity ? { integrity: rowIntegrity } : {}
14053
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
14054
+ ...reason ? { integrityReason: reason } : {}
13964
14055
  };
13965
14056
  rows.push(row);
13966
14057
  if (!options.integrity)
@@ -13988,18 +14079,48 @@ function readLedger(cwd, options = {}) {
13988
14079
  const byId = new Map;
13989
14080
  for (const row of rows) {
13990
14081
  const target = row.supersedes ? byId.get(row.supersedes) : undefined;
13991
- if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
14082
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
13992
14083
  target.superseded = true;
13993
14084
  else if (row.supersedes)
13994
14085
  row.supersedeIgnored = true;
13995
14086
  if (!byId.has(row.id))
13996
14087
  byId.set(row.id, row);
13997
14088
  }
14089
+ if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
14090
+ tamperCache.set(file.value, {
14091
+ size: stat.size,
14092
+ stamp: fileStamp(stat),
14093
+ breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
14094
+ });
13998
14095
  return {
13999
14096
  ok: true,
14000
14097
  value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
14001
14098
  };
14002
14099
  }
14100
+ var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
14101
+ var tamperCache = new Map;
14102
+ var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
14103
+ function ledgerTampering(cwd) {
14104
+ const file = ledgerPath(cwd);
14105
+ if (!file.ok)
14106
+ return [];
14107
+ try {
14108
+ const stat = fs12.statSync(file.value);
14109
+ const cached = tamperCache.get(file.value);
14110
+ if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
14111
+ return cached.breaks;
14112
+ } catch {
14113
+ return [];
14114
+ }
14115
+ const read = readLedger(cwd, { integrity: true });
14116
+ return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
14117
+ }
14118
+ function tamperedUnblock(cwd) {
14119
+ const file = ledgerPath(cwd);
14120
+ const where = file.ok ? file.value : "the ledger file";
14121
+ return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
14122
+ }
14123
+ var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
14003
14124
  function verdictBasis(verdict, current) {
14004
14125
  if (verdict.head && verdict.head === current.head)
14005
14126
  return "fresh";
@@ -14007,7 +14128,7 @@ function verdictBasis(verdict, current) {
14007
14128
  return "carried";
14008
14129
  return "stale";
14009
14130
  }
14010
- var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
14131
+ var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
14011
14132
  function trailerSessions(cwd, base, head) {
14012
14133
  if (!base || !head || !safeRef(base) || !safeRef(head))
14013
14134
  return [];
@@ -14088,6 +14209,56 @@ var authoredBy = (authors, session) => {
14088
14209
  return true;
14089
14210
  return false;
14090
14211
  };
14212
+ var TRUST_REASONS = new Set([
14213
+ "unsigned",
14214
+ "hash_mismatch",
14215
+ "broken_link",
14216
+ "duplicate_id",
14217
+ "unissued_session",
14218
+ "unbound_session"
14219
+ ]);
14220
+ var SHELL_VOUCHED_HOSTS = new Set([
14221
+ "cli",
14222
+ "opencode",
14223
+ "pi",
14224
+ "codex_cli",
14225
+ "codex_desktop"
14226
+ ]);
14227
+ function sessionTrust(cwd, row) {
14228
+ const session = row.actor.session;
14229
+ if (!session || row.self === true)
14230
+ return null;
14231
+ const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
14232
+ const host = row.actor.host;
14233
+ const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
14234
+ if (!issuedOnly)
14235
+ return null;
14236
+ const binding = issuedBinding(cwd, session);
14237
+ if (binding === null)
14238
+ return "unissued_session";
14239
+ return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
14240
+ }
14241
+ function trustReasons(cwd, row) {
14242
+ if (row.integrity === "unverified")
14243
+ return [row.integrityReason ?? "unsigned"];
14244
+ if (row.integrity !== "verified")
14245
+ return ["unsigned"];
14246
+ const session = sessionTrust(cwd, row);
14247
+ return session ? [session] : [];
14248
+ }
14249
+ function withIntegrity(cwd, rows) {
14250
+ if (rows.every((row) => row.integrity !== undefined))
14251
+ return rows;
14252
+ const read = readLedger(cwd, { integrity: true });
14253
+ const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
14254
+ return rows.map((row) => {
14255
+ if (row.integrity !== undefined)
14256
+ return row;
14257
+ const fresh = bySeq.get(row.seq);
14258
+ return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
14259
+ });
14260
+ }
14261
+ var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
14091
14262
  var independenceReasons = (row, authors) => {
14092
14263
  const reasons = [];
14093
14264
  if (!row.actor.session)
@@ -14098,15 +14269,17 @@ var independenceReasons = (row, authors) => {
14098
14269
  reasons.push("self");
14099
14270
  return reasons;
14100
14271
  };
14101
- function effectiveOf(rows, authors, sameCode, isCurrent) {
14272
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
14102
14273
  let effective = null;
14103
14274
  for (const row of rows) {
14104
14275
  if (!effective) {
14105
14276
  effective = row;
14106
14277
  continue;
14107
14278
  }
14108
- const rowIndependent = independenceReasons(row, authors).length === 0;
14109
- const effIndependent = independenceReasons(effective, authors).length === 0;
14279
+ if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
14280
+ continue;
14281
+ const rowIndependent = reasonsOf(row).length === 0;
14282
+ const effIndependent = reasonsOf(effective).length === 0;
14110
14283
  if (effIndependent && !rowIndependent && isCurrent(effective))
14111
14284
  continue;
14112
14285
  if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
@@ -14120,7 +14293,7 @@ function restackCarries(rows, branch, from, to) {
14120
14293
  return false;
14121
14294
  const edges = new Map;
14122
14295
  for (const row of rows)
14123
- if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
14296
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
14124
14297
  edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
14125
14298
  const seen = new Set([from]);
14126
14299
  const queue = [from];
@@ -14136,7 +14309,8 @@ function restackCarries(rows, branch, from, to) {
14136
14309
  }
14137
14310
  return false;
14138
14311
  }
14139
- function checkVerdicts(cwd, branch, rows) {
14312
+ function checkVerdicts(cwd, branch, ledgerRows) {
14313
+ const rows = withIntegrity(cwd, ledgerRows);
14140
14314
  const head = branchHead(cwd, branch);
14141
14315
  const fallbackBase = defaultBase(cwd);
14142
14316
  const scope = branchScope(cwd, branch);
@@ -14166,21 +14340,47 @@ function checkVerdicts(cwd, branch, rows) {
14166
14340
  return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
14167
14341
  };
14168
14342
  const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
14343
+ const failureAuthorCache = new Map;
14344
+ const failureAuthors = (row) => {
14345
+ let set = failureAuthorCache.get(row.seq);
14346
+ if (!set) {
14347
+ set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
14348
+ failureAuthorCache.set(row.seq, set);
14349
+ }
14350
+ return set;
14351
+ };
14352
+ const reasonCache = new Map;
14353
+ const reasonsOf = (row) => {
14354
+ let reasons = reasonCache.get(row);
14355
+ if (!reasons) {
14356
+ const failing = FAILING.has(String(row.result));
14357
+ reasons = [
14358
+ ...trustReasons(cwd, row),
14359
+ ...independenceReasons(row, failing ? failureAuthors(row) : authors)
14360
+ ];
14361
+ reasonCache.set(row, reasons);
14362
+ }
14363
+ return reasons;
14364
+ };
14365
+ const retired = new Set;
14366
+ for (const row of rows)
14367
+ if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
14368
+ retired.add(row.supersedes);
14169
14369
  const byKind = new Map;
14170
14370
  for (const row of rows)
14171
- if (isLiveVerdict(row) && scope(row)) {
14371
+ if (isLiveVerdict(row, retired) && scope(row)) {
14172
14372
  const kind = typeof row.kind === "string" ? row.kind : "review";
14173
14373
  byKind.set(kind, [...byKind.get(kind) ?? [], row]);
14174
14374
  }
14175
14375
  const verdicts = [];
14176
14376
  const warnings = [];
14177
14377
  for (const [kind, list] of byKind) {
14178
- const row = effectiveOf(list, authors, sameCode, (candidate) => basisOf(candidate) !== "stale");
14378
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
14179
14379
  if (!row)
14180
14380
  continue;
14181
14381
  const basis = basisOf(row);
14182
14382
  const current = basis !== "stale";
14183
- const independence = independenceReasons(row, authors);
14383
+ const independence = reasonsOf(row);
14184
14384
  const reasons = [
14185
14385
  ...current ? [] : ["stale"],
14186
14386
  ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
@@ -14197,23 +14397,31 @@ function checkVerdicts(cwd, branch, rows) {
14197
14397
  });
14198
14398
  if (reasons.length === 0) {
14199
14399
  for (const later of list)
14200
- if (later.seq > row.seq && independenceReasons(later, authors).length > 0 && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
14400
+ if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
14201
14401
  warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
14202
14402
  }
14203
14403
  }
14204
14404
  for (const row of rows)
14205
14405
  if (row.integrity === "unverified" && scope(row))
14206
14406
  warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
14407
+ const tampered = ledgerTampering(cwd);
14408
+ if (tampered.length) {
14409
+ warnings.push(tamperedWarning(cwd, tampered));
14410
+ for (const entry of verdicts) {
14411
+ entry.accepted = false;
14412
+ entry.reasons = [...entry.reasons, "ledger_tampered"];
14413
+ }
14414
+ }
14207
14415
  verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
14208
14416
  const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
14209
14417
  const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
14210
14418
  const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
14211
- const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
14419
+ const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
14212
14420
  accepted: false,
14213
14421
  verdict: failing?.verdict ?? null,
14214
14422
  reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
14215
14423
  };
14216
- const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
14424
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
14217
14425
  return {
14218
14426
  branch,
14219
14427
  head,
@@ -20027,27 +20235,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
20027
20235
  };
20028
20236
  // packages/workit-core/src/hooks/write-gate.ts
20029
20237
  import { realpathSync as realpathSync5 } from "node:fs";
20238
+ import os7 from "node:os";
20239
+ import path23 from "node:path";
20240
+
20241
+ // packages/workit-core/src/core/live-session.ts
20242
+ import { spawnSync as spawnSync13 } from "node:child_process";
20243
+ import { createHash as createHash15 } from "node:crypto";
20244
+ import fs17 from "node:fs";
20245
+ import os6 from "node:os";
20030
20246
  import path22 from "node:path";
20247
+ var LIVE_SESSION_TTL_MS = 15 * 60000;
20248
+ var HEARTBEAT_EVERY_MS = 30000;
20249
+ var liveProbe = {
20250
+ platform: process.platform,
20251
+ run: (args) => spawnSync13("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
20252
+ start: (pid) => processStartOf(pid)
20253
+ };
20254
+ var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
20255
+ var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
20256
+ var processAlive = (pid) => {
20257
+ try {
20258
+ process.kill(pid, 0);
20259
+ return true;
20260
+ } catch (error) {
20261
+ return error.code === "EPERM";
20262
+ }
20263
+ };
20264
+ var processInfo = (pid) => {
20265
+ try {
20266
+ if (liveProbe.platform === "linux") {
20267
+ const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
20268
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20269
+ const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20270
+ return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20271
+ }
20272
+ if (liveProbe.platform === "win32")
20273
+ return null;
20274
+ const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
20275
+ return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
20276
+ } catch {
20277
+ return null;
20278
+ }
20279
+ };
20280
+ var WRAPPER = /^(?:\S*\/)?(?:sh|bash|dash|zsh|ash|ksh|fish|env|timeout|npx|npm|bunx|cmd\.exe|pwsh|powershell)(?:\s|$)|launch\.mjs|npx-cli\.js/u;
20281
+ var PIDS = new Map;
20282
+ var hostPid = (transport) => {
20283
+ if (transport !== "hook-process")
20284
+ return process.pid;
20285
+ const key = `${liveProbe.platform}\x00${process.ppid}`;
20286
+ if (PIDS.has(key))
20287
+ return PIDS.get(key) ?? null;
20288
+ if (liveProbe.platform === "win32") {
20289
+ PIDS.set(key, null);
20290
+ return null;
20291
+ }
20292
+ let pid = process.ppid;
20293
+ for (let hop = 0;hop < 4; hop++) {
20294
+ const info = processInfo(pid);
20295
+ if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
20296
+ break;
20297
+ pid = info.ppid;
20298
+ }
20299
+ PIDS.set(key, pid);
20300
+ return pid;
20301
+ };
20302
+ var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
20303
+ var liveOwner = (input, transport, env = process.env) => {
20304
+ const session = input.session.id?.trim();
20305
+ if (!session)
20306
+ return null;
20307
+ const leads = new Set([leadOf(sessionSafe(session))]);
20308
+ if (input.session.parentId)
20309
+ leads.add(leadOf(sessionSafe(input.session.parentId)));
20310
+ if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
20311
+ leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
20312
+ return {
20313
+ host: input.host,
20314
+ session: sessionSafe(session),
20315
+ leads: [...leads],
20316
+ pid: () => hostPid(transport)
20317
+ };
20318
+ };
20319
+ var checkoutOf = (target) => {
20320
+ let dir = path22.resolve(target);
20321
+ while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20322
+ dir = path22.dirname(dir);
20323
+ if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20324
+ dir = path22.dirname(dir);
20325
+ const parts = dir.split(path22.sep);
20326
+ const dotGit = parts.lastIndexOf(".git");
20327
+ if (dotGit > 0)
20328
+ dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
20329
+ const location = resolveStore(dir);
20330
+ if (location instanceof Error || !location.shared || !location.top)
20331
+ return null;
20332
+ let top = location.top;
20333
+ try {
20334
+ top = fs17.realpathSync.native(top);
20335
+ } catch {}
20336
+ return top;
20337
+ };
20338
+ var markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
20339
+ var checkoutKey = (top, platform = process.platform) => {
20340
+ let key = path22.resolve(top);
20341
+ try {
20342
+ key = fs17.realpathSync.native(key);
20343
+ } catch {}
20344
+ return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20345
+ };
20346
+ var readMarker = (file) => {
20347
+ try {
20348
+ const value = JSON.parse(fs17.readFileSync(file, "utf8"));
20349
+ if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20350
+ return null;
20351
+ return {
20352
+ version: 1,
20353
+ session: value.session,
20354
+ leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
20355
+ host: value.host,
20356
+ pid: typeof value.pid === "number" ? value.pid : null,
20357
+ pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
20358
+ machine: typeof value.machine === "string" ? value.machine : "",
20359
+ checkout: typeof value.checkout === "string" ? value.checkout : "",
20360
+ heartbeatAt: value.heartbeatAt
20361
+ };
20362
+ } catch {
20363
+ return null;
20364
+ }
20365
+ };
20366
+ var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
20367
+ var isLive = (marker, deps) => {
20368
+ const age = ageOf2(marker, deps.now ?? new Date);
20369
+ if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20370
+ return false;
20371
+ if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
20372
+ return true;
20373
+ if (!(deps.alive ?? processAlive)(marker.pid))
20374
+ return false;
20375
+ if (marker.pidStart === null)
20376
+ return true;
20377
+ const start = liveProbe.start(marker.pid);
20378
+ return start === null || start === marker.pidStart;
20379
+ };
20380
+ var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
20381
+ var sameOwner = (marker, owner, machine) => {
20382
+ if (sameLead(marker, owner))
20383
+ return true;
20384
+ if (marker.machine !== machine)
20385
+ return false;
20386
+ const pid = owner.pid();
20387
+ if (pid === null || marker.pid === null || marker.pidStart === null)
20388
+ return familyOf(marker.host) === familyOf(owner.host);
20389
+ return marker.pid === pid;
20390
+ };
20391
+ var liveConflict = (target, owner, deps = {}) => {
20392
+ try {
20393
+ const checkout = checkoutOf(target);
20394
+ if (!checkout)
20395
+ return null;
20396
+ const file = markerFile2(checkout);
20397
+ const marker = readMarker(file);
20398
+ if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20399
+ return null;
20400
+ if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
20401
+ return null;
20402
+ return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20403
+ } catch {
20404
+ return null;
20405
+ }
20406
+ };
20407
+ var claimLiveSession = (target, owner, deps = {}) => {
20408
+ try {
20409
+ const checkout = checkoutOf(target);
20410
+ if (!checkout)
20411
+ return;
20412
+ const file = markerFile2(checkout);
20413
+ const now = deps.now ?? new Date;
20414
+ const machine = deps.machine ?? os6.hostname();
20415
+ const current = readMarker(file);
20416
+ if (current) {
20417
+ const age = ageOf2(current, now);
20418
+ if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
20419
+ return;
20420
+ if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
20421
+ return;
20422
+ }
20423
+ const pid = owner.pid();
20424
+ const marker = {
20425
+ version: 1,
20426
+ session: owner.session,
20427
+ leads: owner.leads,
20428
+ host: owner.host,
20429
+ pid,
20430
+ pidStart: pid === null ? null : liveProbe.start(pid),
20431
+ machine,
20432
+ checkout,
20433
+ heartbeatAt: now.toISOString()
20434
+ };
20435
+ fs17.mkdirSync(path22.dirname(file), { recursive: true });
20436
+ const temp = `${file}.${process.pid}.tmp`;
20437
+ fs17.writeFileSync(temp, JSON.stringify(marker));
20438
+ fs17.renameSync(temp, file);
20439
+ } catch {}
20440
+ };
20441
+
20442
+ // packages/workit-core/src/hooks/write-gate.ts
20031
20443
  var NONE3 = { kind: "none" };
20032
20444
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20033
20445
  var canonical2 = (file) => {
20034
20446
  let rest = "";
20035
- for (let dir = path22.resolve(file);; dir = path22.dirname(dir)) {
20447
+ for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
20036
20448
  try {
20037
- return path22.join(realpathSync5(dir), rest);
20449
+ return path23.join(realpathSync5(dir), rest);
20038
20450
  } catch {
20039
- if (path22.dirname(dir) === dir)
20040
- return path22.resolve(file);
20041
- rest = rest ? path22.join(path22.basename(dir), rest) : path22.basename(dir);
20451
+ if (path23.dirname(dir) === dir)
20452
+ return path23.resolve(file);
20453
+ rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
20042
20454
  }
20043
20455
  }
20044
20456
  };
20045
20457
  var exempt = (cwd, root, file, plans) => {
20046
20458
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
20047
- const relative = path22.relative(fold(canonical2(root)), fold(canonical2(path22.resolve(cwd, file))));
20048
- if (relative.startsWith("..") || path22.isAbsolute(relative))
20459
+ const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20460
+ if (relative.startsWith("..") || path23.isAbsolute(relative))
20049
20461
  return true;
20050
- const posix = relative.split(path22.sep).join("/");
20462
+ const posix = relative.split(path23.sep).join("/");
20051
20463
  const segments = posix.split("/");
20052
20464
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20053
20465
  };
@@ -20145,7 +20557,7 @@ var shellWrites = (command) => {
20145
20557
  return { writes: true, targets: unknown ? null : targets };
20146
20558
  };
20147
20559
  var cache2 = new Map;
20148
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path22.resolve(root, file)) ?? "-").join("\x00");
20560
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00");
20149
20561
  var gateFor = (cwd) => {
20150
20562
  const store = new TaskStore(cwd);
20151
20563
  const listed = store.listTaskIndex();
@@ -20183,7 +20595,218 @@ var gateFor = (cwd) => {
20183
20595
  cache2.delete(cache2.keys().next().value);
20184
20596
  return gate;
20185
20597
  };
20186
- function writeGate(cwd, paths) {
20598
+ var MUTATING_GIT = new Set([
20599
+ "add",
20600
+ "am",
20601
+ "apply",
20602
+ "checkout",
20603
+ "cherry-pick",
20604
+ "clean",
20605
+ "commit",
20606
+ "merge",
20607
+ "mv",
20608
+ "pull",
20609
+ "rebase",
20610
+ "reset",
20611
+ "restore",
20612
+ "revert",
20613
+ "rm",
20614
+ "stash",
20615
+ "switch"
20616
+ ]);
20617
+ var DRY_N = new Set(["add", "clean", "rm", "mv"]);
20618
+ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
20619
+ var DIR_TARGET = ".workit-live-target";
20620
+ var OVERRIDE = "WORKIT_LIVE_SESSION=override";
20621
+ var inert = (args, sub) => args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run" || DRY_N.has(sub) && /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg) && !arg.startsWith("--"));
20622
+ var gitMutationDirs = (cwd, command, dialect) => {
20623
+ let invocations;
20624
+ try {
20625
+ invocations = rawInvocations(command, dialect);
20626
+ } catch {
20627
+ return [];
20628
+ }
20629
+ return invocations.flatMap((invocation) => {
20630
+ if (invocation.tool !== "git" || invocation.dir === undefined)
20631
+ return [];
20632
+ const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
20633
+ const sub = invocation.args[at] ?? "";
20634
+ const rest = invocation.args.slice(at + 1);
20635
+ if (!MUTATING_GIT.has(sub) || inert(rest, sub))
20636
+ return [];
20637
+ if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20638
+ return [];
20639
+ return [path23.resolve(cwd, invocation.dir ?? ".")];
20640
+ });
20641
+ };
20642
+ var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
20643
+ var PUSHD2 = new Set(["pushd", "push-location"]);
20644
+ var POPD2 = new Set(["popd", "pop-location"]);
20645
+ var cdInto = (dir, words) => {
20646
+ const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20647
+ if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20648
+ return;
20649
+ return target === undefined ? os7.homedir() : path23.resolve(dir, target);
20650
+ };
20651
+ var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
20652
+ var segmentTargets = (cwd, command, dialect) => {
20653
+ const targets = [];
20654
+ let dir = cwd;
20655
+ let stack = [];
20656
+ const groups = [];
20657
+ const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
20658
+ for (const segment of segmentsOf(command, dialect)) {
20659
+ if (segment.group === "open") {
20660
+ groups.push({ dir, stack: [...stack] });
20661
+ continue;
20662
+ }
20663
+ if (segment.group === "close") {
20664
+ const saved = groups.pop();
20665
+ if (saved)
20666
+ ({ dir, stack } = saved);
20667
+ continue;
20668
+ }
20669
+ const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
20670
+ const words = lead < 0 ? [] : segment.words.slice(lead);
20671
+ const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20672
+ if (CD2.has(name)) {
20673
+ dir = cdInto(dir, words);
20674
+ continue;
20675
+ }
20676
+ if (PUSHD2.has(name)) {
20677
+ stack.push(dir);
20678
+ dir = cdInto(dir, words);
20679
+ continue;
20680
+ }
20681
+ if (POPD2.has(name)) {
20682
+ dir = stack.length ? stack.pop() : undefined;
20683
+ continue;
20684
+ }
20685
+ for (const file of segment.redirects)
20686
+ if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20687
+ const resolved = at(file);
20688
+ if (resolved)
20689
+ targets.push(resolved);
20690
+ }
20691
+ const edits = editsOf(segment.words);
20692
+ if (edits === null) {
20693
+ if (dir !== undefined)
20694
+ targets.push(path23.join(dir, DIR_TARGET));
20695
+ } else
20696
+ for (const file of edits) {
20697
+ const resolved = at(file);
20698
+ if (resolved)
20699
+ targets.push(resolved);
20700
+ }
20701
+ if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
20702
+ targets.push(path23.join(dir, DIR_TARGET));
20703
+ }
20704
+ return targets;
20705
+ };
20706
+ var overridden = (command, dialect) => {
20707
+ const segments = segmentsOf(command, dialect);
20708
+ if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
20709
+ return false;
20710
+ const words = segments[0].words;
20711
+ const lead = words.findIndex((word) => !/^\w+=/.test(word));
20712
+ return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
20713
+ };
20714
+ var HOST_LABELS = {
20715
+ claude_code: "Claude Code",
20716
+ opencode: "OpenCode",
20717
+ codex_cli: "Codex CLI",
20718
+ codex_desktop: "Codex Desktop",
20719
+ cursor: "Cursor",
20720
+ pi: "Pi"
20721
+ };
20722
+ var ageText = (ms) => {
20723
+ const seconds = Math.max(0, Math.round(ms / 1000));
20724
+ return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
20725
+ };
20726
+ var liveDeny = ({ marker, checkout, ageMs, file }) => {
20727
+ const unblock = `Work in your own worktree (\`git worktree add <path> -b <branch>\`, or \`workit fanout worktree create <slice>\`). Only if the user confirms that session is finished: release it with \`rm '${file}'\`, or prefix one command with ${OVERRIDE}.`;
20728
+ return {
20729
+ kind: "deny",
20730
+ reason: `workit live-session guard: ${checkout} belongs to another live agent session, ${HOST_LABELS[marker.host] ?? marker.host} session ${marker.session}${marker.pid === null ? "" : ` (pid ${marker.pid})`}, last active ${ageText(ageMs)} ago. Do not edit a checkout another session is working in; read-only commands stay allowed. ${unblock}`,
20731
+ unblock
20732
+ };
20733
+ };
20734
+ var guarded = (targets) => {
20735
+ const seen = new Set;
20736
+ return targets.filter((target) => {
20737
+ const dir = path23.dirname(target);
20738
+ if (seen.has(dir))
20739
+ return false;
20740
+ seen.add(dir);
20741
+ return true;
20742
+ });
20743
+ };
20744
+ var liveGate = (owner, targets, deps) => {
20745
+ try {
20746
+ for (const target of guarded(targets)) {
20747
+ const conflict = liveConflict(target, owner, deps);
20748
+ if (conflict && workspaceLiveSessionGuard(conflict.checkout))
20749
+ return liveDeny(conflict);
20750
+ }
20751
+ } catch {}
20752
+ return NONE3;
20753
+ };
20754
+ var claimAll = (owner, targets, deps) => {
20755
+ for (const target of guarded(targets))
20756
+ claimLiveSession(target, owner, deps);
20757
+ };
20758
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)];
20759
+ function writeGate(cwd, paths, owner, deps) {
20760
+ if (!owner)
20761
+ return beforeWriteGate(cwd, paths);
20762
+ const targets = liveTargets(cwd, paths);
20763
+ const live = liveGate(owner, targets, deps);
20764
+ if (live.kind !== "none")
20765
+ return live;
20766
+ const decision = beforeWriteGate(cwd, paths);
20767
+ if (decision.kind === "none")
20768
+ claimAll(owner, targets, deps);
20769
+ return decision;
20770
+ }
20771
+ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20772
+ const writes = shellWrites(command);
20773
+ let targets = [];
20774
+ if (owner) {
20775
+ try {
20776
+ targets = [
20777
+ ...segmentTargets(cwd, command, dialect),
20778
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
20779
+ ];
20780
+ } catch {
20781
+ targets = [];
20782
+ }
20783
+ }
20784
+ if (owner && targets.length) {
20785
+ const live = liveGate(owner, targets, deps);
20786
+ if (live.kind !== "none") {
20787
+ if (!overridden(command, dialect))
20788
+ return live;
20789
+ noteOverride(cwd, owner, command);
20790
+ }
20791
+ }
20792
+ const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
20793
+ if (owner && targets.length && decision.kind === "none")
20794
+ claimAll(owner, targets, deps);
20795
+ return decision;
20796
+ }
20797
+ var noteOverride = (cwd, owner, command) => {
20798
+ try {
20799
+ if (!inWorkitWorkspace(cwd))
20800
+ return;
20801
+ appendHookObserved(cwd, {
20802
+ type: "live_session.override",
20803
+ actor: { host: owner.host, session: owner.session, agentId: null },
20804
+ session: owner.session,
20805
+ command: command.slice(0, 200)
20806
+ });
20807
+ } catch {}
20808
+ };
20809
+ function beforeWriteGate(cwd, paths) {
20187
20810
  try {
20188
20811
  const gate = gateFor(cwd);
20189
20812
  if (!gate || gate.blockers.length === 0)
@@ -20201,7 +20824,7 @@ function writeGate(cwd, paths) {
20201
20824
  }
20202
20825
  }
20203
20826
  // packages/workit-core/src/hooks/stop.ts
20204
- import { spawnSync as spawnSync13 } from "node:child_process";
20827
+ import { spawnSync as spawnSync14 } from "node:child_process";
20205
20828
  var NONE4 = { kind: "none" };
20206
20829
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
20207
20830
  var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
@@ -20222,7 +20845,7 @@ var asksUser = (message) => {
20222
20845
  };
20223
20846
  var short = (sha) => (sha ?? "?").slice(0, 12);
20224
20847
  var gitList = (cwd, args) => {
20225
- const run = spawnSync13("git", args, {
20848
+ const run = spawnSync14("git", args, {
20226
20849
  cwd,
20227
20850
  encoding: "utf8",
20228
20851
  timeout: 3000,
@@ -20313,10 +20936,10 @@ function stopObligation(input, descriptor) {
20313
20936
  const branch = currentBranch(input.cwd);
20314
20937
  if (!branch || guardedBranch(input.cwd, branch))
20315
20938
  return null;
20316
- const ledger = readLedger(input.cwd);
20939
+ const ledger = readLedger(input.cwd, { integrity: true });
20317
20940
  if (!ledger.ok)
20318
20941
  return null;
20319
- const all = ledger.value.rows;
20942
+ const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
20320
20943
  const rows = all.filter((row) => row.branch === branch);
20321
20944
  const mine = rows.filter((row) => sessionOf(row) === session);
20322
20945
  const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
@@ -20586,12 +21209,12 @@ ${line}` };
20586
21209
  };
20587
21210
  // packages/workit-core/src/hooks/hosts/fields.ts
20588
21211
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20589
- import path23 from "node:path";
21212
+ import path24 from "node:path";
20590
21213
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
20591
21214
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
20592
21215
  var optionalText = (value) => typeof value === "string" ? value : null;
20593
21216
  var existingDirectory = (value) => {
20594
- if (!nonEmpty2(value) || !path23.isAbsolute(value) || !existsSync7(value))
21217
+ if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
20595
21218
  return null;
20596
21219
  try {
20597
21220
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -20692,9 +21315,9 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
20692
21315
  if (role !== "judge" || !event.agentId)
20693
21316
  return readOnly;
20694
21317
  const own = subagentSession(session, event.agentId);
20695
- if (session)
20696
- recordIssuedSession(input.cwd || process.cwd(), session, own);
20697
- return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> --session ${own} ...\` so the ledger tells it apart from the author.`;
21318
+ const token = session ? recordIssuedSession(input.cwd || process.cwd(), session, own) : null;
21319
+ const flags = token ? `--session ${own} --token ${token}` : `--session ${own}`;
21320
+ return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> ${flags} ...\` so the ledger tells it apart from the author.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
20698
21321
  };
20699
21322
  function handleHook(input, deps) {
20700
21323
  const { descriptor } = deps;
@@ -20736,8 +21359,7 @@ function handleHook(input, deps) {
20736
21359
  const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
20737
21360
  if (raw.kind === "deny")
20738
21361
  return raw;
20739
- const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
20740
- const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
21362
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
20741
21363
  if (gate.kind === "deny")
20742
21364
  return gate;
20743
21365
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
@@ -20750,7 +21372,7 @@ function handleHook(input, deps) {
20750
21372
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
20751
21373
  return NONE5;
20752
21374
  case "write.pre":
20753
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
21375
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
20754
21376
  case "subagent.start":
20755
21377
  return {
20756
21378
  kind: "context",
@@ -21329,8 +21951,8 @@ var codexAdapter = {
21329
21951
  };
21330
21952
 
21331
21953
  // packages/workit-core/src/hooks/hosts/cursor.ts
21332
- import fs17 from "node:fs";
21333
- import path24 from "node:path";
21954
+ import fs18 from "node:fs";
21955
+ import path25 from "node:path";
21334
21956
  var undocumented = { support: "undocumented", native: null };
21335
21957
  var none = { support: "none", native: null };
21336
21958
  var CURSOR_DESCRIPTOR = {
@@ -21527,17 +22149,17 @@ var protocolEvent2 = (input) => {
21527
22149
  };
21528
22150
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
21529
22151
  var lastAssistantText = (file) => {
21530
- if (!file || !path24.isAbsolute(file))
22152
+ if (!file || !path25.isAbsolute(file))
21531
22153
  return null;
21532
22154
  try {
21533
- const size = fs17.statSync(file).size;
22155
+ const size = fs18.statSync(file).size;
21534
22156
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21535
22157
  const buffer = Buffer.alloc(size - start);
21536
- const fd = fs17.openSync(file, "r");
22158
+ const fd = fs18.openSync(file, "r");
21537
22159
  try {
21538
- fs17.readSync(fd, buffer, 0, buffer.length, start);
22160
+ fs18.readSync(fd, buffer, 0, buffer.length, start);
21539
22161
  } finally {
21540
- fs17.closeSync(fd);
22162
+ fs18.closeSync(fd);
21541
22163
  }
21542
22164
  const lines = buffer.toString("utf8").split(`
21543
22165
  `);
@@ -21564,7 +22186,7 @@ var lastAssistantText = (file) => {
21564
22186
  return null;
21565
22187
  }
21566
22188
  };
21567
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path24.sep) ? root : `${root}${path24.sep}`);
22189
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`);
21568
22190
  var hookCwd = (input) => {
21569
22191
  const cwd = existingDirectory(input.cwd);
21570
22192
  if (cwd && input.hook_event_name === "beforeShellExecution")