@brainervirus/workit-cli 8.13.0 → 8.13.1

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 (2) hide show
  1. package/dist/index.js +610 -207
  2. package/package.json +5 -4
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "@brainervirus/workit-cli",
68
- version: "8.13.0",
68
+ version: "8.13.1",
69
69
  private: false,
70
70
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
71
71
  keywords: [
@@ -103,13 +103,14 @@ var init_package = __esm(() => {
103
103
  },
104
104
  dependencies: {
105
105
  "@babel/parser": "7.29.9",
106
- "@brainervirus/workit-core": "^8.13.0",
107
- "@brainervirus/workit-cursor": "^8.13.0",
108
- "@brainervirus/workit-opencode": "^8.13.0",
106
+ "@brainervirus/workit-cursor": "^8.13.1",
109
107
  "@inkjs/ui": "2.0.0",
110
108
  ink: "7.1.1",
111
109
  react: "19.2.8"
112
110
  },
111
+ devDependencies: {
112
+ "@brainervirus/workit-core": "^8.13.1"
113
+ },
113
114
  engines: {
114
115
  node: ">=24"
115
116
  }
@@ -10429,7 +10430,7 @@ var init_paths = __esm(() => {
10429
10430
  });
10430
10431
 
10431
10432
  // packages/workit-core/src/host-session.ts
10432
- import { createHash as createHash3 } from "node:crypto";
10433
+ import { createHash as createHash3, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
10433
10434
  import fs6 from "node:fs";
10434
10435
  import path6 from "node:path";
10435
10436
  function hostSessionFromEnv(env) {
@@ -10482,7 +10483,12 @@ function migrateRepoIssued(cwd) {
10482
10483
  const to = path6.join(userIssuedRoot(), lead);
10483
10484
  fs6.mkdirSync(to, { recursive: true, mode: 448 });
10484
10485
  for (const id of fs6.readdirSync(from)) {
10485
- fs6.copyFileSync(path6.join(from, id), path6.join(to, id));
10486
+ try {
10487
+ fs6.copyFileSync(path6.join(from, id), path6.join(to, id), fs6.constants.COPYFILE_EXCL);
10488
+ } catch (error) {
10489
+ if (error.code !== "EEXIST")
10490
+ throw error;
10491
+ }
10486
10492
  fs6.rmSync(path6.join(from, id), { force: true });
10487
10493
  }
10488
10494
  removeIfEmpty(from);
@@ -10490,7 +10496,25 @@ function migrateRepoIssued(cwd) {
10490
10496
  }
10491
10497
  removeIfEmpty(old);
10492
10498
  }
10493
- function sessionClaimRefusal(env, claimed, cwd) {
10499
+ function issuedBinding(cwd, claimed) {
10500
+ const record = issuedRecord(cwd, claimed);
10501
+ const { stored, leadBound } = issuedToken(claimed);
10502
+ if (stored)
10503
+ return "token";
10504
+ if (record === null)
10505
+ return null;
10506
+ return leadBound ? "token" : "legacy";
10507
+ }
10508
+ function issuedTokenMatches(cwd, claimed, token) {
10509
+ migrateRepoIssued(cwd);
10510
+ const { stored } = issuedToken(claimed);
10511
+ if (!stored || !token)
10512
+ return false;
10513
+ const given = Buffer.from(tokenHash(token.trim()));
10514
+ const want = Buffer.from(stored);
10515
+ return given.length === want.length && timingSafeEqual(given, want);
10516
+ }
10517
+ function sessionClaimRefusal(env, claimed, cwd, options = {}) {
10494
10518
  const real = realHostSession(env);
10495
10519
  const host = family(real.host);
10496
10520
  if (real.kind !== "derived" && host !== null && HOST_SET_VARIABLE[host])
@@ -10500,18 +10524,22 @@ function sessionClaimRefusal(env, claimed, cwd) {
10500
10524
  };
10501
10525
  if (real.kind === "none" || !real.session)
10502
10526
  return null;
10503
- let issuedClaim = false;
10504
- if (claimed === real.session) {
10505
- const declaredChild = real.kind === "declared" && host === "claude_code" && claimed.includes(":");
10506
- if (!declaredChild || wasIssued(cwd, leadOf(claimed), claimed))
10507
- return null;
10508
- issuedClaim = true;
10509
- } else if (real.kind === "derived" && claimed.endsWith(`:${real.session}`))
10510
- return null;
10511
- else if (claimed.startsWith(`${real.session}:`)) {
10512
- if (wasIssued(cwd, real.session, claimed))
10527
+ const ownShell = claimed === real.session && !(real.kind === "declared" && claimed.includes(":")) || real.kind === "derived" && claimed.endsWith(`:${real.session}`);
10528
+ if (ownShell)
10529
+ return options.flag ? {
10530
+ error: `session_flag: --session ${claimed} names this shell's own ${real.host} session; on a host only an issued subagent id is passed as --session`,
10531
+ unblock: "drop --session: the shell's own session is already the acting one"
10532
+ } : null;
10533
+ const issuedClaim = claimed === real.session || claimed.startsWith(`${real.session}:`);
10534
+ if (issuedClaim) {
10535
+ const binding = issuedBinding(cwd, claimed);
10536
+ if (binding === "legacy")
10513
10537
  return null;
10514
- issuedClaim = true;
10538
+ if (binding === "token")
10539
+ return issuedTokenMatches(cwd, claimed, options.token) ? null : {
10540
+ error: `unbound_session: ${claimed} was issued to a subagent, and this call did not present the token its SubagentStart hook gave it`,
10541
+ unblock: `only that subagent acts as ${claimed}: it passes --token exactly as its SubagentStart context names it (or WORKIT_SESSION_TOKEN); anyone else spawns their own verifier`
10542
+ };
10515
10543
  }
10516
10544
  const error = `forged_session: ${claimed} is neither this shell's session (${real.session}) nor an id its host issued to a child of it`;
10517
10545
  if (issuedClaim && noIssuedRecord(cwd, leadOf(claimed)) && host !== null && PLUGIN_UPDATE[host])
@@ -10522,10 +10550,10 @@ function sessionClaimRefusal(env, claimed, cwd) {
10522
10550
  const ownChildShell = host !== null && OWN_CHILD_SHELL.has(host);
10523
10551
  return {
10524
10552
  error,
10525
- unblock: ownChildShell ? `record the verdict from the verifier's own ${host === "pi" ? "Pi worker" : "OpenCode child session"} shell, where WORKIT_SESSION_ID is already set; never set WORKIT_SESSION_ID or --session by hand` : "never name a session yourself: spawn a verifier or reviewer subagent and pass exactly the --session its SubagentStart context names, or pass --as verifier"
10553
+ unblock: ownChildShell ? `record the verdict from the verifier's own ${host === "pi" ? "Pi worker" : "OpenCode child session"} shell, where WORKIT_SESSION_ID is already set; never set WORKIT_SESSION_ID or --session by hand` : "never name a session yourself: spawn a verifier or reviewer subagent and pass exactly the --session and --token its SubagentStart context names, or pass --as verifier"
10526
10554
  };
10527
10555
  }
10528
- var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, hash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path6.join(resolveStateDir(), "issued"), safeList = (dir) => {
10556
+ var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, hash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path6.join(resolveStateDir(), "issued"), userTokenRoot = () => path6.join(resolveStateDir(), "issued-tokens"), safeList = (dir) => {
10529
10557
  try {
10530
10558
  return fs6.readdirSync(dir);
10531
10559
  } catch {
@@ -10544,12 +10572,26 @@ var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "")
10544
10572
  } catch {
10545
10573
  return false;
10546
10574
  }
10547
- }, PLUGIN_UPDATE, wasIssued = (cwd, lead, claimed) => {
10575
+ }, PLUGIN_UPDATE, tokenHash = (token) => createHash3("sha256").update(token).digest("hex"), issuedRecord = (cwd, claimed) => {
10576
+ const cut = claimed.lastIndexOf(":");
10577
+ if (cut <= 0)
10578
+ return null;
10548
10579
  migrateRepoIssued(cwd);
10549
10580
  try {
10550
- return fs6.existsSync(path6.join(userIssuedRoot(), hash(lead), hash(claimed)));
10581
+ return fs6.readFileSync(path6.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
10551
10582
  } catch {
10552
- return false;
10583
+ return null;
10584
+ }
10585
+ }, TOKEN_HASH, issuedToken = (claimed) => {
10586
+ const cut = claimed.lastIndexOf(":");
10587
+ if (cut <= 0)
10588
+ return { stored: null, leadBound: false };
10589
+ const dir = path6.join(userTokenRoot(), hash(claimed.slice(0, cut)));
10590
+ try {
10591
+ const text = fs6.readFileSync(path6.join(dir, hash(claimed)), "utf8");
10592
+ return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
10593
+ } catch {
10594
+ return { stored: null, leadBound: safeList(dir).length > 0 };
10553
10595
  }
10554
10596
  }, leadOf = (session) => session.slice(0, session.lastIndexOf(":")), OWN_CHILD_SHELL, HOST_SET_VARIABLE;
10555
10597
  var init_host_session = __esm(() => {
@@ -10567,6 +10609,7 @@ var init_host_session = __esm(() => {
10567
10609
  claude_code: "claude plugin marketplace update workit && claude plugin update workit@workit",
10568
10610
  codex: "codex plugin remove workit@workit && codex plugin add workit@workit"
10569
10611
  };
10612
+ TOKEN_HASH = /^([0-9a-f]{64})$/mu;
10570
10613
  OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
10571
10614
  HOST_SET_VARIABLE = {
10572
10615
  opencode: "OPENCODE_SESSION_ID",
@@ -10599,7 +10642,7 @@ import { spawnSync as spawnSync3 } from "node:child_process";
10599
10642
  import fs7 from "node:fs";
10600
10643
  import { hostname } from "node:os";
10601
10644
  import path8 from "node:path";
10602
- var metadataLockSchema, FOREIGN_LOCK_TTL_MS, UNREADABLE_LOCK_TTL_MS = 30000, RECLAIM_GUARD_TTL_MS = 30000, defaultLockTimeoutMs = 250, defaultLockTimeout = () => defaultLockTimeoutMs, setDefaultLockTimeout = (ms) => {
10645
+ var metadataLockSchema, FOREIGN_LOCK_TTL_MS, UNREADABLE_LOCK_TTL_MS = 30000, RECLAIM_GUARD_TTL_MS = 30000, IN_PROCESS_LOCK_TIMEOUT_MS = 250, defaultLockTimeoutMs, defaultLockTimeout = () => defaultLockTimeoutMs, hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS, setDefaultLockTimeout = (ms) => {
10603
10646
  defaultLockTimeoutMs = ms;
10604
10647
  }, parseMetadataLock = (raw) => {
10605
10648
  let value;
@@ -10789,6 +10832,7 @@ var init_store_lock = __esm(() => {
10789
10832
  externalAction: literal(true).optional()
10790
10833
  }).strict();
10791
10834
  FOREIGN_LOCK_TTL_MS = 10 * 60000;
10835
+ defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
10792
10836
  TASK_STORE_LOCK = { reclaimUnverifiedAfterTtl: true };
10793
10837
  });
10794
10838
 
@@ -14789,7 +14833,7 @@ var init_vcs_config = __esm(() => {
14789
14833
 
14790
14834
  // packages/workit-core/src/ledger.ts
14791
14835
  import { spawnSync as spawnSync9 } from "node:child_process";
14792
- import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
14836
+ import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
14793
14837
  import fs12 from "node:fs";
14794
14838
  import path15 from "node:path";
14795
14839
  function resolveCommit(cwd, ref) {
@@ -14878,7 +14922,7 @@ function actorFromEnv(env) {
14878
14922
  agentId: value("WORKIT_AGENT_ID")
14879
14923
  };
14880
14924
  }
14881
- function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
14925
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
14882
14926
  const lock = `${file}.lock`;
14883
14927
  const deadline = Date.now() + waitMs;
14884
14928
  let token = null;
@@ -14902,8 +14946,10 @@ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
14902
14946
  }
14903
14947
  function appendLine(file, build, options) {
14904
14948
  const write = () => {
14905
- const fd = fs12.openSync(file, "a+");
14949
+ const fd = fs12.openSync(file, "a+", 384);
14906
14950
  try {
14951
+ if (process.platform !== "win32")
14952
+ tighten(fd);
14907
14953
  const size = fs12.fstatSync(fd).size;
14908
14954
  let fence = "";
14909
14955
  if (size > 0) {
@@ -14982,7 +15028,9 @@ function readLedger(cwd, options = {}) {
14982
15028
  if (!file.ok)
14983
15029
  return file;
14984
15030
  let text = "";
15031
+ let stat = null;
14985
15032
  try {
15033
+ stat = fs12.statSync(file.value);
14986
15034
  text = fs12.readFileSync(file.value, "utf8");
14987
15035
  } catch (error) {
14988
15036
  if (error.code === "ENOENT")
@@ -15002,6 +15050,7 @@ function readLedger(cwd, options = {}) {
15002
15050
  const integrity = { chained: 0, legacy: 0, unverified: [] };
15003
15051
  const earlier = new Set([null]);
15004
15052
  let chainStarted = false;
15053
+ const ids = new Set;
15005
15054
  for (const raw of text.split(`
15006
15055
  `)) {
15007
15056
  const trimmed = raw.trim();
@@ -15035,6 +15084,10 @@ function readLedger(cwd, options = {}) {
15035
15084
  reason = "unsigned";
15036
15085
  else
15037
15086
  rowIntegrity = "legacy";
15087
+ if (options.integrity && !reason && ids.has(parsed.id))
15088
+ reason = "duplicate_id";
15089
+ if (options.integrity)
15090
+ ids.add(parsed.id);
15038
15091
  if (reason)
15039
15092
  rowIntegrity = "unverified";
15040
15093
  const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
@@ -15058,7 +15111,8 @@ function readLedger(cwd, options = {}) {
15058
15111
  seq: rows.length + 1,
15059
15112
  superseded: false,
15060
15113
  supersedeIgnored: false,
15061
- ...rowIntegrity ? { integrity: rowIntegrity } : {}
15114
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
15115
+ ...reason ? { integrityReason: reason } : {}
15062
15116
  };
15063
15117
  rows.push(row);
15064
15118
  if (!options.integrity)
@@ -15086,18 +15140,44 @@ function readLedger(cwd, options = {}) {
15086
15140
  const byId = new Map;
15087
15141
  for (const row of rows) {
15088
15142
  const target = row.supersedes ? byId.get(row.supersedes) : undefined;
15089
- if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
15143
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
15090
15144
  target.superseded = true;
15091
15145
  else if (row.supersedes)
15092
15146
  row.supersedeIgnored = true;
15093
15147
  if (!byId.has(row.id))
15094
15148
  byId.set(row.id, row);
15095
15149
  }
15150
+ if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
15151
+ tamperCache.set(file.value, {
15152
+ size: stat.size,
15153
+ stamp: fileStamp(stat),
15154
+ breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
15155
+ });
15096
15156
  return {
15097
15157
  ok: true,
15098
15158
  value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
15099
15159
  };
15100
15160
  }
15161
+ function ledgerTampering(cwd) {
15162
+ const file = ledgerPath(cwd);
15163
+ if (!file.ok)
15164
+ return [];
15165
+ try {
15166
+ const stat = fs12.statSync(file.value);
15167
+ const cached = tamperCache.get(file.value);
15168
+ if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
15169
+ return cached.breaks;
15170
+ } catch {
15171
+ return [];
15172
+ }
15173
+ const read = readLedger(cwd, { integrity: true });
15174
+ return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
15175
+ }
15176
+ function tamperedUnblock(cwd) {
15177
+ const file = ledgerPath(cwd);
15178
+ const where = file.ok ? file.value : "the ledger file";
15179
+ 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`;
15180
+ }
15101
15181
  function filterRows(rows, filter) {
15102
15182
  const matched = rows.filter((row) => (filter.branch === undefined || row.branch === filter.branch) && (filter.pr === undefined || row.pr === filter.pr) && (filter.type === undefined || row.type === filter.type));
15103
15183
  return filter.last === undefined ? matched : matched.slice(Math.max(0, matched.length - filter.last));
@@ -15213,15 +15293,51 @@ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, s
15213
15293
  sessions.add(session);
15214
15294
  return sessions;
15215
15295
  }
15216
- function effectiveOf(rows, authors, sameCode, isCurrent) {
15296
+ function sessionTrust(cwd, row) {
15297
+ const session = row.actor.session;
15298
+ if (!session || row.self === true)
15299
+ return null;
15300
+ const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
15301
+ const host = row.actor.host;
15302
+ const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
15303
+ if (!issuedOnly)
15304
+ return null;
15305
+ const binding = issuedBinding(cwd, session);
15306
+ if (binding === null)
15307
+ return "unissued_session";
15308
+ return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
15309
+ }
15310
+ function trustReasons(cwd, row) {
15311
+ if (row.integrity === "unverified")
15312
+ return [row.integrityReason ?? "unsigned"];
15313
+ if (row.integrity !== "verified")
15314
+ return ["unsigned"];
15315
+ const session = sessionTrust(cwd, row);
15316
+ return session ? [session] : [];
15317
+ }
15318
+ function withIntegrity(cwd, rows) {
15319
+ if (rows.every((row) => row.integrity !== undefined))
15320
+ return rows;
15321
+ const read = readLedger(cwd, { integrity: true });
15322
+ const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
15323
+ return rows.map((row) => {
15324
+ if (row.integrity !== undefined)
15325
+ return row;
15326
+ const fresh = bySeq.get(row.seq);
15327
+ return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
15328
+ });
15329
+ }
15330
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
15217
15331
  let effective = null;
15218
15332
  for (const row of rows) {
15219
15333
  if (!effective) {
15220
15334
  effective = row;
15221
15335
  continue;
15222
15336
  }
15223
- const rowIndependent = independenceReasons(row, authors).length === 0;
15224
- const effIndependent = independenceReasons(effective, authors).length === 0;
15337
+ if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
15338
+ continue;
15339
+ const rowIndependent = reasonsOf(row).length === 0;
15340
+ const effIndependent = reasonsOf(effective).length === 0;
15225
15341
  if (effIndependent && !rowIndependent && isCurrent(effective))
15226
15342
  continue;
15227
15343
  if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
@@ -15235,7 +15351,7 @@ function restackCarries(rows, branch, from, to) {
15235
15351
  return false;
15236
15352
  const edges = new Map;
15237
15353
  for (const row of rows)
15238
- if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
15354
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
15239
15355
  edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
15240
15356
  const seen = new Set([from]);
15241
15357
  const queue = [from];
@@ -15251,7 +15367,8 @@ function restackCarries(rows, branch, from, to) {
15251
15367
  }
15252
15368
  return false;
15253
15369
  }
15254
- function checkVerdicts(cwd, branch, rows) {
15370
+ function checkVerdicts(cwd, branch, ledgerRows) {
15371
+ const rows = withIntegrity(cwd, ledgerRows);
15255
15372
  const head = branchHead(cwd, branch);
15256
15373
  const fallbackBase = defaultBase(cwd);
15257
15374
  const scope = branchScope(cwd, branch);
@@ -15281,21 +15398,47 @@ function checkVerdicts(cwd, branch, rows) {
15281
15398
  return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
15282
15399
  };
15283
15400
  const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
15401
+ const failureAuthorCache = new Map;
15402
+ const failureAuthors = (row) => {
15403
+ let set = failureAuthorCache.get(row.seq);
15404
+ if (!set) {
15405
+ set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
15406
+ failureAuthorCache.set(row.seq, set);
15407
+ }
15408
+ return set;
15409
+ };
15410
+ const reasonCache = new Map;
15411
+ const reasonsOf = (row) => {
15412
+ let reasons = reasonCache.get(row);
15413
+ if (!reasons) {
15414
+ const failing = FAILING.has(String(row.result));
15415
+ reasons = [
15416
+ ...trustReasons(cwd, row),
15417
+ ...independenceReasons(row, failing ? failureAuthors(row) : authors)
15418
+ ];
15419
+ reasonCache.set(row, reasons);
15420
+ }
15421
+ return reasons;
15422
+ };
15423
+ const retired = new Set;
15424
+ for (const row of rows)
15425
+ if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
15426
+ retired.add(row.supersedes);
15284
15427
  const byKind = new Map;
15285
15428
  for (const row of rows)
15286
- if (isLiveVerdict(row) && scope(row)) {
15429
+ if (isLiveVerdict(row, retired) && scope(row)) {
15287
15430
  const kind = typeof row.kind === "string" ? row.kind : "review";
15288
15431
  byKind.set(kind, [...byKind.get(kind) ?? [], row]);
15289
15432
  }
15290
15433
  const verdicts = [];
15291
15434
  const warnings = [];
15292
15435
  for (const [kind, list] of byKind) {
15293
- const row = effectiveOf(list, authors, sameCode, (candidate) => basisOf(candidate) !== "stale");
15436
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
15294
15437
  if (!row)
15295
15438
  continue;
15296
15439
  const basis = basisOf(row);
15297
15440
  const current = basis !== "stale";
15298
- const independence = independenceReasons(row, authors);
15441
+ const independence = reasonsOf(row);
15299
15442
  const reasons = [
15300
15443
  ...current ? [] : ["stale"],
15301
15444
  ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
@@ -15312,23 +15455,31 @@ function checkVerdicts(cwd, branch, rows) {
15312
15455
  });
15313
15456
  if (reasons.length === 0) {
15314
15457
  for (const later of list)
15315
- if (later.seq > row.seq && independenceReasons(later, authors).length > 0 && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
15458
+ if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
15316
15459
  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`);
15317
15460
  }
15318
15461
  }
15319
15462
  for (const row of rows)
15320
15463
  if (row.integrity === "unverified" && scope(row))
15321
15464
  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)`);
15465
+ const tampered = ledgerTampering(cwd);
15466
+ if (tampered.length) {
15467
+ warnings.push(tamperedWarning(cwd, tampered));
15468
+ for (const entry of verdicts) {
15469
+ entry.accepted = false;
15470
+ entry.reasons = [...entry.reasons, "ledger_tampered"];
15471
+ }
15472
+ }
15322
15473
  verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
15323
15474
  const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
15324
15475
  const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
15325
15476
  const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
15326
- const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
15477
+ const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
15327
15478
  accepted: false,
15328
15479
  verdict: failing?.verdict ?? null,
15329
15480
  reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
15330
15481
  };
15331
- const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
15482
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
15332
15483
  return {
15333
15484
  branch,
15334
15485
  head,
@@ -15523,6 +15674,7 @@ function recordVerdict(context, input) {
15523
15674
  self: self2,
15524
15675
  selfReason,
15525
15676
  sessionSource: context.sessionSource ?? "env",
15677
+ ...context.sessionBound === true ? { sessionBound: true } : {},
15526
15678
  evidenceRefs: input.evidenceRefs ?? []
15527
15679
  }, { now: context.now });
15528
15680
  }
@@ -15751,7 +15903,7 @@ var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, VERDICT_RESULTS, PASSING, FAILING
15751
15903
  } catch {
15752
15904
  return null;
15753
15905
  }
15754
- }, newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
15906
+ }, newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
15755
15907
  if (process.platform !== "linux")
15756
15908
  return false;
15757
15909
  try {
@@ -15804,6 +15956,11 @@ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
15804
15956
  return text || null;
15805
15957
  }
15806
15958
  return null;
15959
+ }, tighten = (fd) => {
15960
+ try {
15961
+ if ((fs12.fstatSync(fd).mode & 63) !== 0)
15962
+ fs12.fchmodSync(fd, 384);
15963
+ } catch {}
15807
15964
  }, sealed = (row, id, at, previous) => {
15808
15965
  const { prevHash: _prev, rowHash: _row, ...fields } = row;
15809
15966
  const prevHash = previous === null ? null : sha2562(previous);
@@ -15818,7 +15975,7 @@ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
15818
15975
  session: str2(actor.session),
15819
15976
  agentId: str2(actor.agentId)
15820
15977
  };
15821
- }, isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded, RENAMED, renameAwareBranch = (scopeOf, branch) => {
15978
+ }, TAMPER_REASONS, tamperCache, fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`, 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)}`, isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id), RENAMED, renameAwareBranch = (scopeOf, branch) => {
15822
15979
  return (target) => {
15823
15980
  const own = typeof branch === "string" ? branch : null;
15824
15981
  const theirs = typeof target.branch === "string" ? target.branch : null;
@@ -15835,7 +15992,7 @@ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
15835
15992
  if (derivesFrom(author, session) || derivesFrom(session, author))
15836
15993
  return true;
15837
15994
  return false;
15838
- }, independenceReasons = (row, authors) => {
15995
+ }, TRUST_REASONS, SHELL_VOUCHED_HOSTS, untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason)), independenceReasons = (row, authors) => {
15839
15996
  const reasons = [];
15840
15997
  if (!row.actor.session)
15841
15998
  reasons.push("no_session");
@@ -15897,6 +16054,7 @@ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
15897
16054
  return brief.pr === null ? "workit pr create" : `workit pr status --pr ${brief.pr}`;
15898
16055
  };
15899
16056
  var init_ledger = __esm(() => {
16057
+ init_store_lock();
15900
16058
  init_vcs_config();
15901
16059
  init_rev();
15902
16060
  init_host_session();
@@ -15935,14 +16093,14 @@ var init_ledger = __esm(() => {
15935
16093
  return null;
15936
16094
  throw error;
15937
16095
  }
15938
- const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
16096
+ const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
15939
16097
  fs12.writeFileSync(path15.join(lock, LOCK_OWNER), token);
15940
16098
  return token;
15941
16099
  },
15942
16100
  release(lock, token) {
15943
16101
  if (lockToken(lock) !== token)
15944
16102
  return false;
15945
- const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
16103
+ const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
15946
16104
  try {
15947
16105
  fs12.renameSync(lock, tomb);
15948
16106
  } catch {
@@ -15959,7 +16117,7 @@ var init_ledger = __esm(() => {
15959
16117
  },
15960
16118
  token: lockToken,
15961
16119
  reap(lock, observed) {
15962
- const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
16120
+ const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
15963
16121
  try {
15964
16122
  fs12.renameSync(lock, tomb);
15965
16123
  } catch {
@@ -15978,7 +16136,24 @@ var init_ledger = __esm(() => {
15978
16136
  }
15979
16137
  };
15980
16138
  TAIL_BYTES = 64 * 1024;
16139
+ TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
16140
+ tamperCache = new Map;
15981
16141
  RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
16142
+ TRUST_REASONS = new Set([
16143
+ "unsigned",
16144
+ "hash_mismatch",
16145
+ "broken_link",
16146
+ "duplicate_id",
16147
+ "unissued_session",
16148
+ "unbound_session"
16149
+ ]);
16150
+ SHELL_VOUCHED_HOSTS = new Set([
16151
+ "cli",
16152
+ "opencode",
16153
+ "pi",
16154
+ "codex_cli",
16155
+ "codex_desktop"
16156
+ ]);
15982
16157
  OBSERVED_ONLY = new Set([
15983
16158
  "check",
15984
16159
  "merge.unverified",
@@ -63713,7 +63888,8 @@ var init_changelog = __esm(() => {
63713
63888
  });
63714
63889
 
63715
63890
  // packages/workit-core/src/core/hygiene.ts
63716
- import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
63891
+ import { spawnSync as spawnSync12 } from "node:child_process";
63892
+ import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
63717
63893
  import path26 from "node:path";
63718
63894
  var repoRoot2, templatesDir = () => path26.join(repoRoot2, "templates", "hygiene"), packageJson = (root) => {
63719
63895
  if (!existsSync9(path26.join(root, "package.json")))
@@ -63743,6 +63919,23 @@ var repoRoot2, templatesDir = () => path26.join(repoRoot2, "templates", "hygiene
63743
63919
  }
63744
63920
  }
63745
63921
  return "";
63922
+ }, projectTarget = (dir) => {
63923
+ const start = path26.resolve(dir);
63924
+ const top = spawnSync12("git", ["rev-parse", "--show-toplevel"], { cwd: start, encoding: "utf8" });
63925
+ const root = top.status === 0 ? (top.stdout ?? "").trim() : "";
63926
+ if (root)
63927
+ return { ok: true, root: path26.resolve(root) };
63928
+ let children = [];
63929
+ try {
63930
+ children = readdirSync5(start, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync9(path26.join(start, entry.name, ".git"))).map((entry) => path26.join(start, entry.name)).toSorted();
63931
+ } catch {
63932
+ children = [];
63933
+ }
63934
+ const where = children.length > 0 ? `run it inside one of its repositories: ${children.slice(0, 5).map((child) => `cd ${child}`).join(", ")}${children.length > 5 ? ` (+${children.length - 5} more)` : ""}` : "cd <repo> (or git init here) and run workit init again";
63935
+ return {
63936
+ ok: false,
63937
+ refused: `${start} is not a git repository, so no project files were added there; ${where}`
63938
+ };
63746
63939
  }, planHygieneFiles = (root, opts = {}) => {
63747
63940
  const openSource = opts.includeOpenSource ?? isOpenSource(root);
63748
63941
  const files = ["CHANGELOG.md", "README.md", ".editorconfig", ".gitattributes"];
@@ -63967,13 +64160,13 @@ var init_support_matrix = __esm(() => {
63967
64160
  });
63968
64161
 
63969
64162
  // packages/workit-cli/src/admin/host-install.ts
63970
- import { spawnSync as spawnSync12 } from "node:child_process";
64163
+ import { spawnSync as spawnSync13 } from "node:child_process";
63971
64164
  import {
63972
64165
  accessSync,
63973
64166
  constants as constants2,
63974
64167
  existsSync as existsSync11,
63975
64168
  readFileSync as readFileSync11,
63976
- readdirSync as readdirSync5,
64169
+ readdirSync as readdirSync6,
63977
64170
  realpathSync as realpathSync6,
63978
64171
  statSync as statSync2
63979
64172
  } from "node:fs";
@@ -64016,7 +64209,7 @@ function findHostExecutable(name, options = {}) {
64016
64209
  [path29.join(home, ".nvm", "versions", "node"), "bin"]
64017
64210
  ]) {
64018
64211
  try {
64019
- for (const entry of readdirSync5(base, { withFileTypes: true })) {
64212
+ for (const entry of readdirSync6(base, { withFileTypes: true })) {
64020
64213
  if (entry.isDirectory())
64021
64214
  dirs.push(path29.join(base, entry.name, tail));
64022
64215
  }
@@ -64238,7 +64431,7 @@ function runHostCommand(step, context = {}) {
64238
64431
  stderr: `Cannot safely execute batch shim ${step.command}; resolve its Node.js CLI entrypoint and retry`
64239
64432
  };
64240
64433
  }
64241
- const result = spawnSync12(step.command, step.args, {
64434
+ const result = spawnSync13(step.command, step.args, {
64242
64435
  cwd: step.cwd,
64243
64436
  env: (() => {
64244
64437
  const home = context.home ?? context.env?.HOME ?? os7.homedir();
@@ -64292,6 +64485,40 @@ var resolveWindowsShimScript = (shim) => {
64292
64485
  } catch {
64293
64486
  return false;
64294
64487
  }
64488
+ }, OPENCODE_PLUGIN_SCOPE = "@brainervirus", OPENCODE_PLUGIN_NAME = "workit-opencode", packageVersionAt = (root, name) => {
64489
+ try {
64490
+ const pkg = JSON.parse(readFileSync11(path29.join(root, "package.json"), "utf8"));
64491
+ return pkg.name === name && typeof pkg.version === "string" ? pkg.version : null;
64492
+ } catch {
64493
+ return null;
64494
+ }
64495
+ }, compareVersions = (a, b) => {
64496
+ const pa = a.split(/[.+-]/).map((n) => Number(n) || 0);
64497
+ const pb = b.split(/[.+-]/).map((n) => Number(n) || 0);
64498
+ for (let i = 0;i < Math.max(pa.length, pb.length); i++) {
64499
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
64500
+ if (d !== 0)
64501
+ return d;
64502
+ }
64503
+ return 0;
64504
+ }, openCodeCacheDir = (home, env = process.env) => path29.join(env.XDG_CACHE_HOME || path29.join(home, ".cache"), "opencode"), subdirs = (dir) => {
64505
+ try {
64506
+ return readdirSync6(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path29.join(dir, entry.name));
64507
+ } catch {
64508
+ return [];
64509
+ }
64510
+ }, openCodeInstalledPlugin = (home, env = process.env) => {
64511
+ const name = `${OPENCODE_PLUGIN_SCOPE}/${OPENCODE_PLUGIN_NAME}`;
64512
+ const cache = openCodeCacheDir(home, env);
64513
+ const specDirs = (layout) => subdirs(path29.join(cache, layout, OPENCODE_PLUGIN_SCOPE)).filter((dir) => path29.basename(dir) === `${OPENCODE_PLUGIN_NAME}@latest`);
64514
+ const installs = [...specDirs("npm").flatMap(subdirs), ...specDirs("packages")].map((dir) => path29.join(dir, "node_modules", OPENCODE_PLUGIN_SCOPE, OPENCODE_PLUGIN_NAME));
64515
+ let best = null;
64516
+ for (const root of installs) {
64517
+ const version = packageVersionAt(root, name);
64518
+ if (version && (!best || compareVersions(version, best.version) > 0))
64519
+ best = { root, version };
64520
+ }
64521
+ return best;
64295
64522
  }, isCanonicalCodexPackage = (root) => {
64296
64523
  for (const file of [
64297
64524
  path29.join(root, ".codex-plugin", "plugin.json"),
@@ -64311,7 +64538,7 @@ var resolveWindowsShimScript = (shim) => {
64311
64538
  if (isCanonicalCodexPackage(root))
64312
64539
  return true;
64313
64540
  try {
64314
- return readdirSync5(root, { withFileTypes: true }).some((entry) => entry.isDirectory() && isCanonicalCodexPackage(path29.join(root, entry.name)));
64541
+ return readdirSync6(root, { withFileTypes: true }).some((entry) => entry.isDirectory() && isCanonicalCodexPackage(path29.join(root, entry.name)));
64315
64542
  } catch {
64316
64543
  return false;
64317
64544
  }
@@ -64320,7 +64547,7 @@ var resolveWindowsShimScript = (shim) => {
64320
64547
  return true;
64321
64548
  const root = path29.join(codexHome, "plugins", "cache");
64322
64549
  try {
64323
- for (const marketplace of readdirSync5(root, { withFileTypes: true })) {
64550
+ for (const marketplace of readdirSync6(root, { withFileTypes: true })) {
64324
64551
  if (!marketplace.isDirectory())
64325
64552
  continue;
64326
64553
  if (isPackageOrVersionDirectory(path29.join(root, marketplace.name, "workit")))
@@ -64398,9 +64625,9 @@ var init_host_install = __esm(() => {
64398
64625
  });
64399
64626
 
64400
64627
  // packages/workit-core/src/core/skill-manifests.ts
64401
- import { cpSync as cpSync3, existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync6, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
64628
+ import { cpSync as cpSync3, existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
64402
64629
  import path30 from "node:path";
64403
- var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ? readdirSync6(root).filter((name) => existsSync12(path30.join(root, name, "SKILL.md"))).toSorted() : [], validateSkillManifests = (root, expected, label) => {
64630
+ var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ? readdirSync7(root).filter((name) => existsSync12(path30.join(root, name, "SKILL.md"))).toSorted() : [], validateSkillManifests = (root, expected, label) => {
64404
64631
  const actual = skillManifestNames(root);
64405
64632
  const missing = expected.filter((name) => !actual.includes(name));
64406
64633
  const extra = actual.filter((name) => !expected.includes(name));
@@ -64412,7 +64639,7 @@ var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ? re
64412
64639
  const pending = [path30.join(pluginDir, "skills")];
64413
64640
  while (pending.length > 0) {
64414
64641
  const dir = pending.pop();
64415
- for (const entry of readdirSync6(dir, { withFileTypes: true })) {
64642
+ for (const entry of readdirSync7(dir, { withFileTypes: true })) {
64416
64643
  const file = path30.join(dir, entry.name);
64417
64644
  if (entry.isDirectory())
64418
64645
  pending.push(file);
@@ -64443,7 +64670,7 @@ var init_skill_manifests = __esm(() => {
64443
64670
  });
64444
64671
 
64445
64672
  // packages/workit-core/src/git/session-hook.ts
64446
- import { spawnSync as spawnSync13 } from "node:child_process";
64673
+ import { spawnSync as spawnSync14 } from "node:child_process";
64447
64674
  import {
64448
64675
  chmodSync,
64449
64676
  existsSync as existsSync13,
@@ -64638,7 +64865,7 @@ add_trailer "$(git rev-parse --git-path COMMIT_EDITMSG)"
64638
64865
  [ "$2" != merge ] || add_trailer "$(git rev-parse --git-path MERGE_MSG)"
64639
64866
  exit 0
64640
64867
  `, manualTrailerLine = () => `h="$(git rev-parse --git-common-dir 2>/dev/null)/${HELPER.join("/")}"; [ ! -f "$h" ] || sh "$h" "$@" || true`, lefthookTrailerRun = () => `set -f; IFS=; h=$(git rev-parse --git-common-dir 2>/dev/null)/${HELPER.join("/")}; [ ! -f $h ] || sh $h '' {2} {3} || true`, HELPER_REF, SESSION_HOOK_SCRIPT, run2 = (cwd, args, env) => {
64641
- const r = spawnSync13("git", args, {
64868
+ const r = spawnSync14("git", args, {
64642
64869
  cwd,
64643
64870
  encoding: "utf8",
64644
64871
  timeout: GIT_TIMEOUTS.local,
@@ -64799,14 +65026,14 @@ ${manualTrailerLine()}
64799
65026
  });
64800
65027
 
64801
65028
  // packages/workit-cli/src/admin/doctor.ts
64802
- import { spawnSync as spawnSync14 } from "node:child_process";
65029
+ import { spawnSync as spawnSync15 } from "node:child_process";
64803
65030
  import { createHash as createHash14 } from "node:crypto";
64804
65031
  import {
64805
65032
  existsSync as existsSync14,
64806
65033
  lstatSync as lstatSync5,
64807
65034
  mkdirSync as mkdirSync6,
64808
65035
  mkdtempSync,
64809
- readdirSync as readdirSync7,
65036
+ readdirSync as readdirSync8,
64810
65037
  readFileSync as readFileSync14,
64811
65038
  readlinkSync as readlinkSync2,
64812
65039
  rmSync as rmSync3,
@@ -64832,7 +65059,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64832
65059
  const configDir = options.configDir ?? env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path32.join(home, ".config", "workit");
64833
65060
  const stateDir = options.stateDir ?? env.WORKFLOW_TOOLKIT_STATE ?? path32.join(home, ".local", "state", "workit");
64834
65061
  const cwd = options.cwd ?? process.cwd();
64835
- const dev = options.dev ?? env.WORKFLOW_TOOLKIT_DEV ?? findDevFromCwd(cwd);
65062
+ const dev = options.dev?.trim() || env.WORKFLOW_TOOLKIT_DEV?.trim() || findDevFromCwd(cwd);
64836
65063
  return {
64837
65064
  host: options.host ?? "cli",
64838
65065
  home,
@@ -64842,7 +65069,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64842
65069
  workspaceRoot: options.workspaceRoot ?? env.WORKFLOW_WORKSPACE_ROOT ?? cwd,
64843
65070
  dev,
64844
65071
  opencodeConfig: options.opencodeConfig ?? path32.join(home, ".config", "opencode", "opencode.json"),
64845
- opencodePackageCacheDir: options.opencodePackageCacheDir ?? path32.join(home, ".cache", "opencode", "packages", "@brainervirus", "workit-opencode@latest"),
65072
+ opencodePackageCacheDir: options.opencodePackageCacheDir ?? null,
64846
65073
  cursorSettings: options.cursorSettings ?? path32.join(home, ".cursor", "settings.json"),
64847
65074
  cursorMcp: options.cursorMcp ?? path32.join(home, ".cursor", "mcp.json"),
64848
65075
  cursorPluginDir: options.cursorPluginDir ?? path32.join(home, ".cursor", "plugins", "local", "workit"),
@@ -64888,7 +65115,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64888
65115
  }
64889
65116
  return null;
64890
65117
  }, versionOf = (bin, env, timeout) => {
64891
- const r = spawnSync14(bin, ["--version"], { encoding: "utf8", env, timeout });
65118
+ const r = spawnSync15(bin, ["--version"], { encoding: "utf8", env, timeout });
64892
65119
  if (r.error)
64893
65120
  return null;
64894
65121
  return (r.stdout ?? "").trim();
@@ -64906,7 +65133,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64906
65133
  if (env.BUN && existsSync14(env.BUN))
64907
65134
  return env.BUN;
64908
65135
  for (const candidate of [
64909
- path32.join(os8.homedir(), ".bun/bin/bun"),
65136
+ path32.join(env.HOME || os8.homedir(), ".bun/bin/bun"),
64910
65137
  "/usr/local/bin/bun",
64911
65138
  "/usr/bin/bun"
64912
65139
  ]) {
@@ -64915,47 +65142,50 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64915
65142
  }
64916
65143
  return commandOnPath("bun", env) ? "bun" : null;
64917
65144
  }, checkRuntime = (res) => {
64918
- const node = versionOf("node", res.env);
64919
- const nodeOk = node !== null && semverAtLeast(node, SUPPORT_MATRIX.node.minimum);
65145
+ const running = `v${process.versions.node}`;
65146
+ const runningOk = semverAtLeast(running, SUPPORT_MATRIX.node.minimum);
65147
+ const pathNode = versionOf("node", res.env);
64920
65148
  const bun = resolveBun(res.env);
64921
65149
  const bunVersion = bun ? versionOf(bun, res.env) : null;
64922
65150
  const bunOk = bunVersion !== null;
64923
65151
  const needsBun = res.dev !== null;
64924
- if (!nodeOk) {
65152
+ if (!runningOk) {
64925
65153
  return {
64926
65154
  id: "runtime",
64927
65155
  status: "fail",
64928
- detail: `node ${SUPPORT_MATRIX.node.minimum}+ required (found ${node ?? "none on PATH"})`,
65156
+ detail: `node ${SUPPORT_MATRIX.node.minimum}+ required (workit runs under ${running} at ${process.execPath})`,
64929
65157
  fix: `Install Node ${SUPPORT_MATRIX.node.minimum}+ (declared support: ${SUPPORT_MATRIX.node.minimum}/current)`
64930
65158
  };
64931
65159
  }
64932
- if (!bunOk && needsBun) {
65160
+ const pathNote = pathNode !== null && pathNode !== running ? `; node on PATH is ${pathNode}${semverAtLeast(pathNode, SUPPORT_MATRIX.node.minimum) ? "" : ` (below ${SUPPORT_MATRIX.node.minimum}; it does not run this CLI)`}` : "";
65161
+ if (pathNode === null) {
64933
65162
  return {
64934
65163
  id: "runtime",
64935
- status: "fail",
64936
- detail: `bun not found on PATH (dev checkout requires the pinned toolchain ${SUPPORT_MATRIX.bun})`,
64937
- fix: `Install bun ${SUPPORT_MATRIX.bun} (curl -fsSL https://bun.sh/install | bash)`
65164
+ status: "warn",
65165
+ detail: `workit runs under node ${running}, but no node is on PATH for host hooks`,
65166
+ fix: `Put Node ${SUPPORT_MATRIX.node.minimum}+ on PATH (host hooks launch \`node\`)`
64938
65167
  };
64939
65168
  }
64940
- if (!bunOk) {
65169
+ if (!bunOk && needsBun) {
64941
65170
  return {
64942
65171
  id: "runtime",
64943
- status: "warn",
64944
- detail: "bun not found on PATH — not required for published artifacts",
64945
- fix: "Install bun for development/installer use"
65172
+ status: "fail",
65173
+ detail: `bun not found on PATH (dev checkout requires the pinned toolchain ${SUPPORT_MATRIX.bun})`,
65174
+ fix: `Install bun ${SUPPORT_MATRIX.bun} (curl -fsSL https://bun.sh/install | bash)`
64946
65175
  };
64947
65176
  }
65177
+ const pathOld = !semverAtLeast(pathNode, SUPPORT_MATRIX.node.minimum);
64948
65178
  return {
64949
65179
  id: "runtime",
64950
- status: "pass",
64951
- detail: `node ${node} (>=${SUPPORT_MATRIX.node.minimum}), bun ${bunVersion ?? "n/a"}`
65180
+ status: bunOk && !pathOld ? "pass" : "info",
65181
+ detail: `node ${running} (>=${SUPPORT_MATRIX.node.minimum})${pathNote}, ${bunOk ? `bun ${bunVersion}` : "no bun (needed only for a dev checkout)"}`
64952
65182
  };
64953
65183
  }, checkVersions = (res) => {
64954
65184
  if (!res.dev) {
64955
65185
  return {
64956
65186
  id: "versions",
64957
- status: "warn",
64958
- detail: "no dev checkout found (WORKFLOW_TOOLKIT_DEV) — skipping version parity"
65187
+ status: "info",
65188
+ detail: "installed package, not a dev checkout — version parity applies to checkouts only"
64959
65189
  };
64960
65190
  }
64961
65191
  const corePkg = readJson(path32.join(res.dev, "packages/workit-core/package.json"));
@@ -64969,7 +65199,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64969
65199
  const refs = new Set;
64970
65200
  for (const name of ["workit-opencode", "workit-cursor", "workit-cli"]) {
64971
65201
  const pkg = readJson(path32.join(res.dev, "packages", name, "package.json"));
64972
- const dep = pkg?.dependencies?.["@brainervirus/workit-core"];
65202
+ const dep = pkg?.dependencies?.["@brainervirus/workit-core"] ?? pkg?.devDependencies?.["@brainervirus/workit-core"];
64973
65203
  if (typeof dep === "string")
64974
65204
  refs.add(dep);
64975
65205
  }
@@ -65066,10 +65296,16 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65066
65296
  status: "pass",
65067
65297
  detail: `codex CLI ${installed} matches the qualified pin`
65068
65298
  };
65299
+ if (semverAtLeast(installed, qualified))
65300
+ return {
65301
+ id: "codex_pin",
65302
+ status: "info",
65303
+ detail: `codex CLI ${installed} is newer than the qualified ${qualified}`
65304
+ };
65069
65305
  return {
65070
65306
  id: "codex_pin",
65071
65307
  status: "warn",
65072
- detail: `codex CLI ${installed} differs from the qualified ${qualified}`,
65308
+ detail: `codex CLI ${installed} is older than the qualified ${qualified}`,
65073
65309
  fix: `Reinstall the qualified Codex CLI ${qualified} or record fresh qualification evidence`
65074
65310
  };
65075
65311
  }, readTomlTables = (file) => {
@@ -65126,7 +65362,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65126
65362
  const dir = path32.join(codexHome, "plugins", "cache", marketplace, "workit");
65127
65363
  let versions = [];
65128
65364
  try {
65129
- versions = readdirSync7(dir).filter((v) => existsSync14(path32.join(dir, v, ".codex-plugin")));
65365
+ versions = readdirSync8(dir).filter((v) => existsSync14(path32.join(dir, v, ".codex-plugin")));
65130
65366
  } catch {
65131
65367
  versions = [];
65132
65368
  }
@@ -65235,7 +65471,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65235
65471
  const installed = [];
65236
65472
  for (const install of installs) {
65237
65473
  const source = install.root ? path32.join(install.root, "agents") : null;
65238
- const files = source && existsSync14(source) ? readdirSync7(source).filter((f) => f.endsWith(".toml")).toSorted() : [];
65474
+ const files = source && existsSync14(source) ? readdirSync8(source).filter((f) => f.endsWith(".toml")).toSorted() : [];
65239
65475
  if (files.length === 0) {
65240
65476
  problems.push(`${install.id} ${install.version ?? "?"} bundles no agents`);
65241
65477
  fixes.push(`codex plugin remove ${install.id} && codex plugin add ${install.id}`);
@@ -65463,8 +65699,8 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65463
65699
  if (!dev && res.host === "opencode") {
65464
65700
  return {
65465
65701
  id: "assets",
65466
- status: "warn",
65467
- detail: "no dev checkout found (WORKFLOW_TOOLKIT_DEV) — skipping asset check"
65702
+ status: "info",
65703
+ detail: "installed package — OpenCode loads the published assets, no checkout to compare"
65468
65704
  };
65469
65705
  }
65470
65706
  return {
@@ -65499,7 +65735,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65499
65735
  `)) {
65500
65736
  return false;
65501
65737
  }
65502
- return spawnSync14(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
65738
+ return spawnSync15(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
65503
65739
  } catch {
65504
65740
  return false;
65505
65741
  }
@@ -65589,8 +65825,8 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65589
65825
  if (!dev && res.host !== "cursor") {
65590
65826
  return {
65591
65827
  id: "launcher",
65592
- status: "warn",
65593
- detail: "no dev checkout found (WORKFLOW_TOOLKIT_DEV) — skipping non-Cursor launcher checks"
65828
+ status: "info",
65829
+ detail: "installed package — non-Cursor launchers ship inside each host plugin, no checkout to check"
65594
65830
  };
65595
65831
  }
65596
65832
  return {
@@ -65630,7 +65866,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65630
65866
  fix: "Re-run install-cursor-plugin.sh (or `workit init` with Cursor) to rewrite the hook entries"
65631
65867
  };
65632
65868
  const started = performance.now();
65633
- const probe = spawnSync14("node", [launcher, "--probe", "workit-cursor-hook"], {
65869
+ const probe = spawnSync15("node", [launcher, "--probe", "workit-cursor-hook"], {
65634
65870
  encoding: "utf8",
65635
65871
  env: { ...res.env, WORKIT_CURSOR_HOOK_NPX_OFFLINE: "1" },
65636
65872
  timeout: 40000,
@@ -65699,18 +65935,18 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65699
65935
  fix: "Install git and ensure it is on PATH"
65700
65936
  };
65701
65937
  }
65702
- if (process.platform !== "win32" && !commandOnPath("flock", res.env)) {
65938
+ if (res.dev && process.platform !== "win32" && !commandOnPath("flock", res.env)) {
65703
65939
  return {
65704
65940
  id: "utility",
65705
65941
  status: "warn",
65706
- detail: "flock (util-linux) not found — sync-runtime requires it for locking",
65707
- fix: "Install util-linux (flock)"
65942
+ detail: "flock not found — the dev checkout's sync-runtime requires it for locking",
65943
+ fix: process.platform === "darwin" ? "brew install flock (Homebrew's util-linux is keg-only and leaves flock off PATH)" : "Install util-linux (flock)"
65708
65944
  };
65709
65945
  }
65710
65946
  return {
65711
65947
  id: "utility",
65712
65948
  status: "pass",
65713
- detail: "git (and flock where required) on PATH"
65949
+ detail: res.dev ? "git and flock on PATH" : "git on PATH"
65714
65950
  };
65715
65951
  }, isFragileCachePin = (entry) => {
65716
65952
  const n = entry.replaceAll("\\", "/").toLowerCase();
@@ -65804,7 +66040,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65804
66040
  if (!cmd)
65805
66041
  return null;
65806
66042
  try {
65807
- const r = spawnSync14(cmd, ["view", pkgName, "version"], {
66043
+ const r = spawnSync15(cmd, ["view", pkgName, "version"], {
65808
66044
  encoding: "utf8",
65809
66045
  timeout: 20000,
65810
66046
  env: res.env
@@ -65817,9 +66053,18 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65817
66053
  }, installedPluginVersion = (res) => {
65818
66054
  const pkg = readJson(path32.join(res.cursorPluginDir, "package.json"));
65819
66055
  return typeof pkg?.version === "string" && pkg.version ? pkg.version : null;
65820
- }, installedOpenCodeCacheVersion = (res) => {
65821
- const pkg = readJson(path32.join(res.opencodePackageCacheDir, "node_modules", "@brainervirus", "workit-opencode", "package.json"));
65822
- return typeof pkg?.version === "string" && pkg.version ? pkg.version : null;
66056
+ }, installedOpenCodeCache = (res) => {
66057
+ if (res.opencodePackageCacheDir !== null) {
66058
+ const pkg = readJson(path32.join(res.opencodePackageCacheDir, "node_modules", "@brainervirus", "workit-opencode", "package.json"));
66059
+ return typeof pkg?.version === "string" && pkg.version ? { version: pkg.version, dir: res.opencodePackageCacheDir } : null;
66060
+ }
66061
+ const found = openCodeInstalledPlugin(res.home, res.env);
66062
+ if (!found)
66063
+ return null;
66064
+ let dir = path32.dirname(path32.dirname(path32.dirname(found.root)));
66065
+ if (path32.basename(dir) !== "workit-opencode@latest")
66066
+ dir = path32.dirname(dir);
66067
+ return { version: found.version, dir };
65823
66068
  }, checkOpenCodePackageCache = (res) => {
65824
66069
  if (!existsSync14(res.opencodeConfig)) {
65825
66070
  return {
@@ -65851,8 +66096,9 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65851
66096
  detail: `opencode pin ${pin} is an exact version — not compared to registry latest`
65852
66097
  };
65853
66098
  }
65854
- const installed = installedOpenCodeCacheVersion(res);
65855
- if (installed === null) {
66099
+ const cache = installedOpenCodeCache(res);
66100
+ const installed = cache?.version ?? null;
66101
+ if (cache === null || installed === null) {
65856
66102
  return {
65857
66103
  id: "stale_install",
65858
66104
  status: "pass",
@@ -65866,7 +66112,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65866
66112
  status: "warn",
65867
66113
  registryProbed: true,
65868
66114
  detail: "registry_unreachable: cannot compare OpenCode @latest cache against published workit-opencode",
65869
- fix: `Retry when npm is reachable, or remove ${res.opencodePackageCacheDir} and restart OpenCode to re-resolve`
66115
+ fix: `Retry when npm is reachable, or remove ${cache.dir} and restart OpenCode to re-resolve`
65870
66116
  };
65871
66117
  }
65872
66118
  if (!semverAtLeast(installed, expected)) {
@@ -65875,7 +66121,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
65875
66121
  status: "fail",
65876
66122
  registryProbed: true,
65877
66123
  detail: `stale_install: OpenCode @latest cache has workit-opencode ${installed} behind published ${expected}`,
65878
- fix: `Remove ${res.opencodePackageCacheDir} and restart OpenCode — it re-resolves @latest on the next launch`
66124
+ fix: `Remove ${cache.dir} and restart OpenCode — it re-resolves @latest on the next launch`
65879
66125
  };
65880
66126
  }
65881
66127
  return {
@@ -66344,7 +66590,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66344
66590
  }, defaultIdentityProbes = (env) => ({
66345
66591
  origin: (cwd) => {
66346
66592
  try {
66347
- const out = spawnSync14("git", ["remote", "get-url", "origin"], {
66593
+ const out = spawnSync15("git", ["remote", "get-url", "origin"], {
66348
66594
  cwd,
66349
66595
  encoding: "utf8",
66350
66596
  env
@@ -66356,7 +66602,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66356
66602
  },
66357
66603
  ghLogin: (e) => {
66358
66604
  try {
66359
- const out = spawnSync14("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
66605
+ const out = spawnSync15("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
66360
66606
  const login = (out.stdout ?? "").trim();
66361
66607
  return out.status === 0 && login ? login : null;
66362
66608
  } catch {
@@ -66365,7 +66611,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66365
66611
  },
66366
66612
  glabLogin: (e) => {
66367
66613
  try {
66368
- const out = spawnSync14("glab", ["api", "user", "--jq", ".username"], {
66614
+ const out = spawnSync15("glab", ["api", "user", "--jq", ".username"], {
66369
66615
  encoding: "utf8",
66370
66616
  env: e
66371
66617
  });
@@ -66377,7 +66623,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66377
66623
  },
66378
66624
  sshLogin: (host) => {
66379
66625
  try {
66380
- const out = spawnSync14("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
66626
+ const out = spawnSync15("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
66381
66627
  encoding: "utf8",
66382
66628
  env
66383
66629
  });
@@ -66534,10 +66780,10 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66534
66780
  fix: repair,
66535
66781
  registryProbed
66536
66782
  };
66537
- }, HOOK_ISSUED_MARKER = "workit-issued-sessions/2", hookRecordsIssued = (root) => {
66783
+ }, HOOK_ISSUED_MARKER = "workit-issued-sessions/3", hookRecordsIssued = (root) => {
66538
66784
  let files;
66539
66785
  try {
66540
- files = readdirSync7(path32.join(root, "dist")).filter((f) => f.endsWith(".js"));
66786
+ files = readdirSync8(path32.join(root, "dist")).filter((f) => f.endsWith(".js"));
66541
66787
  } catch {
66542
66788
  return null;
66543
66789
  }
@@ -66596,12 +66842,12 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66596
66842
  if (cached)
66597
66843
  return cached;
66598
66844
  const viaShell = process.platform === "win32" && /\.(?:cmd|bat)$/i.test(bin);
66599
- const r = viaShell ? spawnSync14(`"${bin}" --version`, {
66845
+ const r = viaShell ? spawnSync15(`"${bin}" --version`, {
66600
66846
  encoding: "utf8",
66601
66847
  env,
66602
66848
  timeout: WORKIT_PROBE_TIMEOUT_MS,
66603
66849
  shell: true
66604
- }) : spawnSync14(bin, ["--version"], { encoding: "utf8", env, timeout: WORKIT_PROBE_TIMEOUT_MS });
66850
+ }) : spawnSync15(bin, ["--version"], { encoding: "utf8", env, timeout: WORKIT_PROBE_TIMEOUT_MS });
66605
66851
  const timedOut = r.error !== undefined && "code" in r.error && r.error.code === "ETIMEDOUT";
66606
66852
  const version = r.status === 0 ? (r.stdout ?? "").match(SEMVER)?.[0] ?? null : null;
66607
66853
  const probe = { version: timedOut ? null : version, timedOut };
@@ -66614,7 +66860,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66614
66860
  version: i.version
66615
66861
  })),
66616
66862
  { label: "Cursor plugin", version: installedPluginVersion(res) },
66617
- { label: "OpenCode plugin", version: installedOpenCodeCacheVersion(res) }
66863
+ { label: "OpenCode plugin", version: installedOpenCodeCache(res)?.version ?? null }
66618
66864
  ].filter((p) => p.version !== null);
66619
66865
  const newest = plugins.reduce((best, p) => best && semverAtLeast(best.version, p.version) ? best : p, null);
66620
66866
  const target = newest?.version ?? "latest";
@@ -66655,7 +66901,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66655
66901
  } : c) : raw;
66656
66902
  const failed = checks.filter((c) => c.status === "fail").length;
66657
66903
  const warned = checks.filter((c) => c.status === "warn").length;
66658
- const passed = checks.filter((c) => c.status === "pass").length;
66904
+ const passed = checks.filter((c) => c.status === "pass" || c.status === "info").length;
66659
66905
  const exitCode = failed > 0 ? 1 : 0;
66660
66906
  const offline = !raw.some((c) => ("registryProbed" in c) && c.registryProbed);
66661
66907
  const report = {
@@ -66746,13 +66992,14 @@ import {
66746
66992
  mkdirSync as mkdirSync7,
66747
66993
  mkdtempSync as mkdtempSync2,
66748
66994
  readFileSync as readFileSync15,
66749
- readdirSync as readdirSync8,
66995
+ readdirSync as readdirSync9,
66750
66996
  realpathSync as realpathSync7,
66751
66997
  renameSync as renameSync4,
66752
66998
  rmSync as rmSync4,
66753
66999
  statSync as statSync6,
66754
67000
  writeFileSync as writeFileSync8
66755
67001
  } from "node:fs";
67002
+ import { createRequire as createRequire2 } from "node:module";
66756
67003
  import os9 from "node:os";
66757
67004
  import path33 from "node:path";
66758
67005
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
@@ -66789,6 +67036,7 @@ function buildSetupPreview(values, opts = {}) {
66789
67036
  const blocked = [];
66790
67037
  const mutations = [];
66791
67038
  const preserved = [];
67039
+ let projectRefused;
66792
67040
  const overrides = activeSetupOverrides(env);
66793
67041
  for (const entry of [state.config, state.youtrack, state.vcs, state.workspaces]) {
66794
67042
  if (entry.status === "malformed")
@@ -66911,8 +67159,11 @@ function buildSetupPreview(values, opts = {}) {
66911
67159
  }
66912
67160
  }
66913
67161
  }
66914
- if (values.applyProject) {
66915
- const root = path33.resolve(opts.cwd ?? process.cwd());
67162
+ const target = values.applyProject ? projectTarget(opts.cwd ?? process.cwd()) : null;
67163
+ if (target && !target.ok)
67164
+ projectRefused = target.refused;
67165
+ if (target?.ok) {
67166
+ const root = target.root;
66916
67167
  const gitignorePath = path33.join(root, ".gitignore");
66917
67168
  const existing = existsSync15(gitignorePath) ? readFileSync15(gitignorePath, "utf8") : "";
66918
67169
  const existingLines = new Set(existing.split(`
@@ -66931,7 +67182,8 @@ function buildSetupPreview(values, opts = {}) {
66931
67182
  overrides,
66932
67183
  preserved,
66933
67184
  platforms: values.platforms ?? [],
66934
- state
67185
+ state,
67186
+ ...projectRefused === undefined ? {} : { projectRefused }
66935
67187
  };
66936
67188
  }
66937
67189
  function resolveApply(preview, options) {
@@ -67114,7 +67366,14 @@ function adapterRoot(platform, res, preferInstalledAdapter = false) {
67114
67366
  if (res.dev) {
67115
67367
  candidates.push(path33.join(res.dev, "packages", `workit-${platform}`));
67116
67368
  } else {
67117
- candidates.push(path33.join(packageRoot(), "..", `workit-${platform}`));
67369
+ candidates.push(...nodeResolvedAdapters(platform));
67370
+ if (platform === "cursor")
67371
+ candidates.push(stagedCursor);
67372
+ if (platform === "opencode") {
67373
+ const cached = openCodeInstalledPlugin(res.home, res.env);
67374
+ if (cached)
67375
+ candidates.push(cached.root);
67376
+ }
67118
67377
  let dir = path33.resolve(res.cwd);
67119
67378
  while (true) {
67120
67379
  candidates.push(path33.join(dir, "packages", `workit-${platform}`));
@@ -67124,17 +67383,14 @@ function adapterRoot(platform, res, preferInstalledAdapter = false) {
67124
67383
  dir = parent;
67125
67384
  }
67126
67385
  }
67127
- candidates.push(path33.join(res.home, ".local", "share", "workit", "packages", `workit-${platform}`));
67128
- if (platform === "cursor")
67129
- candidates.push(stagedCursor);
67130
67386
  for (const candidate of candidates) {
67131
67387
  if (isAdapter(candidate, platform))
67132
- return candidate;
67388
+ return realRoot(candidate);
67133
67389
  }
67134
67390
  return null;
67135
67391
  }
67136
67392
  function applyOpenCode(root, res) {
67137
- const resolvedPin = opencodePin(root, res);
67393
+ const resolvedPin = root === null ? OPENCODE_NPM_PIN : opencodePin(root, res);
67138
67394
  if (!resolvedPin) {
67139
67395
  return {
67140
67396
  platform: "opencode",
@@ -67433,6 +67689,10 @@ function applySetupPreview(preview, options = {}) {
67433
67689
  root = adapterRoot(platform, res, options.preferInstalledAdapter);
67434
67690
  rootFor.set(platform, root);
67435
67691
  }
67692
+ if (root === null && registersNpmPin(platform, res)) {
67693
+ entries.push(applyOpenCode(null, res));
67694
+ continue;
67695
+ }
67436
67696
  if (root === null) {
67437
67697
  entries.push({
67438
67698
  platform,
@@ -67474,12 +67734,20 @@ function applySetupPreview(preview, options = {}) {
67474
67734
  detail: "existing credential preserved"
67475
67735
  });
67476
67736
  }
67737
+ if (preview.projectRefused) {
67738
+ entries.push({
67739
+ platform: "core",
67740
+ file: path33.resolve(res.cwd),
67741
+ status: "Skipped",
67742
+ detail: preview.projectRefused
67743
+ });
67744
+ }
67477
67745
  const doctor = [];
67478
67746
  for (const platform of preview.platforms) {
67479
67747
  if (platform !== "opencode" && platform !== "cursor")
67480
67748
  continue;
67481
67749
  const root = rootFor.get(platform);
67482
- if (root === null || root === undefined)
67750
+ if (root === undefined || root === null && !registersNpmPin(platform, res))
67483
67751
  continue;
67484
67752
  if (!entries.some((e) => e.platform === platform && e.status !== "Failed"))
67485
67753
  continue;
@@ -67608,6 +67876,16 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
67608
67876
  } catch {
67609
67877
  return false;
67610
67878
  }
67879
+ }, nodeResolvedAdapters = (platform) => {
67880
+ const name = `@brainervirus/workit-${platform}`;
67881
+ const require2 = createRequire2(path33.join(packageRoot(), "package.json"));
67882
+ return (require2.resolve.paths(name) ?? []).map((dir) => path33.join(dir, name));
67883
+ }, realRoot = (root) => {
67884
+ try {
67885
+ return realpathSync7(root);
67886
+ } catch {
67887
+ return root;
67888
+ }
67611
67889
  }, isDevOpenCodeAdapterRoot = (root, dev) => {
67612
67890
  if (dev)
67613
67891
  return true;
@@ -67622,7 +67900,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
67622
67900
  return `file://${entry}`;
67623
67901
  }
67624
67902
  return null;
67625
- }, opencodePin = (root, res) => resolveOpenCodePin(root, { dev: res.dev }), CURSOR_HOOKS_FILE, localizedCursorHooks = (shipped, pluginDir) => {
67903
+ }, opencodePin = (root, res) => resolveOpenCodePin(root, { dev: res.dev }), registersNpmPin = (platform, res) => platform === "opencode" && !res.dev, CURSOR_HOOKS_FILE, localizedCursorHooks = (shipped, pluginDir) => {
67626
67904
  try {
67627
67905
  const merged = mergeCursorHooks(JSON.parse(shipped.toString("utf8")), { command: cursorHooksEntry(pluginDir).command }, pluginDir);
67628
67906
  return Buffer.from(`${JSON.stringify(merged.config, null, 2)}
@@ -67632,7 +67910,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
67632
67910
  }
67633
67911
  }, samePluginContent = (src, dest, pluginDir, relative = "") => {
67634
67912
  try {
67635
- for (const entry of readdirSync8(src, { withFileTypes: true })) {
67913
+ for (const entry of readdirSync9(src, { withFileTypes: true })) {
67636
67914
  if (entry.name === "node_modules")
67637
67915
  continue;
67638
67916
  const source = path33.join(src, entry.name);
@@ -67647,7 +67925,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
67647
67925
  return false;
67648
67926
  }
67649
67927
  }
67650
- for (const entry of readdirSync8(dest, { withFileTypes: true })) {
67928
+ for (const entry of readdirSync9(dest, { withFileTypes: true })) {
67651
67929
  if (existsSync15(path33.join(src, entry.name)))
67652
67930
  continue;
67653
67931
  const rel = path33.join(relative, entry.name);
@@ -67666,7 +67944,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
67666
67944
  const sourceRules = path33.join(src, "rules");
67667
67945
  const installedRules = path33.join(dest, "rules");
67668
67946
  try {
67669
- for (const entry of readdirSync8(installedRules, { withFileTypes: true })) {
67947
+ for (const entry of readdirSync9(installedRules, { withFileTypes: true })) {
67670
67948
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
67671
67949
  continue;
67672
67950
  if (existsSync15(path33.join(sourceRules, entry.name)))
@@ -67683,7 +67961,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
67683
67961
  if (existsSync15(legacyRules)) {
67684
67962
  try {
67685
67963
  mkdirSync7(path33.join(res.cursorPluginDir, "rules"), { recursive: true });
67686
- for (const entry of readdirSync8(legacyRules, { withFileTypes: true })) {
67964
+ for (const entry of readdirSync9(legacyRules, { withFileTypes: true })) {
67687
67965
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
67688
67966
  continue;
67689
67967
  const target = path33.join(res.cursorPluginDir, "rules", entry.name);
@@ -67693,7 +67971,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
67693
67971
  } catch {}
67694
67972
  }
67695
67973
  rmSync4(legacy, { recursive: true, force: true });
67696
- }, failedDetail = (report) => report.checks.filter((c) => c.status === "fail").map((c) => `${c.id}: ${c.detail}`).join("; "), setupCompletionGuidance = () => [
67974
+ }, failedDetail = (report) => report.checks.filter((c) => c.status === "fail").map((c) => c.detail.startsWith(`${c.id}:`) ? c.detail : `${c.id}: ${c.detail}`).join("; "), setupCompletionGuidance = () => [
67697
67975
  "Run `workit doctor` to verify your installation.",
67698
67976
  "Run /wk-status in OpenCode to review token setup steps."
67699
67977
  ];
@@ -70762,7 +71040,43 @@ function Screen({
70762
71040
  }, undefined, false, undefined, this)
70763
71041
  ]
70764
71042
  }, undefined, true, undefined, this);
70765
- case "project":
71043
+ case "project": {
71044
+ const target = projectTargetFor(resolveBasePath(draft.values));
71045
+ if (!target.ok) {
71046
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
71047
+ flexDirection: "column",
71048
+ gap: 1,
71049
+ children: [
71050
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
71051
+ bold: true,
71052
+ children: "Step 6 — Project setup"
71053
+ }, undefined, false, undefined, this),
71054
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
71055
+ color: "yellow",
71056
+ children: [
71057
+ "Skipped: ",
71058
+ target.refused
71059
+ ]
71060
+ }, undefined, true, undefined, this),
71061
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(ConfirmInput, {
71062
+ defaultChoice: "confirm",
71063
+ submitOnEnter: false,
71064
+ onConfirm: () => {
71065
+ dispatch({ type: "set", field: "applyProject", value: true });
71066
+ dispatch({ type: "next" });
71067
+ },
71068
+ onCancel: () => {
71069
+ dispatch({ type: "set", field: "applyProject", value: false });
71070
+ dispatch({ type: "next" });
71071
+ }
71072
+ }, undefined, false, undefined, this),
71073
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
71074
+ dimColor: true,
71075
+ children: "y to keep this note in the summary · n to skip it · either adds no project files · b Back · Esc Cancel"
71076
+ }, undefined, false, undefined, this)
71077
+ ]
71078
+ }, undefined, true, undefined, this);
71079
+ }
70766
71080
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
70767
71081
  flexDirection: "column",
70768
71082
  gap: 1,
@@ -70775,7 +71089,7 @@ function Screen({
70775
71089
  dimColor: true,
70776
71090
  children: [
70777
71091
  "Apply gitignore + hygiene in ",
70778
- resolveBasePath(draft.values),
71092
+ target.root,
70779
71093
  "? (Existing files are never overwritten.)"
70780
71094
  ]
70781
71095
  }, undefined, true, undefined, this),
@@ -70797,6 +71111,7 @@ function Screen({
70797
71111
  }, undefined, false, undefined, this)
70798
71112
  ]
70799
71113
  }, undefined, true, undefined, this);
71114
+ }
70800
71115
  case "summary": {
70801
71116
  const policy = effectivePolicy(draft.values);
70802
71117
  const preview = buildSetupPreview(draft.values, { cwd: resolveBasePath(draft.values) });
@@ -70938,8 +71253,15 @@ function Screen({
70938
71253
  }, undefined, true, undefined, this),
70939
71254
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
70940
71255
  children: [
70941
- "Project hygiene: ",
70942
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
71256
+ "Project hygiene:",
71257
+ " ",
71258
+ preview.projectRefused ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
71259
+ color: "yellow",
71260
+ children: [
71261
+ "skipped — ",
71262
+ preview.projectRefused
71263
+ ]
71264
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
70943
71265
  color: "green",
70944
71266
  children: draft.values.applyProject ? "yes" : "no"
70945
71267
  }, undefined, false, undefined, this)
@@ -71028,7 +71350,11 @@ function Screen({
71028
71350
  }, undefined, false, undefined, this);
71029
71351
  }
71030
71352
  }
71031
- var import_react66, jsx_dev_runtime2, PLATFORM_LABELS, BRANCH_PRESETS, BRANCH_PRESET_DESCRIPTIONS, SCREEN_PLACEHOLDERS, VCS_PROVIDERS, LOCALE_PICKER_OPTIONS, ISSUE_TRACKERS, WORKSPACE_ADVANCED_FIELDS, PROFILE_FIELDS, TRACK_FIELDS, enumFieldOptions = (field) => {
71353
+ var import_react66, jsx_dev_runtime2, lastProjectTarget = null, projectTargetFor = (base) => {
71354
+ if (lastProjectTarget?.base !== base)
71355
+ lastProjectTarget = { base, target: projectTarget(base) };
71356
+ return lastProjectTarget.target;
71357
+ }, PLATFORM_LABELS, BRANCH_PRESETS, BRANCH_PRESET_DESCRIPTIONS, SCREEN_PLACEHOLDERS, VCS_PROVIDERS, LOCALE_PICKER_OPTIONS, ISSUE_TRACKERS, WORKSPACE_ADVANCED_FIELDS, PROFILE_FIELDS, TRACK_FIELDS, enumFieldOptions = (field) => {
71032
71358
  if (field === "commitPolicy.preset")
71033
71359
  return [{ label: "Inherit global", value: "inherit" }, ...commitPresetOptions];
71034
71360
  if (field === "branchPolicy.preset")
@@ -71070,6 +71396,7 @@ var init_steps = __esm(async () => {
71070
71396
  init_logic();
71071
71397
  init_workspaces();
71072
71398
  init_branch_policy();
71399
+ init_hygiene();
71073
71400
  init_wizard_state();
71074
71401
  init_detect_hosts();
71075
71402
  await __promiseAll([
@@ -71461,7 +71788,7 @@ var init_init = __esm(() => {
71461
71788
  });
71462
71789
 
71463
71790
  // packages/workit-cli/src/upgrade.ts
71464
- import { spawnSync as spawnSync15 } from "node:child_process";
71791
+ import { spawnSync as spawnSync16 } from "node:child_process";
71465
71792
  import {
71466
71793
  existsSync as existsSync18,
71467
71794
  mkdtempSync as mkdtempSync3,
@@ -71817,7 +72144,7 @@ async function runLaunchCommand(argv, deps = {}) {
71817
72144
  }
71818
72145
  if (deps.run)
71819
72146
  return deps.run(host, hostArgs).status ?? 1;
71820
- return spawnSync15(launch.command, launch.args, {
72147
+ return spawnSync16(launch.command, launch.args, {
71821
72148
  stdio: "inherit",
71822
72149
  env
71823
72150
  }).status ?? 1;
@@ -71999,7 +72326,7 @@ var init_launch = __esm(() => {
71999
72326
  });
72000
72327
 
72001
72328
  // packages/workit-core/src/knowledge.ts
72002
- import { spawnSync as spawnSync16 } from "node:child_process";
72329
+ import { spawnSync as spawnSync17 } from "node:child_process";
72003
72330
  import fs18 from "node:fs";
72004
72331
  import path38 from "node:path";
72005
72332
  function lintKnowledge(root) {
@@ -72120,7 +72447,7 @@ var AGENTS_BYTE_BUDGET, STEERING_FILES, NEED_BASED_FILES, STANDARDS_FILE = "CODI
72120
72447
  return "unreadable";
72121
72448
  }
72122
72449
  }, trackedTopLevel = (root) => {
72123
- const run = spawnSync16("git", ["ls-files", "-z", "--cached"], {
72450
+ const run = spawnSync17("git", ["ls-files", "-z", "--cached"], {
72124
72451
  cwd: root,
72125
72452
  encoding: "utf8",
72126
72453
  maxBuffer: 256 * 1024 * 1024,
@@ -72488,7 +72815,8 @@ var R = "[REDACTED]", TOKEN_PATTERNS, WEBHOOK, URL_USERINFO, CURL_USER, KEY, ASS
72488
72815
  var init_redact = __esm(() => {
72489
72816
  TOKEN_PATTERNS = [
72490
72817
  /\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})/gu,
72491
- /\bgl[a-z]{1,8}-[A-Za-z0-9_-]{16,}/gu,
72818
+ /\b(?:glpat|gloas|gldt|glrtr|glrt|glcbt|glptt|glft|glimt|glagent|glwt|glsoat|glffct)-[A-Za-z0-9_-]{20,}/gu,
72819
+ /\b_gitlab_session=[A-Za-z0-9%_-]{16,}/gu,
72492
72820
  /\bnpm_[A-Za-z0-9]{30,}/gu,
72493
72821
  /\bxox[abposr]-[A-Za-z0-9-]{10,}/gu,
72494
72822
  /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu,
@@ -72522,7 +72850,7 @@ var init_redact = __esm(() => {
72522
72850
  });
72523
72851
 
72524
72852
  // packages/workit-core/src/checks.ts
72525
- import { spawn, spawnSync as spawnSync17 } from "node:child_process";
72853
+ import { spawn, spawnSync as spawnSync18 } from "node:child_process";
72526
72854
  import { createHash as createHash16 } from "node:crypto";
72527
72855
  import fs19 from "node:fs";
72528
72856
  import os13 from "node:os";
@@ -72704,7 +73032,7 @@ function runCheckCommand(argv, options) {
72704
73032
  if (!child?.pid)
72705
73033
  return;
72706
73034
  if (windows)
72707
- spawnSync17("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
73035
+ spawnSync18("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
72708
73036
  stdio: "ignore",
72709
73037
  windowsHide: true
72710
73038
  });
@@ -74040,7 +74368,7 @@ var init_check = __esm(() => {
74040
74368
  var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock) => ({ ok: false, code, error, ...unblock ? { unblock } : {} });
74041
74369
 
74042
74370
  // packages/workit-core/src/git/ops.ts
74043
- import { spawnSync as spawnSync18 } from "node:child_process";
74371
+ import { spawnSync as spawnSync19 } from "node:child_process";
74044
74372
  import { existsSync as existsSync19, readFileSync as readFileSync19 } from "node:fs";
74045
74373
  import path42 from "node:path";
74046
74374
  function operationInProgress(cwd) {
@@ -74355,6 +74683,14 @@ function gitCommit(cwd, input) {
74355
74683
  const session = input.actor.session;
74356
74684
  if (session && !SESSION_SAFE.test(session))
74357
74685
  return failure3("invalid_input", "WORKIT_SESSION_ID must be 1-128 characters of [A-Za-z0-9_.:@/+-]");
74686
+ if (session) {
74687
+ const env = input.env ?? process.env;
74688
+ if (issuedBinding(cwd, session) !== null)
74689
+ return failure3("blocked", `judge_session: ${session} was issued to a verifier or reviewer subagent; it records verdicts and never authors commits`, "commit from the implementer's or the lead's own session (unset WORKIT_SESSION_ID back to the shell's)");
74690
+ const refusal = sessionClaimRefusal(env, session, cwd);
74691
+ if (refusal)
74692
+ return failure3("blocked", refusal.error, "commit from the session this shell really is: never set WORKIT_SESSION_ID by hand for a commit (unset it, or use the id the host put in this shell)");
74693
+ }
74358
74694
  const changeRequired = !input.amend && !input.allowEmpty;
74359
74695
  const emptyAmend = previous !== null && git5(cwd, ["rev-parse", "--verify", "-q", `${previous}^{tree}`]).stdout.trim() === git5(cwd, ["rev-parse", "--verify", "-q", `${previous}~1^{tree}`]).stdout.trim();
74360
74696
  const args = ["commit", "--no-edit"];
@@ -74853,7 +75189,7 @@ ${run.stdout}`;
74853
75189
  } : { ok: false, lease: false, error: gitError(run) };
74854
75190
  }
74855
75191
  var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}) => {
74856
- const result = spawnSync18("git", args, {
75192
+ const result = spawnSync19("git", args, {
74857
75193
  cwd,
74858
75194
  encoding: "utf8",
74859
75195
  env: { ...options.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
@@ -74898,6 +75234,7 @@ var init_ops = __esm(() => {
74898
75234
  init_vcs_config();
74899
75235
  init_workspaces();
74900
75236
  init_redact();
75237
+ init_host_session();
74901
75238
  init_ledger();
74902
75239
  init_rev();
74903
75240
  COMMIT_TIMEOUT_MS = 10 * 60000;
@@ -75386,7 +75723,7 @@ var init_report = __esm(() => {
75386
75723
  });
75387
75724
 
75388
75725
  // packages/workit-core/src/forge/pr-ops.ts
75389
- import { spawnSync as spawnSync19 } from "node:child_process";
75726
+ import { spawnSync as spawnSync20 } from "node:child_process";
75390
75727
  function fillFromCommits(cwd, resolved, base) {
75391
75728
  const tracking = `refs/remotes/${resolved.baseRemote}/${base}`;
75392
75729
  if (!base.startsWith("-"))
@@ -75394,7 +75731,7 @@ function fillFromCommits(cwd, resolved, base) {
75394
75731
  const from = resolveRef(cwd, tracking) ?? resolveRef(cwd, `refs/heads/${base}`);
75395
75732
  if (!from)
75396
75733
  return failure3("not_found", `base ${base} is not available locally`, `git fetch ${resolved.baseRemote} ${base}`);
75397
- const log = spawnSync19("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
75734
+ const log = spawnSync20("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
75398
75735
  cwd,
75399
75736
  encoding: "utf8",
75400
75737
  timeout: GIT_TIMEOUTS.local,
@@ -75591,7 +75928,7 @@ function squashCommitList(cwd, resolved, base, head) {
75591
75928
  const from = resolveRef(cwd, `refs/remotes/${resolved.baseRemote}/${base}`) ?? resolveRef(cwd, `refs/heads/${base}`);
75592
75929
  if (!from)
75593
75930
  return "";
75594
- const log = spawnSync19("git", ["log", "--reverse", "--no-merges", "--format=%B%x1e", `${from}..${head}`], {
75931
+ const log = spawnSync20("git", ["log", "--reverse", "--no-merges", "--format=%B%x1e", `${from}..${head}`], {
75595
75932
  cwd,
75596
75933
  encoding: "utf8",
75597
75934
  timeout: GIT_TIMEOUTS.local,
@@ -75664,7 +76001,7 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
75664
76001
  if (reason)
75665
76002
  return refuse(`protected_branch: --delete-branch would delete ${branch}, which ${reason}; workit never deletes it`, `workit pr merge${input.pr ? ` --pr ${doc.number}` : ""} --method ${input.method} # without --delete-branch`, { reason: "protected_branch" });
75666
76003
  }
75667
- const ledger = readLedger(cwd);
76004
+ const ledger = readLedger(cwd, { integrity: true });
75668
76005
  if (!ledger.ok)
75669
76006
  return ledger;
75670
76007
  const check = checkVerdicts(cwd, branch, ledger.value.rows);
@@ -75696,18 +76033,54 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
75696
76033
  unverified = { reason, recorded: String(recorded.value.id) };
75697
76034
  } else if (check.head !== head)
75698
76035
  return refuse(`head_mismatch: the verdict check reads ${branch} at ${check.head?.slice(0, 12) ?? "(missing)"} but ${label} is at ${head.slice(0, 12)}`, `git fetch && git switch ${branch} && git merge --ff-only @{u} # then re-verify that head`, { reason: "head_mismatch", verdict: summary });
76036
+ else if (!covered && check.accepted.reasons.includes("ledger_tampered"))
76037
+ return refuse(`ledger_tampered: a ledger row was edited, removed or moved, so no verdict for ${label} is accepted until it is repaired`, tamperedUnblock(cwd), { reason: "ledger_tampered", verdict: summary });
75699
76038
  else if (!covered)
75700
76039
  return refuse(`NEEDS_VERDICT: ${label} has no accepted independent verdict for ${head.slice(0, 12)} (${check.accepted.reasons.join(", ")})`, `an independent session verifies the head and runs: workit ledger verdict verified --how "<what was exercised>" --branch ${branch}${grant.allowUnverified ? `; or, only if the user asks to merge unverified: workit pr merge --unverified --reason "<why>"` : ""}`, { reason: "needs_verdict", verdict: summary });
75701
76040
  const squashMessage = squashMessageFor(cwd, resolved, input, doc);
75702
76041
  if (squashMessage && !squashMessage.ok)
75703
76042
  return squashMessage;
76043
+ const intent = appendObserved(cwd, {
76044
+ type: "pr.merge.intent",
76045
+ actor: input.actor,
76046
+ branch,
76047
+ head,
76048
+ pr: doc.number,
76049
+ base: doc.base,
76050
+ method: input.method,
76051
+ verdictId: verdict.verdictId,
76052
+ ...unverified ? { unverified: unverified.recorded } : {},
76053
+ grant: grant.source
76054
+ });
76055
+ if (!intent.ok)
76056
+ return refuse(`intent_unrecorded: the merge intent for ${label} could not be recorded (${intent.error}); nothing was merged`, "fix the ledger (workit ledger check), then merge again", { reason: "intent_unrecorded" });
76057
+ const intentId = String(intent.value.id);
75704
76058
  const merged = resolved.forge.merge(doc.number, {
75705
76059
  sha: head,
75706
76060
  method: input.method,
75707
76061
  ...squashMessage ? { squashMessage: squashMessage.data } : {}
75708
76062
  });
75709
76063
  if (!merged.ok)
75710
- return merged;
76064
+ return {
76065
+ ...merged,
76066
+ error: `${merged.error} (merge intent ${intentId} is recorded: if the forge merged anyway, workit pr status reads it)`
76067
+ };
76068
+ const row = appendObserved(cwd, {
76069
+ type: "pr.merged",
76070
+ actor: input.actor,
76071
+ branch,
76072
+ head,
76073
+ pr: doc.number,
76074
+ base: doc.base,
76075
+ mergeSha: merged.data.mergeSha,
76076
+ method: input.method,
76077
+ verdictId: verdict.verdictId,
76078
+ ...unverified ? { unverified: unverified.recorded } : {},
76079
+ grant: grant.source,
76080
+ intent: intentId,
76081
+ verified: false
76082
+ });
76083
+ const recorded = row.ok ? { id: String(row.value.id) } : { error: row.error };
75711
76084
  let state = "open";
75712
76085
  for (const delay of [0, ...VERIFY_DELAYS_MS]) {
75713
76086
  if (delay)
@@ -75722,9 +76095,21 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
75722
76095
  return {
75723
76096
  ok: false,
75724
76097
  code: "failed",
75725
- error: `merge_unverified: the forge accepted the merge of ${label} but still reports it ${state}`,
75726
- unblock: `workit verify-delivery merged --pr ${doc.number}`
76098
+ error: `merge_unverified: the forge accepted the merge of ${label} but still reports it ${state}${"id" in recorded ? `; recorded as pr.merged ${recorded.id} (verified: false)` : `; the pr.merged row could not be written (${recorded.error})`}`,
76099
+ unblock: `workit verify-delivery merged --pr ${doc.number}`,
76100
+ recorded
75727
76101
  };
76102
+ const confirmation = "id" in recorded ? appendObserved(cwd, {
76103
+ type: "pr.merge.verified",
76104
+ actor: input.actor,
76105
+ branch,
76106
+ head,
76107
+ pr: doc.number,
76108
+ base: doc.base,
76109
+ merged: recorded.id,
76110
+ mergeSha: merged.data.mergeSha
76111
+ }) : null;
76112
+ const confirmed = confirmation === null ? { error: "no pr.merged row to confirm" } : confirmation.ok ? { id: String(confirmation.value.id) } : { error: confirmation.error };
75728
76113
  let deletedBranch = false;
75729
76114
  if (input.deleteBranch) {
75730
76115
  const deleted = deleteRemoteBranch(cwd, resolved.remote, branch, head);
@@ -75732,19 +76117,6 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
75732
76117
  deletedBranch = deleted.ok || gone && gone.ok && gone.sha === null ? true : { error: deleted.error };
75733
76118
  }
75734
76119
  const mergeBack = mergeBackFor(cwd, doc.base, branch);
75735
- const row = appendObserved(cwd, {
75736
- type: "pr.merged",
75737
- actor: input.actor,
75738
- branch,
75739
- head,
75740
- pr: doc.number,
75741
- base: doc.base,
75742
- mergeSha: merged.data.mergeSha,
75743
- method: input.method,
75744
- verdictId: verdict.verdictId,
75745
- ...unverified ? { unverified: unverified.recorded } : {},
75746
- grant: grant.source
75747
- });
75748
76120
  return {
75749
76121
  ok: true,
75750
76122
  data: {
@@ -75759,7 +76131,8 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
75759
76131
  unverified,
75760
76132
  grant: { source: grant.source },
75761
76133
  deletedBranch,
75762
- recorded: row.ok ? { id: String(row.value.id) } : { error: row.error },
76134
+ recorded,
76135
+ confirmed,
75763
76136
  mergeBack: mergeBack.branches,
75764
76137
  mergeBackTrack: mergeBack.track
75765
76138
  }
@@ -75817,8 +76190,8 @@ var init_pr_ops = __esm(() => {
75817
76190
  });
75818
76191
 
75819
76192
  // packages/workit-core/src/stack.ts
75820
- import { spawnSync as spawnSync20 } from "node:child_process";
75821
- import { createHash as createHash17, randomBytes as randomBytes3 } from "node:crypto";
76193
+ import { spawnSync as spawnSync21 } from "node:child_process";
76194
+ import { createHash as createHash17, randomBytes as randomBytes4 } from "node:crypto";
75822
76195
  import fs22 from "node:fs";
75823
76196
  import path44 from "node:path";
75824
76197
  function worktreeOf(cwd, branch) {
@@ -75864,7 +76237,7 @@ function gitRestack(cwd, input) {
75864
76237
  if (!scratch)
75865
76238
  return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
75866
76239
  fs22.mkdirSync(scratch, { recursive: true });
75867
- dir = path44.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes3(4).toString("hex")}`);
76240
+ dir = path44.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes4(4).toString("hex")}`);
75868
76241
  const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
75869
76242
  if (!added.ok)
75870
76243
  return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
@@ -76073,13 +76446,13 @@ function takeLock(lock) {
76073
76446
  pid: process.pid,
76074
76447
  processStart: processStartOf(process.pid),
76075
76448
  host: localLockHost(),
76076
- nonce: randomBytes3(8).toString("hex")
76449
+ nonce: randomBytes4(8).toString("hex")
76077
76450
  };
76078
76451
  fs22.writeFileSync(path44.join(lock, OWNER), JSON.stringify(owner));
76079
76452
  return owner;
76080
76453
  }
76081
76454
  function reapLock(lock, observed) {
76082
- const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
76455
+ const tomb = `${lock}.reap-${randomBytes4(6).toString("hex")}`;
76083
76456
  try {
76084
76457
  fs22.renameSync(lock, tomb);
76085
76458
  } catch {
@@ -76100,7 +76473,7 @@ function releaseLock(lock, owner) {
76100
76473
  const { raw } = readOwner(lock);
76101
76474
  if (raw !== JSON.stringify(owner))
76102
76475
  return;
76103
- const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
76476
+ const tomb = `${lock}.release-${randomBytes4(6).toString("hex")}`;
76104
76477
  try {
76105
76478
  fs22.renameSync(lock, tomb);
76106
76479
  fs22.rmSync(tomb, { recursive: true, force: true });
@@ -76330,7 +76703,7 @@ function commitKeys(cwd, range) {
76330
76703
  const patches = new Map;
76331
76704
  const diff = gitRun3(cwd, ["log", "--no-merges", "-p", "--format=commit %H", range]);
76332
76705
  if (diff.ok && diff.stdout.trim()) {
76333
- const ids = spawnSync20("git", ["patch-id", "--stable"], {
76706
+ const ids = spawnSync21("git", ["patch-id", "--stable"], {
76334
76707
  cwd,
76335
76708
  input: diff.stdout,
76336
76709
  encoding: "utf8",
@@ -76423,7 +76796,7 @@ function stackStatus(ctx, stack) {
76423
76796
  const mismatch = repoMismatch(ctx, stack);
76424
76797
  if (mismatch)
76425
76798
  return mismatch;
76426
- const ledger = readLedger(ctx.cwd);
76799
+ const ledger = readLedger(ctx.cwd, { integrity: true });
76427
76800
  if (!ledger.ok)
76428
76801
  return stackFail(ledger.code, ledger.error, ledger.unblock);
76429
76802
  const remote = remoteFor(ctx.cwd, ctx.resolved, stack.trunk);
@@ -76847,7 +77220,7 @@ async function landStack(ctx, stack, options) {
76847
77220
  absorb(synced.data);
76848
77221
  }
76849
77222
  const ledgerRows = () => {
76850
- const ledger = readLedger(ctx.cwd);
77223
+ const ledger = readLedger(ctx.cwd, { integrity: true });
76851
77224
  return ledger.ok ? ledger.value.rows : [];
76852
77225
  };
76853
77226
  const simulated = new Set;
@@ -76954,7 +77327,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
76954
77327
  ...unblock ? { unblock } : {},
76955
77328
  ...data ? { data } : {}
76956
77329
  }), fromForge = (result) => stackFail(result.code, result.error, result.unblock), stackDeps, gitRun3 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
76957
- const run = spawnSync20("git", args, {
77330
+ const run = spawnSync21("git", args, {
76958
77331
  cwd,
76959
77332
  encoding: "utf8",
76960
77333
  timeout: timeoutMs,
@@ -77035,7 +77408,7 @@ var init_stack = __esm(() => {
77035
77408
  });
77036
77409
 
77037
77410
  // packages/workit-core/src/forge/pr-edit.ts
77038
- import { spawnSync as spawnSync21 } from "node:child_process";
77411
+ import { spawnSync as spawnSync22 } from "node:child_process";
77039
77412
  function openPr(cwd, resolved, pr) {
77040
77413
  const grant = requireGrant(cwd, "pr");
77041
77414
  if (!grant.allowed)
@@ -77200,7 +77573,7 @@ function replyToThread(cwd, resolved, input) {
77200
77573
  });
77201
77574
  }
77202
77575
  var label = (resolved, number) => `${resolved.forge.kind === "github" ? "PR #" : "MR !"}${number}`, git6 = (cwd, args) => {
77203
- const run = spawnSync21("git", args, {
77576
+ const run = spawnSync22("git", args, {
77204
77577
  cwd,
77205
77578
  encoding: "utf8",
77206
77579
  timeout: GIT_TIMEOUTS.local,
@@ -77220,7 +77593,7 @@ var init_pr_edit = __esm(() => {
77220
77593
  });
77221
77594
 
77222
77595
  // packages/workit-core/src/forge/exec.ts
77223
- import { spawnSync as spawnSync22 } from "node:child_process";
77596
+ import { spawnSync as spawnSync23 } from "node:child_process";
77224
77597
  import fs23 from "node:fs";
77225
77598
  import path45 from "node:path";
77226
77599
  function findExecutable(bin, env) {
@@ -77255,7 +77628,7 @@ function systemRunner(kind, apiHost, env = process.env) {
77255
77628
  if (!exe)
77256
77629
  return { status: null, stdout: "", stderr: "", timedOut: false, missing: true };
77257
77630
  const token = options.token ? kind === "github" ? { GH_TOKEN: options.token, GH_ENTERPRISE_TOKEN: options.token } : { GITLAB_TOKEN: options.token } : {};
77258
- const result = spawnSync22(exe, [...args], {
77631
+ const result = spawnSync23(exe, [...args], {
77259
77632
  encoding: "utf8",
77260
77633
  env: { ...childEnv, ...token },
77261
77634
  maxBuffer: MAX_BUFFER2,
@@ -78255,6 +78628,14 @@ function resolveCredential(workspace, derived, run, scope) {
78255
78628
  const explicit = typeof workspaceHost === "string" && workspaceHost.trim().toLowerCase().replace(/^https?:\/\//u, "").replace(/\/.*$/u, "") === apiHost;
78256
78629
  if (derived.via === "host_name" && !explicit)
78257
78630
  return failure3("blocked", `token_host_unverified: push host ${derived.host} only looks like ${kind}; workspace ${workspace?.name ?? "?"} vcs.tokenFile is not sent to it`, `if ${apiHost} is your ${kind} server, set ${kind}.host in ~/.config/workit/vcs.json (or vcs.host for the workspace); otherwise fix the push remote`);
78631
+ if (process.platform !== "win32") {
78632
+ let mode = 0;
78633
+ try {
78634
+ mode = fs24.statSync(tokenFile.trim()).mode;
78635
+ } catch {}
78636
+ if ((mode & 63) !== 0)
78637
+ return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile is readable by group or other (mode ${(mode & 511).toString(8).padStart(4, "0")})`, `chmod 600 ${tokenFile.trim()}`);
78638
+ }
78258
78639
  let token = "";
78259
78640
  try {
78260
78641
  token = fs24.readFileSync(tokenFile.trim(), "utf8").trim();
@@ -78678,17 +79059,20 @@ async function status(argv, io) {
78678
79059
  });
78679
79060
  }
78680
79061
  function verdictBlock(cwd, branch, doc) {
78681
- const ledger = readLedger(cwd);
79062
+ const ledger = readLedger(cwd, { integrity: true });
78682
79063
  if (!ledger.ok)
78683
79064
  return null;
78684
79065
  const check = checkVerdicts(cwd, branch, ledger.value.rows);
78685
79066
  let landed = null;
78686
79067
  if (doc.state === "merged") {
78687
- const row = ledger.value.rows.findLast((candidate) => candidate.type === "pr.merged" && candidate.observer === "workit_cli" && candidate.pr === doc.number && candidate.base === doc.base);
79068
+ const mine = (type) => ledger.value.rows.findLast((candidate) => candidate.type === type && candidate.observer === "workit_cli" && candidate.integrity !== "unverified" && candidate.pr === doc.number && candidate.base === doc.base);
79069
+ const merged = mine("pr.merged");
79070
+ const row = merged ?? mine("pr.merge.intent");
78688
79071
  landed = row ? {
78689
79072
  head: typeof row.head === "string" ? row.head : null,
78690
79073
  verdictId: typeof row.verdictId === "string" ? row.verdictId : null,
78691
- unverified: row.unverified !== undefined && row.unverified !== null
79074
+ unverified: row.unverified !== undefined && row.unverified !== null,
79075
+ ...merged ? {} : { intentOnly: true }
78692
79076
  } : "untracked";
78693
79077
  }
78694
79078
  return {
@@ -78859,7 +79243,7 @@ async function merge3(argv, io) {
78859
79243
  if (!result.ok)
78860
79244
  return emit(io, fail(result.code, result.error, {
78861
79245
  unblock: result.unblock,
78862
- data: "refusal" in result && result.refusal ? result.refusal : {}
79246
+ data: "refusal" in result && result.refusal ? result.refusal : ("recorded" in result) && result.recorded ? { recorded: result.recorded } : {}
78863
79247
  }));
78864
79248
  return emit(io, ok(result.data), (data) => [
78865
79249
  `merged ${connected.data.forge.kind === "github" ? "PR #" : "MR !"}${data.number} (${data.method}) at head ${data.head.slice(0, 12)}${data.mergeSha ? ` -> ${data.mergeSha.slice(0, 12)}` : ""}`,
@@ -78868,7 +79252,8 @@ async function merge3(argv, io) {
78868
79252
  `merge back: ${data.base} is the ${data.mergeBackTrack} production branch; bring it into ${data.mergeBack.join(", ")}`
78869
79253
  ] : [],
78870
79254
  ...data.deletedBranch === true ? [`deleted ${data.branch}`] : typeof data.deletedBranch === "object" ? [`branch not deleted: ${data.deletedBranch.error}`] : [],
78871
- ..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
79255
+ ..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : [],
79256
+ ..."error" in data.confirmed && !("error" in data.recorded) ? [`ledger: merge not confirmed in the ledger (${data.confirmed.error})`] : []
78872
79257
  ]);
78873
79258
  }
78874
79259
  async function ready(argv, io) {
@@ -79018,7 +79403,7 @@ async function run11(argv, io) {
79018
79403
  return reply(rest, io);
79019
79404
  return usage3(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE4);
79020
79405
  }
79021
- var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch] [--unverified --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE4 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}`, METHODS2;
79406
+ var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch] [--unverified --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE4 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}${landed.intentOnly ? " (merge intent recorded; the merge call itself failed)" : ""}`, METHODS2;
79022
79407
  var init_pr = __esm(() => {
79023
79408
  init_pr_body();
79024
79409
  init_pr_edit();
@@ -79543,7 +79928,7 @@ var init_git2 = __esm(() => {
79543
79928
  });
79544
79929
 
79545
79930
  // packages/workit-core/src/forge/verify.ts
79546
- import { spawnSync as spawnSync23 } from "node:child_process";
79931
+ import { spawnSync as spawnSync24 } from "node:child_process";
79547
79932
  function finish(cwd, report, actor) {
79548
79933
  const delivered = report.observations.length > 0 && report.observations.every((entry) => entry.ok);
79549
79934
  const out = { ...report, delivered };
@@ -79756,7 +80141,7 @@ var NPM_NAME, SEMVER2, SAFE_ARG, systemNpm = (args) => {
79756
80141
  };
79757
80142
  const found = process.platform === "win32" ? findExecutable("npm.cmd", process.env) : null;
79758
80143
  const npmCmd = found && !/["%!^]/u.test(found) ? `"${found}"` : "npm.cmd";
79759
- const result = process.platform === "win32" ? spawnSync23(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync23("npm", [...args], options);
80144
+ const result = process.platform === "win32" ? spawnSync24(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync24("npm", [...args], options);
79760
80145
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
79761
80146
  }, short3 = (sha) => sha ? sha.slice(0, 12) : "(absent)", npmView = (stdout) => {
79762
80147
  try {
@@ -80765,7 +81150,7 @@ var init_stack2 = __esm(() => {
80765
81150
  });
80766
81151
 
80767
81152
  // packages/workit-core/src/fanout.ts
80768
- import { spawnSync as spawnSync24 } from "node:child_process";
81153
+ import { spawnSync as spawnSync25 } from "node:child_process";
80769
81154
  import { createHash as createHash18 } from "node:crypto";
80770
81155
  import fs25 from "node:fs";
80771
81156
  import path48 from "node:path";
@@ -81538,7 +81923,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
81538
81923
  text: edges.map((edge) => `add "${edge.dependsOn}" to "dependsOn" of slice ${edge.slice}`).join("; ")
81539
81924
  };
81540
81925
  }, gitRun4 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
81541
- const run = spawnSync24("git", args, {
81926
+ const run = spawnSync25("git", args, {
81542
81927
  cwd,
81543
81928
  encoding: "utf8",
81544
81929
  timeout: timeoutMs,
@@ -81576,7 +81961,7 @@ var init_fanout = __esm(() => {
81576
81961
  });
81577
81962
 
81578
81963
  // packages/workit-core/src/fanout-worktree.ts
81579
- import { randomBytes as randomBytes4 } from "node:crypto";
81964
+ import { randomBytes as randomBytes5 } from "node:crypto";
81580
81965
  import fs26 from "node:fs";
81581
81966
  import path49 from "node:path";
81582
81967
  function rowSample(lines) {
@@ -81660,7 +82045,7 @@ function markAdminDir(worktree) {
81660
82045
  const admin = adminDir(worktree);
81661
82046
  if (!admin)
81662
82047
  return null;
81663
- const token = randomBytes4(16).toString("hex");
82048
+ const token = randomBytes5(16).toString("hex");
81664
82049
  try {
81665
82050
  fs26.writeFileSync(path49.join(admin.path, TOKEN_FILE), `${token}
81666
82051
  `);
@@ -82085,7 +82470,7 @@ var init_fanout_brief = __esm(() => {
82085
82470
  });
82086
82471
 
82087
82472
  // packages/workit-core/src/fanout-landed.ts
82088
- import { spawnSync as spawnSync25 } from "node:child_process";
82473
+ import { spawnSync as spawnSync26 } from "node:child_process";
82089
82474
  function forgeBreaker(resolved) {
82090
82475
  if (!resolved)
82091
82476
  return { forge: null, down: () => null };
@@ -82176,7 +82561,7 @@ function trunkPatchIds(ctx, fork) {
82176
82561
  "--"
82177
82562
  ]);
82178
82563
  if (log.ok && log.stdout.trim()) {
82179
- const run = spawnSync25("git", ["patch-id", "--stable"], {
82564
+ const run = spawnSync26("git", ["patch-id", "--stable"], {
82180
82565
  cwd: ctx.cwd,
82181
82566
  input: log.stdout,
82182
82567
  encoding: "utf8",
@@ -83025,7 +83410,7 @@ var exports_ledger = {};
83025
83410
  __export(exports_ledger, {
83026
83411
  run: () => run18
83027
83412
  });
83028
- import { randomBytes as randomBytes5 } from "node:crypto";
83413
+ import { randomBytes as randomBytes6 } from "node:crypto";
83029
83414
  import { parseArgs } from "node:util";
83030
83415
  function targetBranch(io, rows, branch, pr) {
83031
83416
  if (pr !== undefined) {
@@ -83163,10 +83548,12 @@ async function run18(argv, io) {
83163
83548
  if (rest.length !== 1)
83164
83549
  return usage4(io, `ledger verdict takes one result: ${VERDICT_RESULTS.join("|")}`);
83165
83550
  const claimed = values.self === true ? null : claimedSession(acting);
83166
- const refusal = claimed === null ? null : sessionClaimRefusal(io.env, claimed, io.cwd);
83551
+ const token = values.token ?? io.env.WORKIT_SESSION_TOKEN ?? null;
83552
+ const refusal = claimed === null ? null : sessionClaimRefusal(io.env, claimed, io.cwd, { token, flag: acting.source === "flag" });
83167
83553
  if (refusal)
83168
83554
  return emit(io, fail("blocked", refusal.error, { unblock: refusal.unblock }));
83169
- const verdict = await recorded(recordVerdict(context, {
83555
+ const bound = claimed !== null && issuedTokenMatches(io.cwd, claimed, token);
83556
+ const verdict = await recorded(recordVerdict({ ...context, ...bound ? { sessionBound: true } : {} }, {
83170
83557
  result: rest[0],
83171
83558
  kind: values.kind,
83172
83559
  how: values.how,
@@ -83182,7 +83569,12 @@ async function run18(argv, io) {
83182
83569
  if (shadowed)
83183
83570
  io.stderr(`warning: ${shadowed}
83184
83571
  `);
83185
- return fromResult(io, verdict, (row) => `recorded verdict ${row.id}: ${row.result} [${row.kind}] for ${row.branch} @ ${(row.head ?? "").slice(0, 12)} as ${actor.session ?? "no session"}${row.self ? ` (self${row.selfReason === "no_session" ? ": WORKIT_SESSION_ID unset" : ""}; never accepted)` : ""}`);
83572
+ const withGate = verdict.ok ? { ok: true, value: { ...verdict.value, gate: gateReading(io.cwd, verdict.value) } } : verdict;
83573
+ return fromResult(io, withGate, (row) => [
83574
+ `recorded verdict ${row.id}: ${row.result} [${row.kind}] for ${row.branch} @ ${(row.head ?? "").slice(0, 12)} as ${actor.session ?? "no session"}${row.self ? ` (self${row.selfReason === "no_session" ? ": WORKIT_SESSION_ID unset" : ""}; never accepted)` : ""}`,
83575
+ row.gate ? `gate: ${row.branch} ${row.gate.accepted ? `accepted (verdict ${row.gate.verdictId ?? "?"})` : `not accepted (${row.gate.reasons.join(", ")})`}` : "gate: unknown (ledger unreadable)"
83576
+ ].join(`
83577
+ `));
83186
83578
  }
83187
83579
  function standingFanout(io, values) {
83188
83580
  if (values.fanout !== undefined) {
@@ -83253,7 +83645,7 @@ var USAGE10 = "workit ledger decision|ruling|verdict|standing|list|check ... (wo
83253
83645
  return new Error("--as takes a lowercase role, e.g. verifier");
83254
83646
  const prefix = (actor.session ?? actor.host).slice(0, 96);
83255
83647
  return {
83256
- actor: { ...actor, session: `${prefix}:${values.as}:${randomBytes5(4).toString("hex")}` },
83648
+ actor: { ...actor, session: `${prefix}:${values.as}:${randomBytes6(4).toString("hex")}` },
83257
83649
  source: "as",
83258
83650
  derivedFrom: actor.session
83259
83651
  };
@@ -83296,7 +83688,17 @@ var USAGE10 = "workit ledger decision|ruling|verdict|standing|list|check ... (wo
83296
83688
  ] : [
83297
83689
  `${data.unverified_rows.length} unverified row(s) (not written by the workit CLI, or changed afterwards):`,
83298
83690
  ...data.unverified_rows.map((row) => ` #${row.seq} ${row.id} ${row.type}${row.branch ? ` [${row.branch}]` : ""}: ${row.reason}`)
83299
- ];
83691
+ ], gateReading = (cwd, row) => {
83692
+ const ledger = readLedger(cwd, { integrity: true });
83693
+ if (!ledger.ok || !row.branch)
83694
+ return null;
83695
+ const { accepted } = checkVerdicts(cwd, row.branch, ledger.value.rows);
83696
+ return {
83697
+ accepted: accepted.accepted,
83698
+ reasons: accepted.reasons,
83699
+ verdictId: typeof accepted.verdict?.id === "string" ? accepted.verdict.id : null
83700
+ };
83701
+ };
83300
83702
  var init_ledger2 = __esm(() => {
83301
83703
  init_ledger();
83302
83704
  init_fanout();
@@ -83320,6 +83722,7 @@ var init_ledger2 = __esm(() => {
83320
83722
  last: { type: "string" },
83321
83723
  supersedes: { type: "string" },
83322
83724
  session: { type: "string" },
83725
+ token: { type: "string" },
83323
83726
  as: { type: "string" },
83324
83727
  fanout: { type: "string" },
83325
83728
  json: { type: "boolean" }
@@ -99175,11 +99578,11 @@ var init_rules = __esm(() => {
99175
99578
  });
99176
99579
 
99177
99580
  // packages/workit-cli/src/test-audit/audit.ts
99178
- import { spawnSync as spawnSync26 } from "node:child_process";
99179
- import { existsSync as existsSync20, readdirSync as readdirSync9, readFileSync as readFileSync23, statSync as statSync8 } from "node:fs";
99581
+ import { spawnSync as spawnSync27 } from "node:child_process";
99582
+ import { existsSync as existsSync20, readdirSync as readdirSync10, readFileSync as readFileSync23, statSync as statSync8 } from "node:fs";
99180
99583
  import path52 from "node:path";
99181
99584
  function git7(cwd, args) {
99182
- const run = spawnSync26("git", ["-c", "core.quotepath=off", ...args], {
99585
+ const run = spawnSync27("git", ["-c", "core.quotepath=off", ...args], {
99183
99586
  cwd,
99184
99587
  encoding: "utf8",
99185
99588
  maxBuffer: 64 * 1024 * 1024
@@ -99187,7 +99590,7 @@ function git7(cwd, args) {
99187
99590
  return { ok: run.status === 0, stdout: run.stdout ?? "" };
99188
99591
  }
99189
99592
  function walkTests(dir, out) {
99190
- for (const entry of readdirSync9(dir, { withFileTypes: true })) {
99593
+ for (const entry of readdirSync10(dir, { withFileTypes: true })) {
99191
99594
  if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
99192
99595
  continue;
99193
99596
  const full = path52.join(dir, entry.name);
@@ -99323,7 +99726,7 @@ import {
99323
99726
  lstatSync as lstatSync8,
99324
99727
  mkdirSync as mkdirSync10,
99325
99728
  mkdtempSync as mkdtempSync4,
99326
- readdirSync as readdirSync10,
99729
+ readdirSync as readdirSync11,
99327
99730
  readFileSync as readFileSync24,
99328
99731
  readlinkSync as readlinkSync3,
99329
99732
  realpathSync as realpathSync9,
@@ -99420,7 +99823,7 @@ function removeStaleCopies(tmp = os16.tmpdir()) {
99420
99823
  let removed = 0;
99421
99824
  let names = [];
99422
99825
  try {
99423
- names = readdirSync10(tmp);
99826
+ names = readdirSync11(tmp);
99424
99827
  } catch {
99425
99828
  return 0;
99426
99829
  }
@@ -99449,7 +99852,7 @@ function removeStaleCopies(tmp = os16.tmpdir()) {
99449
99852
  }
99450
99853
  function linkNodeModules(origin, target, repo, copy) {
99451
99854
  mkdirSync10(target, { recursive: true });
99452
- for (const entry of readdirSync10(origin, { withFileTypes: true })) {
99855
+ for (const entry of readdirSync11(origin, { withFileTypes: true })) {
99453
99856
  const from = path53.join(origin, entry.name);
99454
99857
  const to = path53.join(target, entry.name);
99455
99858
  if (entry.name.startsWith("@") && entry.isDirectory()) {
@@ -100586,7 +100989,7 @@ function withSubcommands(entry, options = {}) {
100586
100989
  };
100587
100990
  }
100588
100991
  var VERIFY_OPTIONS = "[--expect pushed|pr|merged|released] [--pr <n> | --branch <b>] [--sha <sha>] [--tag <t>] [--package <name[@version]>]";
100589
- var LEDGER_ACTOR = "[--branch <b> | --pr <n>] [--supersedes <id>] [--session <id> | --as <role>]";
100992
+ var LEDGER_ACTOR = "[--branch <b> | --pr <n>] [--supersedes <id>] [--session <id> [--token <t>] | --as <role>]";
100590
100993
  var VERBS = [
100591
100994
  {
100592
100995
  name: "init",
@@ -100915,7 +101318,7 @@ var VERBS = [
100915
101318
  },
100916
101319
  {
100917
101320
  name: "standing",
100918
- usage: `workit ledger standing add "<order>" | standing list | standing clear [<id>] [--fanout <name>] [--supersedes <id>] [--session <id> | --as <role>]`,
101321
+ usage: `workit ledger standing add "<order>" | standing list | standing clear [<id>] [--fanout <name>] [--supersedes <id>] [--session <id> [--token <t>] | --as <role>]`,
100919
101322
  summary: "The lead's standing orders for a fanout's workers"
100920
101323
  },
100921
101324
  {