@brainervirus/workit-codex 8.9.1 → 8.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,9 +24,9 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
24
24
  };
25
25
  });
26
26
  // packages/workit-core/src/hooks/context.ts
27
- import { createHash as createHash10 } from "node:crypto";
28
- import fs13 from "node:fs";
29
- import path16 from "node:path";
27
+ import { createHash as createHash11 } from "node:crypto";
28
+ import fs14 from "node:fs";
29
+ import path17 from "node:path";
30
30
 
31
31
  // packages/workit-core/src/core/methods.ts
32
32
  var METHODS = {
@@ -101,7 +101,8 @@ Autonomy contract:
101
101
  satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
102
102
  - Author is not verifier: your own verdict is \`--self\`, shown as
103
103
  self-reviewed, never verified. High risk, \`verification: independent\` and
104
- merge need a verifier with its own session (\`WORKIT_SESSION_ID=<lead>-v<n>\`).
104
+ merge need a verifier with its own host-derived session (a subagent's
105
+ \`<lead>:<child>\`, set by the host); never hand-mint one such as \`<lead>-v1\`.
105
106
  - A local-commit endpoint does not imply PR readiness, and a local commit alone
106
107
  is not evidence of a requested remote push. Before reporting delivery,
107
108
  reconcile every requested item and observe it (\`workit verify-delivery\`).
@@ -8787,10 +8788,10 @@ function requirementId(input) {
8787
8788
  }
8788
8789
 
8789
8790
  // packages/workit-core/src/core/task-evaluation.ts
8790
- import { createHash as createHash9 } from "node:crypto";
8791
- import * as fs12 from "node:fs";
8791
+ import { createHash as createHash10 } from "node:crypto";
8792
+ import * as fs13 from "node:fs";
8792
8793
  import { spawnSync as spawnSync10 } from "node:child_process";
8793
- import path14 from "node:path";
8794
+ import path15 from "node:path";
8794
8795
 
8795
8796
  // packages/workit-core/src/check-config.ts
8796
8797
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -9179,9 +9180,9 @@ function pushRemoteName(cwd, branch) {
9179
9180
  }
9180
9181
 
9181
9182
  // packages/workit-core/src/core/task-store.ts
9182
- import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
9183
- import * as fs9 from "node:fs";
9184
- import path8 from "node:path";
9183
+ import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
9184
+ import * as fs10 from "node:fs";
9185
+ import path9 from "node:path";
9185
9186
 
9186
9187
  // node_modules/@openclaw/fs-safe/dist/errors.js
9187
9188
  var OPERATIONAL_CODES = new Set([
@@ -9785,6 +9786,9 @@ function acquireFileLockSync(targetPath, options) {
9785
9786
  }
9786
9787
  }
9787
9788
  // packages/workit-core/src/host-session.ts
9789
+ import { createHash as createHash4 } from "node:crypto";
9790
+ import fs7 from "node:fs";
9791
+ import path6 from "node:path";
9788
9792
  var HOST_SESSION_VARS = [
9789
9793
  ["OPENCODE_SESSION_ID", () => "opencode"],
9790
9794
  ["PI_SESSION_ID", () => "pi"],
@@ -9812,16 +9816,44 @@ function hostSessionFromEnv(env) {
9812
9816
  }
9813
9817
  return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
9814
9818
  }
9819
+ var ISSUED_PER_LEAD = 64;
9820
+ var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/1";
9821
+ var hash = (value) => createHash4("sha256").update(value).digest("hex").slice(0, 32);
9822
+ var issuedRoot = (cwd) => {
9823
+ const location = resolveStore(cwd);
9824
+ return location instanceof Error ? null : path6.join(location.dir, "hooks", "issued");
9825
+ };
9826
+ var issuedDir = (cwd, lead) => {
9827
+ const root = issuedRoot(cwd);
9828
+ return root === null ? null : path6.join(root, hash(lead));
9829
+ };
9830
+ function recordIssuedSession(cwd, lead, issued) {
9831
+ try {
9832
+ const dir = issuedDir(cwd, lead);
9833
+ if (!dir)
9834
+ return;
9835
+ fs7.mkdirSync(dir, { recursive: true });
9836
+ const own = hash(issued);
9837
+ fs7.writeFileSync(path6.join(dir, own), ISSUED_SESSIONS_MARKER);
9838
+ const entries = fs7.readdirSync(dir);
9839
+ if (entries.length <= ISSUED_PER_LEAD)
9840
+ return;
9841
+ const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs7.statSync(path6.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
9842
+ for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
9843
+ fs7.rmSync(path6.join(dir, name), { force: true });
9844
+ } catch {}
9845
+ }
9846
+ var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
9815
9847
 
9816
9848
  // packages/workit-core/src/core/package-root.ts
9817
9849
  import { existsSync } from "node:fs";
9818
- import path6 from "node:path";
9850
+ import path7 from "node:path";
9819
9851
  import { fileURLToPath } from "node:url";
9820
9852
  var packageRoot = () => {
9821
- let dir = path6.dirname(fileURLToPath(import.meta.url));
9853
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
9822
9854
  while (true) {
9823
- const parent = path6.dirname(dir);
9824
- if (existsSync(path6.join(dir, "package.json")) || parent === dir)
9855
+ const parent = path7.dirname(dir);
9856
+ if (existsSync(path7.join(dir, "package.json")) || parent === dir)
9825
9857
  return dir;
9826
9858
  dir = parent;
9827
9859
  }
@@ -9829,7 +9861,7 @@ var packageRoot = () => {
9829
9861
 
9830
9862
  // packages/workit-core/src/core/store-lock.ts
9831
9863
  import { spawnSync as spawnSync4 } from "node:child_process";
9832
- import fs7 from "node:fs";
9864
+ import fs8 from "node:fs";
9833
9865
  import { hostname } from "node:os";
9834
9866
  var metadataLockSchema = object({
9835
9867
  pid: number2().int().nonnegative().safe(),
@@ -9871,7 +9903,7 @@ var parseProcStatStart = (stat) => {
9871
9903
  var processStartOf = (pid) => {
9872
9904
  if (process.platform === "linux") {
9873
9905
  try {
9874
- return parseProcStatStart(fs7.readFileSync(`/proc/${pid}/stat`, "utf8"));
9906
+ return parseProcStatStart(fs8.readFileSync(`/proc/${pid}/stat`, "utf8"));
9875
9907
  } catch {
9876
9908
  return null;
9877
9909
  }
@@ -9892,7 +9924,7 @@ var processStartOf = (pid) => {
9892
9924
  };
9893
9925
  var readTrimmed = (file) => {
9894
9926
  try {
9895
- return fs7.readFileSync(file, "utf8").trim() || null;
9927
+ return fs8.readFileSync(file, "utf8").trim() || null;
9896
9928
  } catch {
9897
9929
  return null;
9898
9930
  }
@@ -9903,7 +9935,7 @@ var localLockHost = () => {
9903
9935
  return cachedLockHost;
9904
9936
  let pidns = null;
9905
9937
  try {
9906
- pidns = /\[(\d+)\]/.exec(fs7.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
9938
+ pidns = /\[(\d+)\]/.exec(fs8.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
9907
9939
  } catch {}
9908
9940
  const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
9909
9941
  cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
@@ -9949,7 +9981,7 @@ var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options =
9949
9981
  };
9950
9982
  var ageOf = (file, nowMs) => {
9951
9983
  try {
9952
- return nowMs - fs7.lstatSync(file).mtimeMs;
9984
+ return nowMs - fs8.lstatSync(file).mtimeMs;
9953
9985
  } catch {
9954
9986
  return null;
9955
9987
  }
@@ -9960,7 +9992,7 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
9960
9992
  if (age === null || age <= RECLAIM_GUARD_TTL_MS)
9961
9993
  return false;
9962
9994
  try {
9963
- fs7.rmdirSync(guard);
9995
+ fs8.rmdirSync(guard);
9964
9996
  return true;
9965
9997
  } catch {
9966
9998
  return false;
@@ -9969,8 +10001,8 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
9969
10001
 
9970
10002
  // packages/workit-core/src/store/event-log.ts
9971
10003
  import { randomUUID as randomUUID2 } from "node:crypto";
9972
- import fs8 from "node:fs";
9973
- import path7 from "node:path";
10004
+ import fs9 from "node:fs";
10005
+ import path8 from "node:path";
9974
10006
  var EVENT_VERSION = 1;
9975
10007
 
9976
10008
  class LogDamage extends Error {
@@ -9999,7 +10031,7 @@ var parseEvent = (line, file, offset) => {
9999
10031
  function readLog(file, from = 0, expectSeq = null) {
10000
10032
  let fd;
10001
10033
  try {
10002
- fd = fs8.openSync(file, "r");
10034
+ fd = fs9.openSync(file, "r");
10003
10035
  } catch (error) {
10004
10036
  if (error.code === "ENOENT")
10005
10037
  return null;
@@ -10008,13 +10040,13 @@ function readLog(file, from = 0, expectSeq = null) {
10008
10040
  let bytes;
10009
10041
  let size;
10010
10042
  try {
10011
- size = fs8.fstatSync(fd).size;
10043
+ size = fs9.fstatSync(fd).size;
10012
10044
  if (from > size)
10013
10045
  throw new LogDamage(`${file}: shorter than its snapshot`);
10014
10046
  bytes = Buffer.alloc(size - from);
10015
10047
  let read = 0;
10016
10048
  while (read < bytes.length) {
10017
- const got = fs8.readSync(fd, bytes, read, bytes.length - read, from + read);
10049
+ const got = fs9.readSync(fd, bytes, read, bytes.length - read, from + read);
10018
10050
  if (got === 0)
10019
10051
  break;
10020
10052
  read += got;
@@ -10022,7 +10054,7 @@ function readLog(file, from = 0, expectSeq = null) {
10022
10054
  bytes = bytes.subarray(0, read);
10023
10055
  size = from + read;
10024
10056
  } finally {
10025
- fs8.closeSync(fd);
10057
+ fs9.closeSync(fd);
10026
10058
  }
10027
10059
  const events = [];
10028
10060
  let start = 0;
@@ -10047,11 +10079,11 @@ function readLog(file, from = 0, expectSeq = null) {
10047
10079
  }
10048
10080
  var fsyncDirectory = (dir) => {
10049
10081
  try {
10050
- const fd = fs8.openSync(dir, "r");
10082
+ const fd = fs9.openSync(dir, "r");
10051
10083
  try {
10052
- fs8.fsyncSync(fd);
10084
+ fs9.fsyncSync(fd);
10053
10085
  } finally {
10054
- fs8.closeSync(fd);
10086
+ fs9.closeSync(fd);
10055
10087
  }
10056
10088
  } catch (error) {
10057
10089
  if (process.platform !== "win32")
@@ -10061,46 +10093,46 @@ var fsyncDirectory = (dir) => {
10061
10093
  function appendEvent(file, event, validEnd) {
10062
10094
  const line = Buffer.from(`${JSON.stringify(event)}
10063
10095
  `, "utf8");
10064
- const created = !fs8.existsSync(file);
10065
- if (!created && fs8.statSync(file).size !== validEnd) {
10066
- const cut = fs8.openSync(file, "r+");
10096
+ const created = !fs9.existsSync(file);
10097
+ if (!created && fs9.statSync(file).size !== validEnd) {
10098
+ const cut = fs9.openSync(file, "r+");
10067
10099
  try {
10068
- fs8.ftruncateSync(cut, validEnd);
10069
- fs8.fsyncSync(cut);
10100
+ fs9.ftruncateSync(cut, validEnd);
10101
+ fs9.fsyncSync(cut);
10070
10102
  } finally {
10071
- fs8.closeSync(cut);
10103
+ fs9.closeSync(cut);
10072
10104
  }
10073
10105
  }
10074
- const fd = fs8.openSync(file, "a", 384);
10106
+ const fd = fs9.openSync(file, "a", 384);
10075
10107
  try {
10076
10108
  let written = 0;
10077
10109
  while (written < line.length)
10078
- written += fs8.writeSync(fd, line, written, line.length - written);
10079
- fs8.fsyncSync(fd);
10110
+ written += fs9.writeSync(fd, line, written, line.length - written);
10111
+ fs9.fsyncSync(fd);
10080
10112
  } finally {
10081
- fs8.closeSync(fd);
10113
+ fs9.closeSync(fd);
10082
10114
  }
10083
10115
  if (created)
10084
- fsyncDirectory(path7.dirname(file));
10116
+ fsyncDirectory(path8.dirname(file));
10085
10117
  return validEnd;
10086
10118
  }
10087
10119
  function replaceFile(file, bytes, durable) {
10088
10120
  const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
10089
10121
  try {
10090
- const fd = fs8.openSync(temporary, "wx", 384);
10122
+ const fd = fs9.openSync(temporary, "wx", 384);
10091
10123
  try {
10092
- fs8.writeSync(fd, bytes);
10124
+ fs9.writeSync(fd, bytes);
10093
10125
  if (durable)
10094
- fs8.fsyncSync(fd);
10126
+ fs9.fsyncSync(fd);
10095
10127
  } finally {
10096
- fs8.closeSync(fd);
10128
+ fs9.closeSync(fd);
10097
10129
  }
10098
10130
  renameWithRetry(temporary, file);
10099
10131
  if (durable)
10100
- fsyncDirectory(path7.dirname(file));
10132
+ fsyncDirectory(path8.dirname(file));
10101
10133
  } catch (error) {
10102
10134
  try {
10103
- fs8.unlinkSync(temporary);
10135
+ fs9.unlinkSync(temporary);
10104
10136
  } catch {}
10105
10137
  throw error;
10106
10138
  }
@@ -10125,7 +10157,7 @@ var retryTransient = (run) => {
10125
10157
  }
10126
10158
  }
10127
10159
  };
10128
- var renameWithRetry = (from, to) => retryTransient(() => fs8.renameSync(from, to));
10160
+ var renameWithRetry = (from, to) => retryTransient(() => fs9.renameSync(from, to));
10129
10161
 
10130
10162
  // packages/workit-core/src/store/patch.ts
10131
10163
  var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -10238,7 +10270,7 @@ var isPatch = (value) => Array.isArray(value) && value.every((op) => {
10238
10270
  });
10239
10271
 
10240
10272
  // packages/workit-core/src/store/reduce.ts
10241
- import { createHash as createHash4 } from "node:crypto";
10273
+ import { createHash as createHash5 } from "node:crypto";
10242
10274
  var OPENING_TYPES = new Set([
10243
10275
  "task.opened",
10244
10276
  "task.imported",
@@ -10267,7 +10299,7 @@ var isObject3 = (value) => typeof value === "object" && value !== null && !Array
10267
10299
  var isKey = (value) => isObject3(value) && typeof value.key === "string" && (value.kind === "branch" || value.kind === "detached" || value.kind === "dir") && (value.branch === null || typeof value.branch === "string");
10268
10300
  var BLOB_REF = "$blob";
10269
10301
  var blobRef = (value) => isObject3(value) && Object.keys(value).length === 1 && typeof value[BLOB_REF] === "string" && /^[0-9a-f]{64}$/.test(value[BLOB_REF]) ? value[BLOB_REF] : null;
10270
- var blobDigest = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
10302
+ var blobDigest = (value) => createHash5("sha256").update(JSON.stringify(value)).digest("hex");
10271
10303
  var toBlob = (value, blobs) => {
10272
10304
  if (!isObject3(value))
10273
10305
  return value;
@@ -10422,7 +10454,7 @@ var indexEntry = (task, key, legacy, file) => {
10422
10454
  };
10423
10455
  var fileSignature = (file) => {
10424
10456
  try {
10425
- const stat = fs9.statSync(file, { bigint: true });
10457
+ const stat = fs10.statSync(file, { bigint: true });
10426
10458
  return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
10427
10459
  } catch {
10428
10460
  return null;
@@ -10440,7 +10472,7 @@ var cachedRuntimeVersion = null;
10440
10472
  var runtimeVersion = () => {
10441
10473
  if (cachedRuntimeVersion === null) {
10442
10474
  try {
10443
- const pkg = JSON.parse(fs9.readFileSync(path8.join(packageRoot(), "package.json"), "utf8"));
10475
+ const pkg = JSON.parse(fs10.readFileSync(path9.join(packageRoot(), "package.json"), "utf8"));
10444
10476
  cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
10445
10477
  } catch {
10446
10478
  cachedRuntimeVersion = "0.0.0";
@@ -10466,14 +10498,14 @@ var isNewerVersion = (candidate, current) => {
10466
10498
  var validId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
10467
10499
  var readText = (file) => {
10468
10500
  try {
10469
- return fs9.readFileSync(file, "utf8");
10501
+ return fs10.readFileSync(file, "utf8");
10470
10502
  } catch {
10471
10503
  return "";
10472
10504
  }
10473
10505
  };
10474
10506
  var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
10475
10507
  var branchStart = (location, branch, aliases) => {
10476
- const first = readText(path8.join(path8.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
10508
+ const first = readText(path9.join(path9.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
10477
10509
  `, 1)[0];
10478
10510
  const created = REFLOG_LINE.exec(first);
10479
10511
  const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
@@ -10483,7 +10515,7 @@ var branchStart = (location, branch, aliases) => {
10483
10515
  return start;
10484
10516
  if (!location.git)
10485
10517
  return null;
10486
- for (const line of readText(path8.join(location.git.gitDir, "logs", "HEAD")).split(`
10518
+ for (const line of readText(path9.join(location.git.gitDir, "logs", "HEAD")).split(`
10487
10519
  `)) {
10488
10520
  const entry = REFLOG_LINE.exec(line);
10489
10521
  const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
@@ -10493,23 +10525,23 @@ var branchStart = (location, branch, aliases) => {
10493
10525
  }
10494
10526
  return null;
10495
10527
  };
10496
- var refExists = (common, ref) => fs9.existsSync(path8.join(common, ref)) || readText(path8.join(common, "packed-refs")).includes(` ${ref}
10528
+ var refExists = (common, ref) => fs10.existsSync(path9.join(common, ref)) || readText(path9.join(common, "packed-refs")).includes(` ${ref}
10497
10529
  `);
10498
10530
  var origins = new Map;
10499
10531
  var sameDirectoryIdentity = (left, right) => {
10500
- if (!path8.isAbsolute(left) || !path8.isAbsolute(right))
10532
+ if (!path9.isAbsolute(left) || !path9.isAbsolute(right))
10501
10533
  return false;
10502
- const normalizedLeft = path8.resolve(left);
10503
- const normalizedRight = path8.resolve(right);
10534
+ const normalizedLeft = path9.resolve(left);
10535
+ const normalizedRight = path9.resolve(right);
10504
10536
  try {
10505
- const a = fs9.statSync(normalizedLeft, { bigint: true });
10506
- const b = fs9.statSync(normalizedRight, { bigint: true });
10537
+ const a = fs10.statSync(normalizedLeft, { bigint: true });
10538
+ const b = fs10.statSync(normalizedRight, { bigint: true });
10507
10539
  if (!a.isDirectory() || !b.isDirectory())
10508
10540
  return false;
10509
10541
  if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
10510
10542
  return true;
10511
- const canonicalLeft = fs9.realpathSync(normalizedLeft);
10512
- const canonicalRight = fs9.realpathSync(normalizedRight);
10543
+ const canonicalLeft = fs10.realpathSync(normalizedLeft);
10544
+ const canonicalRight = fs10.realpathSync(normalizedRight);
10513
10545
  if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
10514
10546
  return true;
10515
10547
  return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
@@ -10546,8 +10578,8 @@ class TaskStore {
10546
10578
  migrationChecked = false;
10547
10579
  compactDue = new Set;
10548
10580
  constructor(root, options = {}) {
10549
- const resolved = path8.resolve(root);
10550
- this.origin = fs9.existsSync(resolved) ? fs9.realpathSync(resolved) : resolved;
10581
+ const resolved = path9.resolve(root);
10582
+ this.origin = fs10.existsSync(resolved) ? fs10.realpathSync(resolved) : resolved;
10551
10583
  this.located = resolveStore(this.origin);
10552
10584
  this.root = checkoutRootOf(this.origin, this.located);
10553
10585
  this.lockTimeoutMs = options.lockTimeoutMs ?? defaultLockTimeout();
@@ -10555,7 +10587,7 @@ class TaskStore {
10555
10587
  this.migrateOnRead = options.migrateOnRead;
10556
10588
  }
10557
10589
  location() {
10558
- if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs9.existsSync(path8.join(this.root, ".git"))) {
10590
+ if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs10.existsSync(path9.join(this.root, ".git"))) {
10559
10591
  this.located = undefined;
10560
10592
  this.migrationChecked = false;
10561
10593
  }
@@ -10619,7 +10651,7 @@ class TaskStore {
10619
10651
  const tasksDir = this.paths().tasks;
10620
10652
  let names;
10621
10653
  try {
10622
- names = fs9.existsSync(tasksDir) ? fs9.readdirSync(tasksDir).filter((name) => validId(name)) : [];
10654
+ names = fs10.existsSync(tasksDir) ? fs10.readdirSync(tasksDir).filter((name) => validId(name)) : [];
10623
10655
  } catch (error) {
10624
10656
  return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
10625
10657
  path: tasksDir
@@ -10660,7 +10692,7 @@ class TaskStore {
10660
10692
  const file = this.paths().workspace;
10661
10693
  let bytes;
10662
10694
  try {
10663
- bytes = retryTransient(() => fs9.readFileSync(file, "utf8"));
10695
+ bytes = retryTransient(() => fs10.readFileSync(file, "utf8"));
10664
10696
  } catch (error) {
10665
10697
  if (error.code === "ENOENT")
10666
10698
  return success(null, null, null);
@@ -10711,7 +10743,7 @@ class TaskStore {
10711
10743
  const moved = this.bindLocked(again.data.id, key, input.now);
10712
10744
  return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
10713
10745
  }
10714
- const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path8.basename(this.root) || this.root;
10746
+ const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path9.basename(this.root) || this.root;
10715
10747
  const intent = {
10716
10748
  objective: input.objective?.trim() || `Work on ${label}`,
10717
10749
  scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
@@ -10771,14 +10803,14 @@ class TaskStore {
10771
10803
  const branch = key.ok ? key.data.branch : null;
10772
10804
  if (!branch || !location.ok)
10773
10805
  return success(null, null, null);
10774
- const common = path8.dirname(location.data.dir);
10775
- const heads = path8.join(common, "logs", "refs", "heads");
10776
- const headLog = location.data.git ? path8.join(location.data.git.gitDir, "logs", "HEAD") : "";
10806
+ const common = path9.dirname(location.data.dir);
10807
+ const heads = path9.join(common, "logs", "refs", "heads");
10808
+ const headLog = location.data.git ? path9.join(location.data.git.gitDir, "logs", "HEAD") : "";
10777
10809
  const signatures = [
10778
- path8.join(heads, branch),
10810
+ path9.join(heads, branch),
10779
10811
  headLog,
10780
10812
  heads,
10781
- path8.join(common, "packed-refs")
10813
+ path9.join(common, "packed-refs")
10782
10814
  ].map((file) => fileSignature(file) ?? "-");
10783
10815
  const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
10784
10816
  let resolved = origins.get(slot);
@@ -10790,7 +10822,7 @@ class TaskStore {
10790
10822
  aliases.push(rename.from);
10791
10823
  const start = branchStart(location.data, branch, aliases);
10792
10824
  const slash = start?.indexOf("/") ?? -1;
10793
- const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path8.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
10825
+ const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path9.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
10794
10826
  const name = start && (remote ? start.slice(slash + 1) : start);
10795
10827
  resolved = {
10796
10828
  signature: signatures.join("|"),
@@ -11028,13 +11060,13 @@ class TaskStore {
11028
11060
  if (!ready.ok)
11029
11061
  return ready;
11030
11062
  const dirs = this.paths();
11031
- const recovery = path8.join(this.root, ".workit", "recovery");
11032
- if (fs9.existsSync(recovery)) {
11063
+ const recovery = path9.join(this.root, ".workit", "recovery");
11064
+ if (fs10.existsSync(recovery)) {
11033
11065
  let files = 0;
11034
11066
  let bytes = 0;
11035
11067
  try {
11036
- for (const name of fs9.readdirSync(recovery)) {
11037
- const stat = fs9.lstatSync(path8.join(recovery, name));
11068
+ for (const name of fs10.readdirSync(recovery)) {
11069
+ const stat = fs10.lstatSync(path9.join(recovery, name));
11038
11070
  if (!stat.isFile())
11039
11071
  continue;
11040
11072
  files += 1;
@@ -11043,17 +11075,17 @@ class TaskStore {
11043
11075
  } catch {}
11044
11076
  const remove = options.pruneRecovery === true && !dryRun;
11045
11077
  if (remove)
11046
- fs9.rmSync(recovery, { recursive: true, force: true });
11078
+ fs10.rmSync(recovery, { recursive: true, force: true });
11047
11079
  report.legacyRecovery = { path: recovery, files, bytes, removed: remove };
11048
11080
  }
11049
- if (!fs9.existsSync(dirs.tasks))
11081
+ if (!fs10.existsSync(dirs.tasks))
11050
11082
  return success(null, null, report);
11051
- const names = fs9.readdirSync(dirs.tasks).filter((name) => validId(name));
11083
+ const names = fs10.readdirSync(dirs.tasks).filter((name) => validId(name));
11052
11084
  for (const name of names) {
11053
11085
  const file = this.eventsPath(name);
11054
11086
  let size = 0;
11055
11087
  try {
11056
- size = fs9.statSync(file).size;
11088
+ size = fs10.statSync(file).size;
11057
11089
  } catch {
11058
11090
  continue;
11059
11091
  }
@@ -11081,16 +11113,16 @@ class TaskStore {
11081
11113
  const references = new Set;
11082
11114
  for (const name of names)
11083
11115
  try {
11084
- for (const match of fs9.readFileSync(this.eventsPath(name), "utf8").matchAll(/"\$blob":"([0-9a-f]{64})"/g))
11116
+ for (const match of fs10.readFileSync(this.eventsPath(name), "utf8").matchAll(/"\$blob":"([0-9a-f]{64})"/g))
11085
11117
  references.add(match[1]);
11086
11118
  } catch {}
11087
- if (fs9.existsSync(dirs.blobs))
11088
- for (const name of fs9.readdirSync(dirs.blobs)) {
11119
+ if (fs10.existsSync(dirs.blobs))
11120
+ for (const name of fs10.readdirSync(dirs.blobs)) {
11089
11121
  const digest = name.replace(/\.json$/, "");
11090
- const file = path8.join(dirs.blobs, name);
11122
+ const file = path9.join(dirs.blobs, name);
11091
11123
  let stat;
11092
11124
  try {
11093
- stat = fs9.lstatSync(file);
11125
+ stat = fs10.lstatSync(file);
11094
11126
  } catch {
11095
11127
  continue;
11096
11128
  }
@@ -11101,11 +11133,11 @@ class TaskStore {
11101
11133
  report.blobs.removed += 1;
11102
11134
  report.blobs.removedBytes += stat.size;
11103
11135
  if (!dryRun)
11104
- fs9.rmSync(file, { force: true });
11136
+ fs10.rmSync(file, { force: true });
11105
11137
  }
11106
11138
  return success(null, null, null);
11107
11139
  };
11108
- const swept = dryRun ? sweep() : this.withLock(path8.join(dirs.dir, "gc.lock"), sweep);
11140
+ const swept = dryRun ? sweep() : this.withLock(path9.join(dirs.dir, "gc.lock"), sweep);
11109
11141
  if (!swept.ok)
11110
11142
  return swept;
11111
11143
  const temporaryDirs = [
@@ -11113,17 +11145,17 @@ class TaskStore {
11113
11145
  dirs.tasks,
11114
11146
  dirs.blobs,
11115
11147
  dirs.checkout,
11116
- ...names.map((name) => path8.join(dirs.tasks, name))
11148
+ ...names.map((name) => path9.join(dirs.tasks, name))
11117
11149
  ];
11118
11150
  for (const directory of temporaryDirs) {
11119
- if (!fs9.existsSync(directory))
11151
+ if (!fs10.existsSync(directory))
11120
11152
  continue;
11121
- for (const name of fs9.readdirSync(directory)) {
11153
+ for (const name of fs10.readdirSync(directory)) {
11122
11154
  if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
11123
11155
  continue;
11124
- const file = path8.join(directory, name);
11156
+ const file = path9.join(directory, name);
11125
11157
  try {
11126
- const stat = fs9.lstatSync(file);
11158
+ const stat = fs10.lstatSync(file);
11127
11159
  if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
11128
11160
  continue;
11129
11161
  } catch {
@@ -11131,37 +11163,37 @@ class TaskStore {
11131
11163
  }
11132
11164
  report.temporary.removed += 1;
11133
11165
  if (!dryRun)
11134
- fs9.rmSync(file, { force: true });
11166
+ fs10.rmSync(file, { force: true });
11135
11167
  }
11136
11168
  }
11137
11169
  return success(null, null, report);
11138
11170
  }
11139
11171
  paths() {
11140
11172
  const location = this.location();
11141
- const dir = location.ok ? location.data.dir : path8.join(this.root, ".workit");
11142
- const checkout = path8.join(dir, "checkouts", checkoutSlug(this.root));
11173
+ const dir = location.ok ? location.data.dir : path9.join(this.root, ".workit");
11174
+ const checkout = path9.join(dir, "checkouts", checkoutSlug(this.root));
11143
11175
  return {
11144
11176
  dir,
11145
11177
  shared: location.ok ? location.data.shared : false,
11146
- marker: path8.join(dir, "store.json"),
11178
+ marker: path9.join(dir, "store.json"),
11147
11179
  checkout,
11148
- workspace: path8.join(checkout, "workspace.json"),
11149
- checkoutLock: path8.join(checkout, "metadata.lock"),
11150
- tasks: path8.join(dir, "tasks"),
11151
- index: path8.join(dir, "task-index.json"),
11152
- blobs: path8.join(dir, "blobs", "candidates"),
11153
- legacy: path8.join(dir, "legacy", checkoutSlug(this.root), "v2"),
11154
- v2: path8.join(this.root, ".workit")
11180
+ workspace: path9.join(checkout, "workspace.json"),
11181
+ checkoutLock: path9.join(checkout, "metadata.lock"),
11182
+ tasks: path9.join(dir, "tasks"),
11183
+ index: path9.join(dir, "task-index.json"),
11184
+ blobs: path9.join(dir, "blobs", "candidates"),
11185
+ legacy: path9.join(dir, "legacy", checkoutSlug(this.root), "v2"),
11186
+ v2: path9.join(this.root, ".workit")
11155
11187
  };
11156
11188
  }
11157
11189
  eventsPath(taskId) {
11158
- return path8.join(this.paths().tasks, taskId, "events.jsonl");
11190
+ return path9.join(this.paths().tasks, taskId, "events.jsonl");
11159
11191
  }
11160
11192
  snapshotPath(taskId) {
11161
- return path8.join(this.paths().tasks, taskId, "snapshot.json");
11193
+ return path9.join(this.paths().tasks, taskId, "snapshot.json");
11162
11194
  }
11163
11195
  taskLockPath(taskId) {
11164
- return path8.join(this.paths().tasks, taskId, "lock");
11196
+ return path9.join(this.paths().tasks, taskId, "lock");
11165
11197
  }
11166
11198
  ready(write = false) {
11167
11199
  const location = this.location();
@@ -11171,7 +11203,7 @@ class TaskStore {
11171
11203
  return success(null, null, null);
11172
11204
  const dirs = this.paths();
11173
11205
  try {
11174
- const marker = JSON.parse(fs9.readFileSync(dirs.marker, "utf8"));
11206
+ const marker = JSON.parse(fs10.readFileSync(dirs.marker, "utf8"));
11175
11207
  if (typeof marker.version === "number" && marker.version > STORE_FORMAT)
11176
11208
  return failure2("recovery_required", `the workit store at ${dirs.dir} has format ${marker.version}; upgrade Workit to use it`, { path: dirs.marker });
11177
11209
  } catch {}
@@ -11200,21 +11232,21 @@ class TaskStore {
11200
11232
  return {
11201
11233
  read: (digest) => {
11202
11234
  try {
11203
- return JSON.parse(fs9.readFileSync(path8.join(dir, `${digest}.json`), "utf8"));
11235
+ return JSON.parse(fs10.readFileSync(path9.join(dir, `${digest}.json`), "utf8"));
11204
11236
  } catch (error) {
11205
11237
  throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
11206
11238
  }
11207
11239
  },
11208
11240
  write: (digest, value) => {
11209
- const file = path8.join(dir, `${digest}.json`);
11210
- if (fs9.existsSync(file)) {
11241
+ const file = path9.join(dir, `${digest}.json`);
11242
+ if (fs10.existsSync(file)) {
11211
11243
  const stamp = new Date;
11212
11244
  try {
11213
- fs9.utimesSync(file, stamp, stamp);
11245
+ fs10.utimesSync(file, stamp, stamp);
11214
11246
  return;
11215
11247
  } catch {}
11216
11248
  }
11217
- fs9.mkdirSync(dir, { recursive: true });
11249
+ fs10.mkdirSync(dir, { recursive: true });
11218
11250
  replaceFile(file, JSON.stringify(value), true);
11219
11251
  }
11220
11252
  };
@@ -11296,7 +11328,7 @@ class TaskStore {
11296
11328
  }
11297
11329
  readSnapshot(taskId) {
11298
11330
  try {
11299
- const value = JSON.parse(fs9.readFileSync(this.snapshotPath(taskId), "utf8"));
11331
+ const value = JSON.parse(fs10.readFileSync(this.snapshotPath(taskId), "utf8"));
11300
11332
  if (!isObject4(value) || value.v !== 1 || value.task !== taskId || typeof value.seq !== "number" || typeof value.lastId !== "string" || typeof value.lastStart !== "number" || typeof value.end !== "number" || !isObject4(value.record))
11301
11333
  return null;
11302
11334
  return value;
@@ -11398,8 +11430,8 @@ class TaskStore {
11398
11430
  }
11399
11431
  }
11400
11432
  openTask(record, type, key, legacy = null) {
11401
- const dir = path8.join(this.paths().tasks, record.id);
11402
- fs9.mkdirSync(dir, { recursive: true });
11433
+ const dir = path9.join(this.paths().tasks, record.id);
11434
+ fs10.mkdirSync(dir, { recursive: true });
11403
11435
  const blobs = this.blobStore();
11404
11436
  const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
11405
11437
  const event = this.event(record.id, 1, type, {
@@ -11451,11 +11483,11 @@ class TaskStore {
11451
11483
  } catch {}
11452
11484
  if (!latest || latest.events[0]?.id !== last.id)
11453
11485
  return failure2("busy", "the task log changed during compaction; retry", { taskId });
11454
- const appended = fs9.readFileSync(file).subarray(log.end, latest.end);
11486
+ const appended = fs10.readFileSync(file).subarray(log.end, latest.end);
11455
11487
  const bytes = Buffer.concat([Buffer.from(prefix, "utf8"), appended]);
11456
11488
  replaceFile(file, bytes.toString("utf8"), true);
11457
11489
  try {
11458
- fs9.rmSync(this.snapshotPath(taskId), { force: true });
11490
+ fs10.rmSync(this.snapshotPath(taskId), { force: true });
11459
11491
  } catch {}
11460
11492
  const reloaded = this.load(taskId);
11461
11493
  if (reloaded.ok && reloaded.data)
@@ -11468,7 +11500,7 @@ class TaskStore {
11468
11500
  }
11469
11501
  countEvents(file) {
11470
11502
  try {
11471
- const bytes = fs9.readFileSync(file);
11503
+ const bytes = fs10.readFileSync(file);
11472
11504
  let count = 0;
11473
11505
  for (let index = bytes.indexOf(10);index >= 0; index = bytes.indexOf(10, index + 1))
11474
11506
  count += 1;
@@ -11490,8 +11522,8 @@ class TaskStore {
11490
11522
  return bound.ok ? success(null, null, bound.data[0] ?? null) : bound;
11491
11523
  }
11492
11524
  withKeyLock(key, operation) {
11493
- const digest = createHash5("sha256").update(key).digest("hex").slice(0, 32);
11494
- return this.withLock(path8.join(this.paths().dir, "keys", `${digest}.lock`), operation);
11525
+ const digest = createHash6("sha256").update(key).digest("hex").slice(0, 32);
11526
+ return this.withLock(path9.join(this.paths().dir, "keys", `${digest}.lock`), operation);
11495
11527
  }
11496
11528
  bindLocked(taskId, key, timestamp) {
11497
11529
  const current = this.readWorkspace();
@@ -11632,7 +11664,7 @@ class TaskStore {
11632
11664
  writeWorkspace(record) {
11633
11665
  const file = this.paths().workspace;
11634
11666
  try {
11635
- fs9.mkdirSync(path8.dirname(file), { recursive: true });
11667
+ fs10.mkdirSync(path9.dirname(file), { recursive: true });
11636
11668
  replaceFile(file, `${canonicalJson(record)}
11637
11669
  `, true);
11638
11670
  return success(null, null, null);
@@ -11642,7 +11674,7 @@ class TaskStore {
11642
11674
  }
11643
11675
  readIndex() {
11644
11676
  try {
11645
- const value = JSON.parse(fs9.readFileSync(this.paths().index, "utf8"));
11677
+ const value = JSON.parse(fs10.readFileSync(this.paths().index, "utf8"));
11646
11678
  if (!isObject4(value) || value.version !== INDEX_VERSION || !isObject4(value.tasks))
11647
11679
  return {};
11648
11680
  const tasks = {};
@@ -11656,7 +11688,7 @@ class TaskStore {
11656
11688
  }
11657
11689
  writeIndex(entries) {
11658
11690
  try {
11659
- fs9.mkdirSync(this.paths().tasks, { recursive: true });
11691
+ fs10.mkdirSync(this.paths().tasks, { recursive: true });
11660
11692
  replaceFile(this.paths().index, JSON.stringify({
11661
11693
  version: INDEX_VERSION,
11662
11694
  tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
@@ -11710,23 +11742,23 @@ class TaskStore {
11710
11742
  return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
11711
11743
  }
11712
11744
  v2Dirs() {
11713
- return [...new Set([this.root, this.origin])].map((dir) => path8.join(dir, ".workit"));
11745
+ return [...new Set([this.root, this.origin])].map((dir) => path9.join(dir, ".workit"));
11714
11746
  }
11715
11747
  pendingMigrations() {
11716
11748
  return this.v2Dirs().filter((dir) => {
11717
- const raw = this.readV2Workspace(path8.join(dir, "workspace.json"));
11749
+ const raw = this.readV2Workspace(path9.join(dir, "workspace.json"));
11718
11750
  return raw !== null && raw !== TOMBSTONE;
11719
11751
  });
11720
11752
  }
11721
11753
  migrateV2(v2) {
11722
11754
  const dirs = this.paths();
11723
- const v2Workspace = path8.join(v2, "workspace.json");
11724
- const v2Tasks = path8.join(v2, "tasks");
11725
- const legacy = path8.join(dirs.dir, "legacy", checkoutSlug(path8.dirname(v2)), "v2");
11755
+ const v2Workspace = path9.join(v2, "workspace.json");
11756
+ const v2Tasks = path9.join(v2, "tasks");
11757
+ const legacy = path9.join(dirs.dir, "legacy", checkoutSlug(path9.dirname(v2)), "v2");
11726
11758
  const raw = this.readV2Workspace(v2Workspace);
11727
11759
  if (raw === null || raw === TOMBSTONE)
11728
11760
  return success(null, null, null);
11729
- return this.withLock(dirs.checkoutLock, () => this.withLock(path8.join(v2, "metadata.lock"), () => {
11761
+ return this.withLock(dirs.checkoutLock, () => this.withLock(path9.join(v2, "metadata.lock"), () => {
11730
11762
  const workspace = this.readV2Workspace(v2Workspace);
11731
11763
  if (workspace === null || workspace === TOMBSTONE)
11732
11764
  return success(null, null, null);
@@ -11735,7 +11767,7 @@ class TaskStore {
11735
11767
  return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
11736
11768
  let files = [];
11737
11769
  try {
11738
- files = fs9.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path8.join(v2Tasks, name)));
11770
+ files = fs10.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path9.join(v2Tasks, name)));
11739
11771
  } catch {}
11740
11772
  const current = this.readWorkspace();
11741
11773
  if (!current.ok)
@@ -11753,22 +11785,22 @@ class TaskStore {
11753
11785
  let skipped = 0;
11754
11786
  const done = new Map;
11755
11787
  try {
11756
- fs9.mkdirSync(path8.join(legacy, "tasks"), { recursive: true });
11788
+ fs10.mkdirSync(path9.join(legacy, "tasks"), { recursive: true });
11757
11789
  for (const name of ["workspace.json", "index.json"])
11758
- if (fs9.existsSync(path8.join(v2, name)))
11759
- fs9.copyFileSync(path8.join(v2, name), path8.join(legacy, name));
11790
+ if (fs10.existsSync(path9.join(v2, name)))
11791
+ fs10.copyFileSync(path9.join(v2, name), path9.join(legacy, name));
11760
11792
  for (const name of files) {
11761
11793
  const id = name.slice(0, -5);
11762
- const file = path8.join(v2Tasks, name);
11763
- const bytes = fs9.readFileSync(file, "utf8");
11794
+ const file = path9.join(v2Tasks, name);
11795
+ const bytes = fs10.readFileSync(file, "utf8");
11764
11796
  const digest = sha256(bytes);
11765
- fs9.writeFileSync(path8.join(legacy, "tasks", name), bytes);
11797
+ fs10.writeFileSync(path9.join(legacy, "tasks", name), bytes);
11766
11798
  const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
11767
11799
  if (!parsed.ok || parsed.data.id !== id)
11768
11800
  return failure2("recovery_required", `cannot migrate 2.x task ${id}: ${parsed.ok ? "file name and record ID differ" : parsed.error}; move ${file} aside to continue`, { path: file });
11769
11801
  const record = { ...parsed.data, workspaceId: target.id };
11770
- const origin = { path: path8.join(legacy, "tasks", name), digest };
11771
- if (!fs9.existsSync(this.eventsPath(id))) {
11802
+ const origin = { path: path9.join(legacy, "tasks", name), digest };
11803
+ if (!fs10.existsSync(this.eventsPath(id))) {
11772
11804
  this.openTask(record, "migrated.from_v2", null, origin);
11773
11805
  migrated += 1;
11774
11806
  } else {
@@ -11803,8 +11835,8 @@ class TaskStore {
11803
11835
  try {
11804
11836
  let complete = true;
11805
11837
  for (const [name, digest] of done) {
11806
- const file = path8.join(v2Tasks, name);
11807
- if (sha256(fs9.readFileSync(file, "utf8")) !== digest) {
11838
+ const file = path9.join(v2Tasks, name);
11839
+ if (sha256(fs10.readFileSync(file, "utf8")) !== digest) {
11808
11840
  complete = false;
11809
11841
  continue;
11810
11842
  }
@@ -11814,7 +11846,7 @@ class TaskStore {
11814
11846
  return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
11815
11847
  path: v2
11816
11848
  });
11817
- fs9.rmSync(path8.join(v2, "index.json"), { force: true });
11849
+ fs10.rmSync(path9.join(v2, "index.json"), { force: true });
11818
11850
  this.writeTombstone(v2, target.id);
11819
11851
  } catch (error) {
11820
11852
  return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
@@ -11834,45 +11866,45 @@ class TaskStore {
11834
11866
  }
11835
11867
  adoptLocalStore() {
11836
11868
  const dirs = this.paths();
11837
- const marker = path8.join(dirs.v2, "store.json");
11838
- if (!dirs.shared || !fs9.existsSync(marker))
11869
+ const marker = path9.join(dirs.v2, "store.json");
11870
+ if (!dirs.shared || !fs10.existsSync(marker))
11839
11871
  return success(null, null, null);
11840
11872
  return this.withLock(dirs.checkoutLock, () => {
11841
- if (!fs9.existsSync(marker))
11873
+ if (!fs10.existsSync(marker))
11842
11874
  return success(null, null, null);
11843
11875
  let tasks = 0;
11844
11876
  try {
11845
11877
  const merge = (from, to) => {
11846
- if (!fs9.existsSync(to)) {
11847
- fs9.mkdirSync(path8.dirname(to), { recursive: true });
11878
+ if (!fs10.existsSync(to)) {
11879
+ fs10.mkdirSync(path9.dirname(to), { recursive: true });
11848
11880
  try {
11849
- fs9.renameSync(from, to);
11881
+ fs10.renameSync(from, to);
11850
11882
  } catch {
11851
- fs9.cpSync(from, to, { recursive: true, errorOnExist: true, force: false });
11852
- fs9.rmSync(from, { recursive: true, force: true });
11883
+ fs10.cpSync(from, to, { recursive: true, errorOnExist: true, force: false });
11884
+ fs10.rmSync(from, { recursive: true, force: true });
11853
11885
  }
11854
11886
  return;
11855
11887
  }
11856
- if (fs9.lstatSync(from).isDirectory() && fs9.lstatSync(to).isDirectory())
11857
- for (const name of fs9.readdirSync(from))
11858
- merge(path8.join(from, name), path8.join(to, name));
11888
+ if (fs10.lstatSync(from).isDirectory() && fs10.lstatSync(to).isDirectory())
11889
+ for (const name of fs10.readdirSync(from))
11890
+ merge(path9.join(from, name), path9.join(to, name));
11859
11891
  };
11860
11892
  for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
11861
- const from = path8.join(dirs.v2, sub);
11862
- if (!fs9.existsSync(from))
11893
+ const from = path9.join(dirs.v2, sub);
11894
+ if (!fs10.existsSync(from))
11863
11895
  continue;
11864
11896
  if (sub === "tasks") {
11865
- for (const name of fs9.readdirSync(from))
11866
- if (validId(name) && !fs9.existsSync(path8.join(dirs.dir, sub, name)))
11897
+ for (const name of fs10.readdirSync(from))
11898
+ if (validId(name) && !fs10.existsSync(path9.join(dirs.dir, sub, name)))
11867
11899
  tasks += 1;
11868
11900
  }
11869
- merge(from, path8.join(dirs.dir, sub));
11901
+ merge(from, path9.join(dirs.dir, sub));
11870
11902
  }
11871
- const ledger = path8.join(dirs.v2, "ledger");
11872
- if (fs9.existsSync(ledger) && !fs9.existsSync(path8.join(dirs.dir, "ledger")))
11873
- merge(ledger, path8.join(dirs.dir, "ledger"));
11874
- fs9.rmSync(path8.join(dirs.v2, "task-index.json"), { force: true });
11875
- fs9.renameSync(marker, path8.join(dirs.v2, "store.moved.json"));
11903
+ const ledger = path9.join(dirs.v2, "ledger");
11904
+ if (fs10.existsSync(ledger) && !fs10.existsSync(path9.join(dirs.dir, "ledger")))
11905
+ merge(ledger, path9.join(dirs.dir, "ledger"));
11906
+ fs10.rmSync(path9.join(dirs.v2, "task-index.json"), { force: true });
11907
+ fs10.renameSync(marker, path9.join(dirs.v2, "store.moved.json"));
11876
11908
  } catch (error) {
11877
11909
  return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
11878
11910
  path: dirs.v2
@@ -11892,7 +11924,7 @@ class TaskStore {
11892
11924
  readV2Workspace(file) {
11893
11925
  let bytes;
11894
11926
  try {
11895
- bytes = fs9.readFileSync(file, "utf8");
11927
+ bytes = fs10.readFileSync(file, "utf8");
11896
11928
  } catch {
11897
11929
  return null;
11898
11930
  }
@@ -11910,7 +11942,7 @@ class TaskStore {
11910
11942
  schemaVersion: SCHEMA_VERSION,
11911
11943
  id: workspaceId ?? newId(),
11912
11944
  revision: newRevision(),
11913
- root: path8.dirname(v2),
11945
+ root: path9.dirname(v2),
11914
11946
  runtime: { createdWith: version, updatedWith: version },
11915
11947
  writer: null,
11916
11948
  store: {
@@ -11921,12 +11953,12 @@ class TaskStore {
11921
11953
  },
11922
11954
  critical: ["store"]
11923
11955
  };
11924
- fs9.mkdirSync(v2, { recursive: true });
11925
- const ignore = path8.join(v2, ".gitignore");
11926
- if (!fs9.existsSync(ignore))
11927
- fs9.writeFileSync(ignore, `*
11956
+ fs10.mkdirSync(v2, { recursive: true });
11957
+ const ignore = path9.join(v2, ".gitignore");
11958
+ if (!fs10.existsSync(ignore))
11959
+ fs10.writeFileSync(ignore, `*
11928
11960
  `);
11929
- replaceFile(path8.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
11961
+ replaceFile(path9.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
11930
11962
  `, true);
11931
11963
  }
11932
11964
  stubFor(taskId) {
@@ -11942,7 +11974,7 @@ class TaskStore {
11942
11974
  }
11943
11975
  isStub(file) {
11944
11976
  try {
11945
- const value = JSON.parse(fs9.readFileSync(file, "utf8"));
11977
+ const value = JSON.parse(fs10.readFileSync(file, "utf8"));
11946
11978
  return isObject4(value) && isObject4(value.store) && value.store.format === "workit-store";
11947
11979
  } catch {
11948
11980
  return false;
@@ -11950,32 +11982,32 @@ class TaskStore {
11950
11982
  }
11951
11983
  writeTombstoneIfMissing() {
11952
11984
  try {
11953
- const v2 = path8.join(this.root, ".workit");
11954
- if (fs9.existsSync(path8.join(v2, "workspace.json")))
11985
+ const v2 = path9.join(this.root, ".workit");
11986
+ if (fs10.existsSync(path9.join(v2, "workspace.json")))
11955
11987
  return;
11956
11988
  let id = null;
11957
11989
  try {
11958
- id = JSON.parse(fs9.readFileSync(this.paths().workspace, "utf8")).id ?? null;
11990
+ id = JSON.parse(fs10.readFileSync(this.paths().workspace, "utf8")).id ?? null;
11959
11991
  } catch {}
11960
11992
  this.writeTombstone(v2, id);
11961
11993
  } catch {}
11962
11994
  }
11963
11995
  initializeStorage() {
11964
11996
  const dirs = this.paths();
11965
- retryTransient(() => fs9.mkdirSync(dirs.checkout, { recursive: true }));
11966
- retryTransient(() => fs9.mkdirSync(dirs.tasks, { recursive: true }));
11967
- if (!fs9.existsSync(dirs.marker))
11997
+ retryTransient(() => fs10.mkdirSync(dirs.checkout, { recursive: true }));
11998
+ retryTransient(() => fs10.mkdirSync(dirs.tasks, { recursive: true }));
11999
+ if (!fs10.existsSync(dirs.marker))
11968
12000
  replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
11969
12001
  `, false);
11970
12002
  if (!dirs.shared) {
11971
- const ignore = path8.join(dirs.dir, ".gitignore");
12003
+ const ignore = path9.join(dirs.dir, ".gitignore");
11972
12004
  let current = null;
11973
12005
  try {
11974
- current = fs9.readFileSync(ignore, "utf8");
12006
+ current = fs10.readFileSync(ignore, "utf8");
11975
12007
  } catch {}
11976
12008
  if (current !== `*
11977
12009
  `)
11978
- retryTransient(() => fs9.writeFileSync(ignore, `*
12010
+ retryTransient(() => fs10.writeFileSync(ignore, `*
11979
12011
  `));
11980
12012
  }
11981
12013
  this.writeTombstoneIfMissing();
@@ -11991,7 +12023,7 @@ class TaskStore {
11991
12023
  shouldReclaim: ({ payload, nowMs }) => {
11992
12024
  let ageMs = null;
11993
12025
  try {
11994
- ageMs = nowMs - fs9.lstatSync(lockPath).mtimeMs;
12026
+ ageMs = nowMs - fs10.lstatSync(lockPath).mtimeMs;
11995
12027
  } catch {}
11996
12028
  return classifyLockOwner(payload, ageMs, localLockHost(), TASK_STORE_LOCK).state === "stale";
11997
12029
  },
@@ -12028,7 +12060,7 @@ class TaskStore {
12028
12060
  withLock(lockPath, operation) {
12029
12061
  try {
12030
12062
  this.initializeStorage();
12031
- fs9.mkdirSync(path8.dirname(lockPath), { recursive: true });
12063
+ fs10.mkdirSync(path9.dirname(lockPath), { recursive: true });
12032
12064
  } catch (error) {
12033
12065
  return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
12034
12066
  path: this.paths().dir
@@ -12068,18 +12100,18 @@ class TaskStore {
12068
12100
  lockDirWritable(lockPath) {
12069
12101
  const probe = `${lockPath}.${process.pid}.${randomUUID3()}.probe`;
12070
12102
  try {
12071
- fs9.closeSync(fs9.openSync(probe, "wx", 384));
12103
+ fs10.closeSync(fs10.openSync(probe, "wx", 384));
12072
12104
  } catch {
12073
12105
  return false;
12074
12106
  }
12075
12107
  try {
12076
- fs9.rmSync(probe, { force: true });
12108
+ fs10.rmSync(probe, { force: true });
12077
12109
  } catch {}
12078
12110
  return true;
12079
12111
  }
12080
12112
  lockHolderPresent(lockPath) {
12081
12113
  try {
12082
- fs9.lstatSync(lockPath);
12114
+ fs10.lstatSync(lockPath);
12083
12115
  return true;
12084
12116
  } catch (error) {
12085
12117
  return error?.code !== "ENOENT";
@@ -12091,12 +12123,12 @@ class TaskStore {
12091
12123
  if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
12092
12124
  return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
12093
12125
  path: lockPath,
12094
- guidance: `Check permissions and the read-only attribute on ${path8.dirname(lockPath)}.`
12126
+ guidance: `Check permissions and the read-only attribute on ${path9.dirname(lockPath)}.`
12095
12127
  });
12096
12128
  if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
12097
12129
  let lock = null;
12098
12130
  try {
12099
- lock = parseMetadataLockOrNull(fs9.readFileSync(lockPath, "utf8"));
12131
+ lock = parseMetadataLockOrNull(fs10.readFileSync(lockPath, "utf8"));
12100
12132
  } catch {}
12101
12133
  return failure2("busy", `workit lock is held by another Workit call${lock ? ` (pid ${lock.pid} on ${lock.host})` : ""}; retry shortly`, {
12102
12134
  outcome: "not_started",
@@ -12111,17 +12143,17 @@ class TaskStore {
12111
12143
 
12112
12144
  // packages/workit-core/src/core/policy/derive.ts
12113
12145
  import { existsSync as existsSync4 } from "node:fs";
12114
- import path13 from "node:path";
12146
+ import path14 from "node:path";
12115
12147
 
12116
12148
  // packages/workit-core/src/ledger.ts
12117
12149
  import { spawnSync as spawnSync9 } from "node:child_process";
12118
- import { createHash as createHash8, randomBytes as randomBytes2 } from "node:crypto";
12119
- import fs11 from "node:fs";
12120
- import path12 from "node:path";
12150
+ import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
12151
+ import fs12 from "node:fs";
12152
+ import path13 from "node:path";
12121
12153
 
12122
12154
  // packages/workit-core/src/core/vcs-config.ts
12123
- import fs10 from "node:fs";
12124
- import path11 from "node:path";
12155
+ import fs11 from "node:fs";
12156
+ import path12 from "node:path";
12125
12157
  import { spawnSync as spawnSync8 } from "node:child_process";
12126
12158
 
12127
12159
  // packages/workit-core/src/core/config.ts
@@ -12135,7 +12167,7 @@ import {
12135
12167
  writeFileSync as writeFileSync2
12136
12168
  } from "node:fs";
12137
12169
  import os2 from "node:os";
12138
- import path9 from "node:path";
12170
+ import path10 from "node:path";
12139
12171
 
12140
12172
  // packages/workit-core/src/core/boundary.ts
12141
12173
  var EVENT = {
@@ -12179,7 +12211,7 @@ var mergePreset = (preset, input = {}, current = {
12179
12211
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
12180
12212
  };
12181
12213
  };
12182
- var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path9.join(process.env.XDG_CONFIG_HOME || path9.join(os2.homedir(), ".config"), "workit");
12214
+ var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workit");
12183
12215
  var migratedDir = null;
12184
12216
  var migrationFailed = false;
12185
12217
  var ensureConfigDir = (dir = resolveConfigDir()) => {
@@ -12189,7 +12221,7 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
12189
12221
  migratedDir = dir;
12190
12222
  return dir;
12191
12223
  }
12192
- const legacy = path9.join(process.env.XDG_CONFIG_HOME || path9.join(os2.homedir(), ".config"), "workflow-toolkit");
12224
+ const legacy = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workflow-toolkit");
12193
12225
  if (!existsSync3(legacy)) {
12194
12226
  migratedDir = dir;
12195
12227
  return dir;
@@ -12202,8 +12234,8 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
12202
12234
  mkdirSync2(dir, { recursive: true });
12203
12235
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
12204
12236
  for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
12205
- const src = path9.join(legacy, entry.name);
12206
- const dest = path9.join(dir, entry.name);
12237
+ const src = path10.join(legacy, entry.name);
12238
+ const dest = path10.join(dir, entry.name);
12207
12239
  if (existsSync3(dest))
12208
12240
  continue;
12209
12241
  try {
@@ -12275,7 +12307,7 @@ var parseConfigResult = (raw, file) => {
12275
12307
  return { status: "valid", path: file, config };
12276
12308
  };
12277
12309
  var readConfigTyped = (dir) => {
12278
- const file = path9.join(dir ?? configDir(), "config.json");
12310
+ const file = path10.join(dir ?? configDir(), "config.json");
12279
12311
  return parseConfigResult(readSafe(file), file);
12280
12312
  };
12281
12313
  var readConfig = () => {
@@ -12284,7 +12316,7 @@ var readConfig = () => {
12284
12316
  throw new Error(result.error);
12285
12317
  return result.config ?? DEFAULTS;
12286
12318
  };
12287
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path9.join(configDir(), "vcs.json");
12319
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
12288
12320
  var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
12289
12321
  var resolveBranchPolicy = (config, workspace, repoPreset) => {
12290
12322
  const wp = workspace?.branchPolicy ?? {};
@@ -12326,9 +12358,9 @@ var resolveCommitPolicy = (config, workspace) => {
12326
12358
 
12327
12359
  // packages/workit-core/src/core/workspaces.ts
12328
12360
  import { spawnSync as spawnSync6 } from "node:child_process";
12329
- import { createHash as createHash6 } from "node:crypto";
12361
+ import { createHash as createHash7 } from "node:crypto";
12330
12362
  import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
12331
- import path10 from "node:path";
12363
+ import path11 from "node:path";
12332
12364
 
12333
12365
  // packages/workit-core/src/core/branch-policy.ts
12334
12366
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -12345,6 +12377,7 @@ var branchNames = (workspaceRoot) => {
12345
12377
  return new Set((r.stdout ?? "").split(`
12346
12378
  `).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
12347
12379
  };
12380
+ var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
12348
12381
  var detectBranchPolicy = (workspaceRoot) => {
12349
12382
  const names = branchNames(workspaceRoot);
12350
12383
  const develop = names.has("develop");
@@ -12453,7 +12486,7 @@ var releaseTrackSchema = object({
12453
12486
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
12454
12487
  }
12455
12488
  }
12456
- if (track.versionSource?.kind === "package-json" && (path10.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
12489
+ if (track.versionSource?.kind === "package-json" && (path11.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
12457
12490
  context.addIssue({
12458
12491
  code: "custom",
12459
12492
  path: ["versionSource", "path"],
@@ -12513,7 +12546,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
12513
12546
  names.add(workspace.name);
12514
12547
  }
12515
12548
  });
12516
- var workspacesRevision = (contents) => createHash6("sha256").update(contents ?? "<missing>").digest("hex");
12549
+ var workspacesRevision = (contents) => createHash7("sha256").update(contents ?? "<missing>").digest("hex");
12517
12550
  var validateWorkspacesDocument = (parsed, file) => {
12518
12551
  if (!isConfigObject(parsed)) {
12519
12552
  return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
@@ -12551,7 +12584,7 @@ var validateWorkspacesDocument = (parsed, file) => {
12551
12584
  };
12552
12585
  };
12553
12586
  var readWorkspacesResult = (dir = configDir()) => {
12554
- const file = path10.join(dir, "workspaces.json");
12587
+ const file = path11.join(dir, "workspaces.json");
12555
12588
  let raw;
12556
12589
  try {
12557
12590
  raw = readFileSync3(file, "utf8");
@@ -12662,9 +12695,9 @@ var mainCheckoutOf = (cwd) => {
12662
12695
  let main = null;
12663
12696
  const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
12664
12697
  const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
12665
- if (gitDir && common && path10.resolve(gitDir) !== path10.resolve(common)) {
12698
+ if (gitDir && common && path11.resolve(gitDir) !== path11.resolve(common)) {
12666
12699
  const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
12667
- main = listed?.trim() || (path10.basename(common) === ".git" ? path10.dirname(common) : common);
12700
+ main = listed?.trim() || (path11.basename(common) === ".git" ? path11.dirname(common) : common);
12668
12701
  }
12669
12702
  mainCheckouts.set(cwd, main);
12670
12703
  return main;
@@ -12755,7 +12788,7 @@ var runtimeWorkspaceIndexSchema = object({
12755
12788
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
12756
12789
  }).passthrough();
12757
12790
  var matchRuntimeWorkspace = (cwd) => {
12758
- const file = path10.join(configDir(), "workspaces.json");
12791
+ const file = path11.join(configDir(), "workspaces.json");
12759
12792
  let raw;
12760
12793
  try {
12761
12794
  raw = readFileSync3(file, "utf8");
@@ -12830,7 +12863,7 @@ function workspaceDefaultTarget(cwd, globalDefault) {
12830
12863
  }
12831
12864
 
12832
12865
  // packages/workit-core/src/core/release-tracks.ts
12833
- import { createHash as createHash7 } from "node:crypto";
12866
+ import { createHash as createHash8 } from "node:crypto";
12834
12867
  import { spawnSync as spawnSync7 } from "node:child_process";
12835
12868
  class ReleaseTrackError extends Error {
12836
12869
  }
@@ -13213,7 +13246,7 @@ function resolveTrackFor(cwd, options) {
13213
13246
  ]) ?? "",
13214
13247
  git3(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
13215
13248
  ] : [];
13216
- key = createHash7("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
13249
+ key = createHash8("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
13217
13250
  const hit = memo2.get(key);
13218
13251
  if (hit)
13219
13252
  return hit;
@@ -13228,7 +13261,7 @@ function resolveTrackFor(cwd, options) {
13228
13261
  }
13229
13262
 
13230
13263
  // packages/workit-core/src/core/vcs-config.ts
13231
- var workspacesPath = () => path11.join(configDir(), "workspaces.json");
13264
+ var workspacesPath = () => path12.join(configDir(), "workspaces.json");
13232
13265
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
13233
13266
  var remoteProvider = (cwd) => {
13234
13267
  const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
@@ -13245,7 +13278,7 @@ var readVcsConfig = () => {
13245
13278
  const file = vcsConfigPath();
13246
13279
  let raw;
13247
13280
  try {
13248
- raw = fs10.readFileSync(file, "utf8");
13281
+ raw = fs11.readFileSync(file, "utf8");
13249
13282
  } catch {
13250
13283
  return { status: "missing", path: file, config: {} };
13251
13284
  }
@@ -13330,7 +13363,7 @@ function vcsConfig(mode, cwd, options = {}) {
13330
13363
  const prov = cfg[provider] ?? {};
13331
13364
  const out = {
13332
13365
  ok: true,
13333
- configPath: path11.resolve(cfgPath),
13366
+ configPath: path12.resolve(cfgPath),
13334
13367
  provider,
13335
13368
  defaultTargetBranch: workspaceDefault,
13336
13369
  pr: cfg.pr ?? {},
@@ -13456,7 +13489,7 @@ function diffHash(cwd, base, head) {
13456
13489
  ], GIT_TIMEOUTS.worktree);
13457
13490
  if (!diff.ok || !diff.stdout)
13458
13491
  return null;
13459
- const hash = createHash8("sha256");
13492
+ const hash = createHash9("sha256");
13460
13493
  for (const line of diff.stdout.split(`
13461
13494
  `)) {
13462
13495
  if (line.startsWith("index "))
@@ -13468,14 +13501,14 @@ function diffHash(cwd, base, head) {
13468
13501
  return `sha256:${hash.digest("hex")}`;
13469
13502
  }
13470
13503
  function storeRoot(cwd) {
13471
- const location = resolveStore(path12.resolve(cwd));
13504
+ const location = resolveStore(path13.resolve(cwd));
13472
13505
  if (location instanceof Error)
13473
13506
  return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
13474
13507
  return { ok: true, value: { root: location.dir, shared: location.shared } };
13475
13508
  }
13476
13509
  function ledgerPath(cwd) {
13477
13510
  const root = storeRoot(cwd);
13478
- return root.ok ? { ok: true, value: path12.join(root.value.root, "ledger", "ledger.jsonl") } : root;
13511
+ return root.ok ? { ok: true, value: path13.join(root.value.root, "ledger", "ledger.jsonl") } : root;
13479
13512
  }
13480
13513
  var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
13481
13514
  var NETWORK_FS_TYPES = new Set([
@@ -13495,7 +13528,7 @@ var onNetworkFs = (dir) => {
13495
13528
  if (process.platform !== "linux")
13496
13529
  return false;
13497
13530
  try {
13498
- return isNetworkFsType(fs11.statfsSync(dir).type);
13531
+ return isNetworkFsType(fs12.statfsSync(dir).type);
13499
13532
  } catch {
13500
13533
  return false;
13501
13534
  }
@@ -13505,15 +13538,15 @@ var LOCK_STALE_MS = 30000;
13505
13538
  var LOCK_OWNER = "owner";
13506
13539
  var lockToken = (lock) => {
13507
13540
  try {
13508
- return fs11.readFileSync(path12.join(lock, LOCK_OWNER), "utf8");
13541
+ return fs12.readFileSync(path13.join(lock, LOCK_OWNER), "utf8");
13509
13542
  } catch {
13510
13543
  return null;
13511
13544
  }
13512
13545
  };
13513
13546
  var lockAgeMs = (lock) => {
13514
- for (const target of [path12.join(lock, LOCK_OWNER), lock]) {
13547
+ for (const target of [path13.join(lock, LOCK_OWNER), lock]) {
13515
13548
  try {
13516
- return Date.now() - fs11.statSync(target).mtimeMs;
13549
+ return Date.now() - fs12.statSync(target).mtimeMs;
13517
13550
  } catch {}
13518
13551
  }
13519
13552
  return null;
@@ -13521,14 +13554,14 @@ var lockAgeMs = (lock) => {
13521
13554
  var ledgerLock = {
13522
13555
  acquire(lock) {
13523
13556
  try {
13524
- fs11.mkdirSync(lock);
13557
+ fs12.mkdirSync(lock);
13525
13558
  } catch (error) {
13526
13559
  if (error.code === "EEXIST")
13527
13560
  return null;
13528
13561
  throw error;
13529
13562
  }
13530
13563
  const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
13531
- fs11.writeFileSync(path12.join(lock, LOCK_OWNER), token);
13564
+ fs12.writeFileSync(path13.join(lock, LOCK_OWNER), token);
13532
13565
  return token;
13533
13566
  },
13534
13567
  release(lock, token) {
@@ -13536,35 +13569,35 @@ var ledgerLock = {
13536
13569
  return false;
13537
13570
  const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
13538
13571
  try {
13539
- fs11.renameSync(lock, tomb);
13572
+ fs12.renameSync(lock, tomb);
13540
13573
  } catch {
13541
13574
  return false;
13542
13575
  }
13543
13576
  if (lockToken(tomb) !== token) {
13544
13577
  try {
13545
- fs11.renameSync(tomb, lock);
13578
+ fs12.renameSync(tomb, lock);
13546
13579
  } catch {}
13547
13580
  return false;
13548
13581
  }
13549
- fs11.rmSync(tomb, { recursive: true, force: true });
13582
+ fs12.rmSync(tomb, { recursive: true, force: true });
13550
13583
  return true;
13551
13584
  },
13552
13585
  token: lockToken,
13553
13586
  reap(lock, observed) {
13554
13587
  const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
13555
13588
  try {
13556
- fs11.renameSync(lock, tomb);
13589
+ fs12.renameSync(lock, tomb);
13557
13590
  } catch {
13558
13591
  return false;
13559
13592
  }
13560
13593
  if (lockToken(tomb) === observed) {
13561
- fs11.rmSync(tomb, { recursive: true, force: true });
13594
+ fs12.rmSync(tomb, { recursive: true, force: true });
13562
13595
  return true;
13563
13596
  }
13564
13597
  try {
13565
- fs11.renameSync(tomb, lock);
13598
+ fs12.renameSync(tomb, lock);
13566
13599
  } catch {
13567
- fs11.rmSync(tomb, { recursive: true, force: true });
13600
+ fs12.rmSync(tomb, { recursive: true, force: true });
13568
13601
  }
13569
13602
  return false;
13570
13603
  }
@@ -13591,14 +13624,14 @@ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
13591
13624
  ledgerLock.release(lock, token);
13592
13625
  }
13593
13626
  }
13594
- var sha2562 = (text) => createHash8("sha256").update(text).digest("hex");
13627
+ var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
13595
13628
  var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
13596
13629
  ${body}`);
13597
13630
  var TAIL_BYTES = 64 * 1024;
13598
13631
  var lastLine = (fd, size) => {
13599
13632
  for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
13600
13633
  const buffer = Buffer.alloc(span);
13601
- fs11.readSync(fd, buffer, 0, span, size - span);
13634
+ fs12.readSync(fd, buffer, 0, span, size - span);
13602
13635
  const end = buffer.lastIndexOf(10);
13603
13636
  if (end < 0) {
13604
13637
  if (span === size)
@@ -13629,13 +13662,13 @@ var lastLine = (fd, size) => {
13629
13662
  };
13630
13663
  function appendLine(file, build, options) {
13631
13664
  const write = () => {
13632
- const fd = fs11.openSync(file, "a+");
13665
+ const fd = fs12.openSync(file, "a+");
13633
13666
  try {
13634
- const size = fs11.fstatSync(fd).size;
13667
+ const size = fs12.fstatSync(fd).size;
13635
13668
  let fence = "";
13636
13669
  if (size > 0) {
13637
13670
  const last = Buffer.alloc(1);
13638
- fs11.readSync(fd, last, 0, 1, size - 1);
13671
+ fs12.readSync(fd, last, 0, 1, size - 1);
13639
13672
  if (last[0] !== 10)
13640
13673
  fence = `
13641
13674
  `;
@@ -13644,17 +13677,17 @@ function appendLine(file, build, options) {
13644
13677
  if (!line.ok)
13645
13678
  return line;
13646
13679
  const bytes = Buffer.from(fence + line.value);
13647
- const written = fs11.writeSync(fd, bytes);
13680
+ const written = fs12.writeSync(fd, bytes);
13648
13681
  if (written !== bytes.length)
13649
13682
  return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
13650
13683
  return { ok: true, value: undefined };
13651
13684
  } finally {
13652
- fs11.closeSync(fd);
13685
+ fs12.closeSync(fd);
13653
13686
  }
13654
13687
  };
13655
13688
  try {
13656
- fs11.mkdirSync(path12.dirname(file), { recursive: true });
13657
- if (!options.forceLock && !onNetworkFs(path12.dirname(file)))
13689
+ fs12.mkdirSync(path13.dirname(file), { recursive: true });
13690
+ if (!options.forceLock && !onNetworkFs(path13.dirname(file)))
13658
13691
  return write();
13659
13692
  const locked = withLedgerLock(file, write);
13660
13693
  return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
@@ -13726,7 +13759,7 @@ function readLedger(cwd, options = {}) {
13726
13759
  return file;
13727
13760
  let text = "";
13728
13761
  try {
13729
- text = fs11.readFileSync(file.value, "utf8");
13762
+ text = fs12.readFileSync(file.value, "utf8");
13730
13763
  } catch (error) {
13731
13764
  if (error.code === "ENOENT")
13732
13765
  return {
@@ -13920,11 +13953,20 @@ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, s
13920
13953
  sessions.add(session);
13921
13954
  return sessions;
13922
13955
  }
13956
+ var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
13957
+ var authoredBy = (authors, session) => {
13958
+ if (authors.has(session))
13959
+ return true;
13960
+ for (const author of authors)
13961
+ if (derivesFrom(author, session) || derivesFrom(session, author))
13962
+ return true;
13963
+ return false;
13964
+ };
13923
13965
  var independenceReasons = (row, authors) => {
13924
13966
  const reasons = [];
13925
13967
  if (!row.actor.session)
13926
13968
  reasons.push("no_session");
13927
- else if (authors.has(row.actor.session))
13969
+ else if (authoredBy(authors, row.actor.session))
13928
13970
  reasons.push("author_session");
13929
13971
  if (row.self === true && !reasons.includes("no_session"))
13930
13972
  reasons.push("self");
@@ -14208,7 +14250,7 @@ var productDecision = (task, ledger) => {
14208
14250
  };
14209
14251
  };
14210
14252
  var planRecorded = (task, root) => {
14211
- const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path13.resolve(root, ref.path)));
14253
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path14.resolve(root, ref.path)));
14212
14254
  if (cited)
14213
14255
  return {
14214
14256
  met: true,
@@ -14267,15 +14309,15 @@ function writeBlockers(task, root) {
14267
14309
  }
14268
14310
 
14269
14311
  // packages/workit-core/src/core/task-evaluation.ts
14270
- var digestBytes = (value) => createHash9("sha256").update(value).digest("hex");
14271
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path14.sep}`);
14272
- var relative = (root, candidate) => path14.relative(root, candidate).split(path14.sep).join("/") || ".";
14312
+ var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
14313
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path15.sep}`);
14314
+ var relative = (root, candidate) => path15.relative(root, candidate).split(path15.sep).join("/") || ".";
14273
14315
  var canonicalPath = (value) => {
14274
14316
  if (typeof value !== "string" || !value)
14275
14317
  return null;
14276
14318
  if (value.includes("\\") || value.split("/").includes(".."))
14277
14319
  return null;
14278
- const normalized = path14.posix.normalize(value);
14320
+ const normalized = path15.posix.normalize(value);
14279
14321
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
14280
14322
  return null;
14281
14323
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -14323,21 +14365,21 @@ var gitPaths = (root) => {
14323
14365
  git: !/not a git repository/i.test(stderr)
14324
14366
  };
14325
14367
  }
14326
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path14.sep).join("/")) : [];
14368
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")) : [];
14327
14369
  const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
14328
14370
  if (stagedDeleted.status !== 0) {
14329
14371
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
14330
14372
  if (!/not a git repository/i.test(stderr))
14331
14373
  return { paths, uncertain: true, git: true };
14332
14374
  } else if (stagedDeleted.stdout) {
14333
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path14.sep).join("/")));
14375
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")));
14334
14376
  }
14335
14377
  return { paths: [...new Set(paths)], uncertain: false, git: true };
14336
14378
  };
14337
14379
  var walk = (root, directory, output) => {
14338
14380
  let names;
14339
14381
  try {
14340
- names = fs12.readdirSync(directory);
14382
+ names = fs13.readdirSync(directory);
14341
14383
  } catch {
14342
14384
  return true;
14343
14385
  }
@@ -14345,10 +14387,10 @@ var walk = (root, directory, output) => {
14345
14387
  for (const name of names) {
14346
14388
  if (name === ".git" || name === ".workit")
14347
14389
  continue;
14348
- const target = path14.join(directory, name);
14390
+ const target = path15.join(directory, name);
14349
14391
  let stat;
14350
14392
  try {
14351
- stat = fs12.lstatSync(target);
14393
+ stat = fs13.lstatSync(target);
14352
14394
  } catch {
14353
14395
  uncertain = true;
14354
14396
  continue;
@@ -14363,14 +14405,14 @@ var walk = (root, directory, output) => {
14363
14405
  var scopeRoots = (root, scope) => {
14364
14406
  const roots = [];
14365
14407
  for (const item of scope.paths.length ? scope.paths : ["."]) {
14366
- const target = path14.resolve(root, item);
14408
+ const target = path15.resolve(root, item);
14367
14409
  if (!inside(root, target))
14368
14410
  return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
14369
14411
  let ancestor = target;
14370
- while (!fs12.existsSync(ancestor) && ancestor !== root)
14371
- ancestor = path14.dirname(ancestor);
14412
+ while (!fs13.existsSync(ancestor) && ancestor !== root)
14413
+ ancestor = path15.dirname(ancestor);
14372
14414
  try {
14373
- if (!inside(root, fs12.realpathSync(ancestor)))
14415
+ if (!inside(root, fs13.realpathSync(ancestor)))
14374
14416
  return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
14375
14417
  } catch {
14376
14418
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -14382,7 +14424,7 @@ var scopeRoots = (root, scope) => {
14382
14424
  function captureCandidate(root, scope, environment = []) {
14383
14425
  let checkout;
14384
14426
  try {
14385
- checkout = fs12.realpathSync(root);
14427
+ checkout = fs13.realpathSync(root);
14386
14428
  } catch {
14387
14429
  return failure2("invalid_input", "candidate root does not exist");
14388
14430
  }
@@ -14400,11 +14442,11 @@ function captureCandidate(root, scope, environment = []) {
14400
14442
  if (item === ".git" || item === ".workit")
14401
14443
  continue;
14402
14444
  names.add(item);
14403
- if (!fs12.existsSync(target))
14445
+ if (!fs13.existsSync(target))
14404
14446
  continue;
14405
14447
  let stat;
14406
14448
  try {
14407
- stat = fs12.lstatSync(target);
14449
+ stat = fs13.lstatSync(target);
14408
14450
  } catch {
14409
14451
  uncertain = true;
14410
14452
  continue;
@@ -14420,17 +14462,17 @@ function captureCandidate(root, scope, environment = []) {
14420
14462
  for (const item of [...names].toSorted()) {
14421
14463
  if (!scopeMatches(item, normalizedScope))
14422
14464
  continue;
14423
- const target = path14.join(checkout, item);
14465
+ const target = path15.join(checkout, item);
14424
14466
  try {
14425
- const stat = fs12.lstatSync(target);
14467
+ const stat = fs13.lstatSync(target);
14426
14468
  if (stat.isSymbolicLink()) {
14427
- const link = fs12.readlinkSync(target);
14469
+ const link = fs13.readlinkSync(target);
14428
14470
  files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
14429
14471
  } else if (stat.isFile()) {
14430
14472
  files.push({
14431
14473
  path: item,
14432
14474
  kind: "file",
14433
- digest: digestBytes(fs12.readFileSync(target)),
14475
+ digest: digestBytes(fs13.readFileSync(target)),
14434
14476
  executable: (stat.mode & 73) !== 0
14435
14477
  });
14436
14478
  } else if (!stat.isDirectory()) {
@@ -14595,7 +14637,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
14595
14637
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
14596
14638
  let root;
14597
14639
  try {
14598
- root = fs12.realpathSync(checkoutRoot);
14640
+ root = fs13.realpathSync(checkoutRoot);
14599
14641
  } catch {
14600
14642
  return failure2("permission_denied", "checkout root is unavailable");
14601
14643
  }
@@ -14604,23 +14646,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
14604
14646
  continue;
14605
14647
  if (!reference.digest)
14606
14648
  return failure2("invalid_input", "document references require a byte digest");
14607
- const target = path14.resolve(root, reference.path);
14608
- if (target !== root && !target.startsWith(`${root}${path14.sep}`))
14649
+ const target = path15.resolve(root, reference.path);
14650
+ if (target !== root && !target.startsWith(`${root}${path15.sep}`))
14609
14651
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
14610
14652
  fields: [
14611
14653
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
14612
14654
  ]
14613
14655
  });
14614
14656
  try {
14615
- const below = path14.relative(root, target);
14657
+ const below = path15.relative(root, target);
14616
14658
  let current = root;
14617
- for (const segment of below.split(path14.sep)) {
14659
+ for (const segment of below.split(path15.sep)) {
14618
14660
  if (!segment)
14619
14661
  continue;
14620
- current = path14.join(current, segment);
14621
- const stat = fs12.lstatSync(current);
14622
- const real = fs12.realpathSync(current);
14623
- if (real !== root && !real.startsWith(`${root}${path14.sep}`))
14662
+ current = path15.join(current, segment);
14663
+ const stat = fs13.lstatSync(current);
14664
+ const real = fs13.realpathSync(current);
14665
+ if (real !== root && !real.startsWith(`${root}${path15.sep}`))
14624
14666
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
14625
14667
  fields: [
14626
14668
  {
@@ -14636,7 +14678,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
14636
14678
  if (current !== target && !stat.isDirectory())
14637
14679
  return failure2("invalid_input", "document reference ancestor is not a directory");
14638
14680
  }
14639
- if (digestBytes(fs12.readFileSync(target)) !== reference.digest)
14681
+ if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
14640
14682
  return failure2("permission_denied", "decision document bytes have changed");
14641
14683
  } catch {
14642
14684
  return failure2("invalid_input", "decision document is unavailable");
@@ -15493,7 +15535,7 @@ var judgeInputSummary = (raw) => {
15493
15535
 
15494
15536
  // packages/workit-core/src/autonomy.ts
15495
15537
  import os3 from "node:os";
15496
- import path15 from "node:path";
15538
+ import path16 from "node:path";
15497
15539
  var accountHome = () => {
15498
15540
  try {
15499
15541
  return os3.userInfo().homedir;
@@ -15502,10 +15544,10 @@ var accountHome = () => {
15502
15544
  }
15503
15545
  };
15504
15546
  var grantsHome = { resolve: accountHome };
15505
- var grantsDir = () => path15.join(grantsHome.resolve(), ".config", "workit");
15547
+ var grantsDir = () => path16.join(grantsHome.resolve(), ".config", "workit");
15506
15548
  var grantsOverride = () => {
15507
- const configured = path15.resolve(resolveConfigDir());
15508
- return configured === path15.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path15.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
15549
+ const configured = path16.resolve(resolveConfigDir());
15550
+ return configured === path16.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path16.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
15509
15551
  };
15510
15552
  var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
15511
15553
  var DEFAULT_GRANTS = Object.freeze({
@@ -17487,7 +17529,7 @@ function entryCompactContext(store, entry, session, context) {
17487
17529
  return null;
17488
17530
  const freshness = { mode: "signal", current: () => recentSignal(store.root) };
17489
17531
  const observed = task?.data.evidence.some(isObservedCheck) ?? false;
17490
- const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path16.resolve(store.root, ref.path)] : []));
17532
+ const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path17.resolve(store.root, ref.path)] : []));
17491
17533
  const observedFiles = filesKey(files);
17492
17534
  const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
17493
17535
  if (!compact.ok || !compact.data)
@@ -17556,7 +17598,7 @@ var turnContextText = (input, descriptor) => {
17556
17598
  }
17557
17599
  };
17558
17600
  var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
17559
- var digestOf = (text) => createHash10("sha256").update(text).digest("hex");
17601
+ var digestOf = (text) => createHash11("sha256").update(text).digest("hex");
17560
17602
  var turnMarker = (input) => {
17561
17603
  if (!input.session.id)
17562
17604
  return null;
@@ -17565,32 +17607,32 @@ var turnMarker = (input) => {
17565
17607
  if (location instanceof Error)
17566
17608
  return null;
17567
17609
  const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
17568
- return path16.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
17610
+ return path17.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
17569
17611
  } catch {
17570
17612
  return null;
17571
17613
  }
17572
17614
  };
17573
17615
  var writeTurnDigest = (file, digest) => {
17574
17616
  try {
17575
- fs13.mkdirSync(path16.dirname(file), { recursive: true });
17576
- fs13.writeFileSync(file, `${JSON.stringify({ digest })}
17617
+ fs14.mkdirSync(path17.dirname(file), { recursive: true });
17618
+ fs14.writeFileSync(file, `${JSON.stringify({ digest })}
17577
17619
  `);
17578
17620
  } catch {}
17579
17621
  };
17580
17622
  var clearTurnDigest = (file) => {
17581
17623
  try {
17582
- fs13.rmSync(file, { force: true });
17624
+ fs14.rmSync(file, { force: true });
17583
17625
  } catch {}
17584
17626
  };
17585
17627
  var pruneTurnDigests = (dir, now) => {
17586
17628
  try {
17587
- for (const name of fs13.readdirSync(dir)) {
17629
+ for (const name of fs14.readdirSync(dir)) {
17588
17630
  if (!name.startsWith("turn-"))
17589
17631
  continue;
17590
- const file = path16.join(dir, name);
17632
+ const file = path17.join(dir, name);
17591
17633
  try {
17592
- if (now - fs13.statSync(file).mtimeMs > TURN_TTL_MS)
17593
- fs13.rmSync(file, { force: true });
17634
+ if (now - fs14.statSync(file).mtimeMs > TURN_TTL_MS)
17635
+ fs14.rmSync(file, { force: true });
17594
17636
  } catch {}
17595
17637
  }
17596
17638
  } catch {}
@@ -17606,7 +17648,7 @@ var changedTurnContext = (input, text) => {
17606
17648
  return text;
17607
17649
  const digest = digestOf(text);
17608
17650
  try {
17609
- const previous = JSON.parse(fs13.readFileSync(file, "utf8"));
17651
+ const previous = JSON.parse(fs14.readFileSync(file, "utf8"));
17610
17652
  if (previous.digest === digest)
17611
17653
  return null;
17612
17654
  } catch {}
@@ -17617,14 +17659,26 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
17617
17659
  const file = turnMarker(input);
17618
17660
  if (!file)
17619
17661
  return;
17620
- pruneTurnDigests(path16.dirname(file), Date.now());
17662
+ pruneTurnDigests(path17.dirname(file), Date.now());
17621
17663
  if (text)
17622
17664
  writeTurnDigest(file, digestOf(text));
17623
17665
  else
17624
17666
  clearTurnDigest(file);
17625
17667
  };
17668
+ // packages/workit-core/src/core/route-intent.ts
17669
+ import { spawnSync as spawnSync12 } from "node:child_process";
17670
+ import { existsSync as existsSync6 } from "node:fs";
17671
+ import path20 from "node:path";
17672
+
17673
+ // packages/workit-core/src/hooks/raw-git.ts
17674
+ import { spawnSync as spawnSync11 } from "node:child_process";
17675
+ import { createHash as createHash14 } from "node:crypto";
17676
+ import fs16 from "node:fs";
17677
+ import os4 from "node:os";
17678
+ import path19 from "node:path";
17679
+
17626
17680
  // packages/workit-core/src/core/branch.ts
17627
- import { createHash as createHash11 } from "node:crypto";
17681
+ import { createHash as createHash12 } from "node:crypto";
17628
17682
  var withReleaseTracks = (workspaceRoot, policy) => {
17629
17683
  const read = workspaceReleaseTracks(workspaceRoot);
17630
17684
  if (read.error)
@@ -17660,10 +17714,72 @@ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
17660
17714
  provenance: { commitPolicy: selected.source }
17661
17715
  };
17662
17716
  }
17717
+ var KIND_SYNONYMS = {
17718
+ fix: "bugfix",
17719
+ fixes: "bugfix",
17720
+ bug: "bugfix",
17721
+ bugs: "bugfix",
17722
+ bugfixes: "bugfix",
17723
+ feat: "feature",
17724
+ features: "feature",
17725
+ chore: "feature",
17726
+ docs: "feature",
17727
+ doc: "feature",
17728
+ refactor: "feature",
17729
+ test: "feature",
17730
+ tests: "feature",
17731
+ ci: "feature",
17732
+ build: "feature",
17733
+ perf: "feature",
17734
+ style: "feature",
17735
+ deps: "feature",
17736
+ task: "feature",
17737
+ enhancement: "feature",
17738
+ hot: "hotfix",
17739
+ hotfixes: "hotfix",
17740
+ releases: "release",
17741
+ rel: "release"
17742
+ };
17743
+ var allowedKinds = (allowed) => [
17744
+ ...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
17745
+ ];
17746
+ var editDistance = (a, b) => {
17747
+ const row = Array.from({ length: b.length + 1 }, (_, index) => index);
17748
+ for (let i = 1;i <= a.length; i++) {
17749
+ let previous = row[0];
17750
+ row[0] = i;
17751
+ for (let j = 1;j <= b.length; j++) {
17752
+ const saved = row[j];
17753
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
17754
+ previous = saved;
17755
+ }
17756
+ }
17757
+ return row[b.length];
17758
+ };
17759
+ var suggestBranchName = (name, kinds) => {
17760
+ if (!kinds.length)
17761
+ return null;
17762
+ const slash = name.indexOf("/");
17763
+ const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
17764
+ const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
17765
+ const synonym = KIND_SYNONYMS[prefix];
17766
+ const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
17767
+ const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
17768
+ return `${kind}/${rest}`;
17769
+ };
17770
+ var kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", ");
17771
+ var aliasNote = (kinds) => {
17772
+ const notes = [
17773
+ ["fix", "bugfix"],
17774
+ ["chore", "feature"]
17775
+ ].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
17776
+ return notes.length ? ` (${notes.join(", ")})` : "";
17777
+ };
17663
17778
  var validateBranchNamePolicy = (name, policy, source) => {
17664
17779
  const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
17780
+ const kinds = allowedKinds(policy.allowed);
17665
17781
  if (policy.protected.has(name.toLowerCase())) {
17666
- const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
17782
+ const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
17667
17783
  return {
17668
17784
  ok: false,
17669
17785
  rule: "protected_ref",
@@ -17674,7 +17790,8 @@ var validateBranchNamePolicy = (name, policy, source) => {
17674
17790
  };
17675
17791
  }
17676
17792
  if (!policy.allowed.some((pattern) => pattern.test(name))) {
17677
- const correction = `choose a branch matching one of: ${allowed.join(", ")}`;
17793
+ const suggestion = suggestBranchName(name, kinds);
17794
+ const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
17678
17795
  return {
17679
17796
  ok: false,
17680
17797
  rule: "allowed_pattern",
@@ -17692,7 +17809,7 @@ var branchPolicySnapshotFor = (workspaceRoot) => {
17692
17809
  const source = resolved.provenance.branchPolicy;
17693
17810
  const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
17694
17811
  const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
17695
- const fingerprint = createHash11("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
17812
+ const fingerprint = createHash12("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
17696
17813
  return {
17697
17814
  branchPolicy,
17698
17815
  namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
@@ -17717,31 +17834,87 @@ var validateBranchNameFor = (workspaceRoot, name) => {
17717
17834
  }
17718
17835
  };
17719
17836
  var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
17720
-
17721
- // packages/workit-core/src/core/route-intent.ts
17722
- var shellBranchTarget = (command) => {
17723
- if (!command.trim() || /[\r\n;&|<>`'"$()\\*?{}~!#]/u.test(command) || command.includes("[") || command.includes("]"))
17724
- return null;
17725
- const args = command.trim().split(/\s+/u);
17726
- if (args[0] !== "git")
17837
+ var gitFlowOf = (root, integration) => {
17838
+ try {
17839
+ if (resolveBranchPolicyFor(root).preset !== "gitflow")
17840
+ return null;
17841
+ const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
17842
+ const names = branchNames(root);
17843
+ const production = productionBranchOf(names);
17844
+ const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
17845
+ return { develop, production, developExists: names.has(develop) };
17846
+ } catch {
17727
17847
  return null;
17728
- const option = args[2];
17729
- const target = args[1] === "switch" && ["-c", "--create", "-C", "--force-create"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "checkout" && ["-b", "-B"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "branch" && (args.length === 3 || args.length === 4) ? args[2] : null;
17730
- return target && !target.startsWith("-") ? target : null;
17848
+ }
17849
+ };
17850
+ var releaseBranchesOf = (cwd) => {
17851
+ try {
17852
+ const resolved = resolveBranchPolicyFor(cwd);
17853
+ const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
17854
+ return {
17855
+ preset: resolved.preset,
17856
+ branches: flow ? [flow.production, flow.develop] : [],
17857
+ production: flow ? flow.production : resolved.defaultTargetBranch
17858
+ };
17859
+ } catch {
17860
+ return { preset: "unknown", branches: [], production: null };
17861
+ }
17731
17862
  };
17732
- var shellBranchPolicyViolation = (root, command) => {
17733
- const target = shellBranchTarget(command);
17734
- return target === null ? null : validateBranchNameFor(root, target);
17863
+ var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
17864
+ var branchGuidance = (cwd) => {
17865
+ try {
17866
+ const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
17867
+ const kinds = allowedKinds(patterns);
17868
+ const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
17869
+ const anyName = patterns.some((pattern) => pattern.source === "^.*$");
17870
+ const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
17871
+ const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
17872
+ const resolved = vcsConfig("resolve", cwd);
17873
+ const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
17874
+ const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
17875
+ if (flow?.developExists)
17876
+ return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
17877
+ const names = branchNames(cwd);
17878
+ const configured = String(resolved.baseBranch ?? integration ?? "") || null;
17879
+ const trunk = productionBranchOf(names);
17880
+ const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
17881
+ return `${allowed} ${base}`;
17882
+ } catch {
17883
+ return null;
17884
+ }
17735
17885
  };
17886
+ var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
17736
17887
 
17737
- // packages/workit-core/src/hooks/policy.ts
17738
- var shellPolicy = (cwd, command) => {
17739
- const policy = shellBranchPolicyViolation(cwd, command);
17740
- return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
17888
+ // packages/workit-core/src/hooks/session-marker.ts
17889
+ import { createHash as createHash13 } from "node:crypto";
17890
+ import fs15 from "node:fs";
17891
+ import path18 from "node:path";
17892
+ var sessionMarkerFile = (input, session, store) => {
17893
+ let dir = store;
17894
+ if (dir === undefined) {
17895
+ const location = resolveStore(input.cwd);
17896
+ if (location instanceof Error || !location.shared)
17897
+ return null;
17898
+ dir = location.dir;
17899
+ }
17900
+ const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
17901
+ return path18.join(dir, "hooks", `skills-${name}.json`);
17902
+ };
17903
+ var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
17904
+ var readSessionMarker = (file) => {
17905
+ try {
17906
+ const value = JSON.parse(fs15.readFileSync(file, "utf8"));
17907
+ return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
17908
+ } catch {
17909
+ return { nudged: [], loaded: [], raw: [] };
17910
+ }
17911
+ };
17912
+ var writeSessionMarker = (file, marker) => {
17913
+ try {
17914
+ fs15.mkdirSync(path18.dirname(file), { recursive: true });
17915
+ fs15.writeFileSync(file, JSON.stringify(marker));
17916
+ } catch {}
17741
17917
  };
17742
- // packages/workit-core/src/hooks/write-gate.ts
17743
- import { realpathSync as realpathSync5 } from "node:fs";
17744
- import path17 from "node:path";
17745
17918
 
17746
17919
  // packages/workit-core/src/hooks/shell-words.ts
17747
17920
  var closingQuote = (command, start, dialect) => {
@@ -17866,295 +18039,81 @@ var segmentsOf = (command, dialect = "posix") => {
17866
18039
  return segments;
17867
18040
  };
17868
18041
 
17869
- // packages/workit-core/src/hooks/write-gate.ts
18042
+ // packages/workit-core/src/hooks/raw-git.ts
17870
18043
  var NONE2 = { kind: "none" };
17871
- var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
17872
- var canonical2 = (file) => {
17873
- let rest = "";
17874
- for (let dir = path17.resolve(file);; dir = path17.dirname(dir)) {
17875
- try {
17876
- return path17.join(realpathSync5(dir), rest);
17877
- } catch {
17878
- if (path17.dirname(dir) === dir)
17879
- return path17.resolve(file);
17880
- rest = rest ? path17.join(path17.basename(dir), rest) : path17.basename(dir);
17881
- }
17882
- }
18044
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
18045
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
18046
+ var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
18047
+ var CD = new Set(["cd", "chdir", "set-location", "sl"]);
18048
+ var PUSHD = new Set(["pushd", "push-location"]);
18049
+ var POPD = new Set(["popd", "pop-location"]);
18050
+ var GIT_VALUE_OPTIONS = new Set([
18051
+ "-c",
18052
+ "--git-dir",
18053
+ "--work-tree",
18054
+ "--namespace",
18055
+ "--exec-path",
18056
+ "--config-env",
18057
+ "--super-prefix"
18058
+ ]);
18059
+ var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
18060
+ var joinDir = (base, next) => {
18061
+ if (base === undefined || /[$`*?]/.test(next) || next === "-")
18062
+ return;
18063
+ const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
18064
+ return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
17883
18065
  };
17884
- var exempt = (cwd, root, file, plans) => {
17885
- const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
17886
- const relative = path17.relative(fold(canonical2(root)), fold(canonical2(path17.resolve(cwd, file))));
17887
- if (relative.startsWith("..") || path17.isAbsolute(relative))
17888
- return true;
17889
- const posix = relative.split(path17.sep).join("/");
17890
- const segments = posix.split("/");
17891
- return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
18066
+ var cdTarget = (words) => {
18067
+ const args = words.slice(1);
18068
+ const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
18069
+ if (flag >= 0)
18070
+ return args[flag + 1] ?? null;
18071
+ return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
17892
18072
  };
17893
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
17894
- var operands = (words) => words.filter((item) => !item.startsWith("-"));
17895
- var editsOf = (input) => {
18073
+ var stripPrefixes = (input) => {
17896
18074
  let words = input;
17897
- while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
17898
- words = words.slice(1);
17899
- const [verb, ...args] = words;
17900
- const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
17901
- if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
17902
- return [];
17903
- switch (name) {
17904
- case "touch":
17905
- case "mkdir":
17906
- case "rm":
17907
- case "rmdir":
17908
- case "tee":
17909
- case "mv":
17910
- case "truncate":
17911
- return operands(args);
17912
- case "cp":
17913
- case "ln":
17914
- case "install": {
17915
- const target = args.indexOf("-t");
17916
- if (target >= 0)
17917
- return args[target + 1] ? [args[target + 1]] : null;
17918
- const last = operands(args).at(-1);
17919
- return last ? [last] : null;
18075
+ for (;; ) {
18076
+ const first = words[0];
18077
+ if (first === undefined)
18078
+ return words;
18079
+ if (/^\w+=/.test(first))
18080
+ words = words.slice(1);
18081
+ else if (PREFIXES2.has(first)) {
18082
+ words = words.slice(1);
18083
+ while (words[0]?.startsWith("-"))
18084
+ words = words.slice(words[0] === "-u" ? 2 : 1);
18085
+ } else if (first === "timeout") {
18086
+ words = words.slice(1);
18087
+ while (words[0]?.startsWith("-"))
18088
+ words = words.slice(1);
18089
+ words = words.slice(1);
18090
+ } else
18091
+ return words;
18092
+ }
18093
+ };
18094
+ var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
18095
+ function rawInvocations(command, dialect = "posix", depth = 0) {
18096
+ const found = [];
18097
+ let dir = null;
18098
+ const groups = [];
18099
+ let stack = [];
18100
+ for (const segment of segmentsOf(command, dialect)) {
18101
+ if (segment.group === "open") {
18102
+ groups.push({ dir, stack: [...stack] });
18103
+ continue;
17920
18104
  }
17921
- case "dd":
17922
- return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
17923
- case "sed":
17924
- case "perl": {
17925
- if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
17926
- return [];
17927
- const scripted = args.some((item) => item === "-e" || item === "-f");
17928
- const files = [];
17929
- for (let index = 0;index < args.length; index++) {
17930
- if (args[index] === "-e" || args[index] === "-f")
17931
- index++;
17932
- else if (!args[index].startsWith("-"))
17933
- files.push(args[index]);
17934
- }
17935
- return scripted ? files : files.slice(1);
18105
+ if (segment.group === "close") {
18106
+ const saved = groups.pop();
18107
+ if (saved)
18108
+ ({ dir, stack } = saved);
18109
+ continue;
17936
18110
  }
17937
- case "patch":
17938
- return null;
17939
- case "set-content":
17940
- case "add-content":
17941
- case "out-file":
17942
- case "new-item":
17943
- case "ni":
17944
- case "remove-item":
17945
- case "del":
17946
- case "copy-item":
17947
- case "move-item": {
17948
- const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
17949
- if (flag >= 0)
17950
- return args[flag + 1] ? [args[flag + 1]] : null;
17951
- const files = operands(args);
17952
- if (name === "move-item")
17953
- return files.length ? files : null;
17954
- const file = name === "copy-item" ? files.at(-1) : files[0];
17955
- return file ? [file] : null;
17956
- }
17957
- case "git": {
17958
- const sub = operands(args)[0];
17959
- if (sub === "apply")
17960
- return null;
17961
- if (sub === "restore" || sub === "mv" || sub === "rm")
17962
- return operands(args).slice(1);
17963
- if (sub === "checkout" && args.includes("--"))
17964
- return args.slice(args.indexOf("--") + 1);
17965
- return [];
17966
- }
17967
- default:
17968
- return [];
17969
- }
17970
- };
17971
- var shellWrites = (command) => {
17972
- const targets = [];
17973
- let unknown = false;
17974
- for (const segment of segmentsOf(command)) {
17975
- targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
17976
- const edits = editsOf(segment.words);
17977
- if (edits === null)
17978
- unknown = true;
17979
- else
17980
- targets.push(...edits);
17981
- }
17982
- if (!unknown && targets.length === 0)
17983
- return { writes: false, targets: null };
17984
- return { writes: true, targets: unknown ? null : targets };
17985
- };
17986
- var cache2 = new Map;
17987
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path17.resolve(root, file)) ?? "-").join("\x00");
17988
- var gateFor = (cwd) => {
17989
- const store = new TaskStore(cwd);
17990
- const listed = store.listTaskIndex();
17991
- if (!listed.ok)
17992
- return null;
17993
- const key = store.currentKey();
17994
- if (!key.ok)
17995
- return null;
17996
- const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
17997
- if (!entry)
17998
- return { blockers: [], root: store.root, plans: new Set };
17999
- const ledger = ledgerPath(store.root);
18000
- const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
18001
- const hit = cache2.get(store.root);
18002
- if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
18003
- return hit;
18004
- const task = store.readTask(entry.id);
18005
- if (!task.ok)
18006
- return null;
18007
- const workspace = store.readWorkspace();
18008
- const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
18009
- const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
18010
- const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
18011
- return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
18012
- });
18013
- const gate = {
18014
- blockers,
18015
- root: store.root,
18016
- plans,
18017
- key: cacheKey,
18018
- planKey: planKeyOf(store.root, plans)
18019
- };
18020
- cache2.set(store.root, gate);
18021
- if (cache2.size > 32)
18022
- cache2.delete(cache2.keys().next().value);
18023
- return gate;
18024
- };
18025
- function writeGate(cwd, paths) {
18026
- try {
18027
- const gate = gateFor(cwd);
18028
- if (!gate || gate.blockers.length === 0)
18029
- return NONE2;
18030
- if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
18031
- return NONE2;
18032
- const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
18033
- return {
18034
- kind: "deny",
18035
- reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
18036
- unblock: gate.blockers[0].reason
18037
- };
18038
- } catch {
18039
- return NONE2;
18040
- }
18041
- }
18042
- // packages/workit-core/src/hooks/stop.ts
18043
- import { spawnSync as spawnSync12 } from "node:child_process";
18044
-
18045
- // packages/workit-core/src/hooks/raw-git.ts
18046
- import { spawnSync as spawnSync11 } from "node:child_process";
18047
- import { createHash as createHash13 } from "node:crypto";
18048
- import fs15 from "node:fs";
18049
- import os4 from "node:os";
18050
- import path19 from "node:path";
18051
-
18052
- // packages/workit-core/src/hooks/session-marker.ts
18053
- import { createHash as createHash12 } from "node:crypto";
18054
- import fs14 from "node:fs";
18055
- import path18 from "node:path";
18056
- var sessionMarkerFile = (input, session, store) => {
18057
- let dir = store;
18058
- if (dir === undefined) {
18059
- const location = resolveStore(input.cwd);
18060
- if (location instanceof Error || !location.shared)
18061
- return null;
18062
- dir = location.dir;
18063
- }
18064
- const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
18065
- return path18.join(dir, "hooks", `skills-${name}.json`);
18066
- };
18067
- var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
18068
- var readSessionMarker = (file) => {
18069
- try {
18070
- const value = JSON.parse(fs14.readFileSync(file, "utf8"));
18071
- return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
18072
- } catch {
18073
- return { nudged: [], loaded: [], raw: [] };
18074
- }
18075
- };
18076
- var writeSessionMarker = (file, marker) => {
18077
- try {
18078
- fs14.mkdirSync(path18.dirname(file), { recursive: true });
18079
- fs14.writeFileSync(file, JSON.stringify(marker));
18080
- } catch {}
18081
- };
18082
-
18083
- // packages/workit-core/src/hooks/raw-git.ts
18084
- var NONE3 = { kind: "none" };
18085
- var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
18086
- var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
18087
- var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
18088
- var CD = new Set(["cd", "chdir", "set-location", "sl"]);
18089
- var PUSHD = new Set(["pushd", "push-location"]);
18090
- var POPD = new Set(["popd", "pop-location"]);
18091
- var GIT_VALUE_OPTIONS = new Set([
18092
- "-c",
18093
- "--git-dir",
18094
- "--work-tree",
18095
- "--namespace",
18096
- "--exec-path",
18097
- "--config-env",
18098
- "--super-prefix"
18099
- ]);
18100
- var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
18101
- var joinDir = (base, next) => {
18102
- if (base === undefined || /[$`*?]/.test(next) || next === "-")
18103
- return;
18104
- const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
18105
- return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
18106
- };
18107
- var cdTarget = (words) => {
18108
- const args = words.slice(1);
18109
- const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
18110
- if (flag >= 0)
18111
- return args[flag + 1] ?? null;
18112
- return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
18113
- };
18114
- var stripPrefixes = (input) => {
18115
- let words = input;
18116
- for (;; ) {
18117
- const first = words[0];
18118
- if (first === undefined)
18119
- return words;
18120
- if (/^\w+=/.test(first))
18121
- words = words.slice(1);
18122
- else if (PREFIXES3.has(first)) {
18123
- words = words.slice(1);
18124
- while (words[0]?.startsWith("-"))
18125
- words = words.slice(words[0] === "-u" ? 2 : 1);
18126
- } else if (first === "timeout") {
18127
- words = words.slice(1);
18128
- while (words[0]?.startsWith("-"))
18129
- words = words.slice(1);
18130
- words = words.slice(1);
18131
- } else
18132
- return words;
18133
- }
18134
- };
18135
- var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
18136
- function rawInvocations(command, dialect = "posix", depth = 0) {
18137
- const found = [];
18138
- let dir = null;
18139
- const groups = [];
18140
- let stack = [];
18141
- for (const segment of segmentsOf(command, dialect)) {
18142
- if (segment.group === "open") {
18143
- groups.push({ dir, stack: [...stack] });
18144
- continue;
18145
- }
18146
- if (segment.group === "close") {
18147
- const saved = groups.pop();
18148
- if (saved)
18149
- ({ dir, stack } = saved);
18150
- continue;
18151
- }
18152
- const words = stripPrefixes(segment.words);
18153
- const name = commandName(words[0]);
18154
- if (CD.has(name)) {
18155
- const target = cdTarget(words);
18156
- dir = target === null ? os4.homedir() : joinDir(dir, target);
18157
- continue;
18111
+ const words = stripPrefixes(segment.words);
18112
+ const name = commandName(words[0]);
18113
+ if (CD.has(name)) {
18114
+ const target = cdTarget(words);
18115
+ dir = target === null ? os4.homedir() : joinDir(dir, target);
18116
+ continue;
18158
18117
  }
18159
18118
  if (PUSHD.has(name)) {
18160
18119
  const target = cdTarget(words);
@@ -18234,6 +18193,10 @@ var pushAction = (args) => {
18234
18193
  let force = false;
18235
18194
  let lease = false;
18236
18195
  let wholesale = false;
18196
+ let allTags = false;
18197
+ let deletes = false;
18198
+ let allBranches = false;
18199
+ let mirror = false;
18237
18200
  let remote = null;
18238
18201
  const positionals = [];
18239
18202
  for (let index = 1;index < args.length; index++) {
@@ -18254,14 +18217,22 @@ var pushAction = (args) => {
18254
18217
  force = true;
18255
18218
  else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
18256
18219
  lease = true;
18257
- else if (["--all", "--mirror", "--branches", "--tags"].includes(arg))
18258
- wholesale = true;
18220
+ else if (arg === "--delete" || arg === "-d")
18221
+ deletes = true;
18222
+ else if (arg === "--tags")
18223
+ allTags = wholesale = true;
18224
+ else if (arg === "--all" || arg === "--branches")
18225
+ allBranches = wholesale = true;
18226
+ else if (arg === "--mirror")
18227
+ mirror = wholesale = true;
18259
18228
  else if (!arg.startsWith("-"))
18260
18229
  positionals.push(arg);
18261
18230
  }
18262
18231
  const specs = remote === null ? positionals.slice(1) : positionals;
18263
18232
  remote ??= positionals[0] ?? null;
18264
18233
  const targets = [];
18234
+ const tags = [];
18235
+ const deleted = [];
18265
18236
  for (const raw of specs) {
18266
18237
  if (raw.startsWith("+"))
18267
18238
  force = true;
@@ -18270,16 +18241,29 @@ var pushAction = (args) => {
18270
18241
  const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
18271
18242
  if (!destination)
18272
18243
  continue;
18273
- if (destination === "HEAD")
18274
- targets.push(null);
18275
- else if (destination.startsWith("refs/heads/"))
18276
- targets.push(destination.slice(11));
18277
- else if (!destination.startsWith("refs/"))
18278
- targets.push(destination);
18244
+ const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
18245
+ if (destination.startsWith("refs/tags/"))
18246
+ tags.push(destination.slice(10));
18247
+ if (name === undefined)
18248
+ continue;
18249
+ targets.push(name);
18250
+ if (colon === 0 && name !== null)
18251
+ deleted.push(name);
18279
18252
  }
18280
18253
  if (specs.length === 0 && !wholesale)
18281
18254
  targets.push(null);
18282
- return { kind: "push", targets, blindForce: force && !lease, remote };
18255
+ return {
18256
+ kind: "push",
18257
+ targets,
18258
+ blindForce: force && !lease,
18259
+ remote,
18260
+ ...tags.length ? { tags } : {},
18261
+ ...allTags ? { allTags: true } : {},
18262
+ ...deletes ? { deletes: true } : {},
18263
+ ...deleted.length ? { deleted } : {},
18264
+ ...allBranches ? { allBranches: true } : {},
18265
+ ...mirror ? { mirror: true } : {}
18266
+ };
18283
18267
  };
18284
18268
  var VALUE_OPTIONS = {
18285
18269
  git: new Set([
@@ -18355,7 +18339,7 @@ var repoAt = (dir) => {
18355
18339
  return { location, dir, branch: resolveTaskKey(dir, location).branch };
18356
18340
  };
18357
18341
  var isWorkitWorkspace = (repo) => {
18358
- if (fs15.existsSync(repo.location.dir))
18342
+ if (fs16.existsSync(repo.location.dir))
18359
18343
  return true;
18360
18344
  try {
18361
18345
  return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
@@ -18386,7 +18370,7 @@ var headSha2 = (repo) => {
18386
18370
  if (!git || git.reftable)
18387
18371
  return null;
18388
18372
  try {
18389
- const head = fs15.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
18373
+ const head = fs16.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
18390
18374
  if (/^[0-9a-f]{40,64}$/.test(head))
18391
18375
  return head;
18392
18376
  const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
@@ -18394,20 +18378,72 @@ var headSha2 = (repo) => {
18394
18378
  return null;
18395
18379
  const common = path19.dirname(repo.location.dir);
18396
18380
  try {
18397
- return fs15.readFileSync(path19.join(common, ref), "utf8").trim() || null;
18381
+ return fs16.readFileSync(path19.join(common, ref), "utf8").trim() || null;
18398
18382
  } catch {}
18399
- const packed = fs15.readFileSync(path19.join(common, "packed-refs"), "utf8");
18383
+ const packed = fs16.readFileSync(path19.join(common, "packed-refs"), "utf8");
18400
18384
  return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
18401
18385
  } catch {
18402
18386
  return null;
18403
18387
  }
18404
18388
  };
18405
18389
  var quote = (text) => `\`${text}\``;
18406
- var refusal = (action, branch) => {
18390
+ var refusal = (action, branch, dir) => {
18407
18391
  if (action.kind === "merge")
18408
18392
  return `workit: raw ${quote(action.forge === "gh" ? "gh pr merge" : "glab mr merge")} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
18393
+ const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
18394
+ if (dir && branch && plainPush && releaseBranch(dir, branch))
18395
+ return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release v<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
18409
18396
  return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
18410
18397
  };
18398
+ var refFacts = (repo, remote) => {
18399
+ const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
18400
+ const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
18401
+ const remotePrefix = `refs/remotes/${remote}/`;
18402
+ for (const ref of (listed.stdout ?? "").split(`
18403
+ `)) {
18404
+ if (ref.startsWith("refs/heads/"))
18405
+ facts.heads.add(ref.slice(11));
18406
+ else if (ref.startsWith("refs/tags/"))
18407
+ facts.tags.add(ref.slice(10));
18408
+ else if (ref.startsWith(remotePrefix))
18409
+ facts.remoteHeads.add(ref.slice(remotePrefix.length));
18410
+ }
18411
+ return facts;
18412
+ };
18413
+ var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
18414
+ var tagRefusal = (action, facts) => {
18415
+ const tags = [
18416
+ ...action.tags ?? [],
18417
+ ...action.targets.filter((name) => name !== null && isTag(facts, name))
18418
+ ];
18419
+ if (!tags.length && !action.allTags)
18420
+ return null;
18421
+ const named = tags.length ? tags.join(" --release ") : "v<version>";
18422
+ return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (v<version> tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
18423
+ };
18424
+ var remoteHas = (repo, remote, branch) => {
18425
+ const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
18426
+ cwd: repo.dir,
18427
+ encoding: "utf8",
18428
+ timeout: 3000,
18429
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
18430
+ });
18431
+ return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
18432
+ };
18433
+ var offPolicyPush = (action, repo, facts, remote) => {
18434
+ const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
18435
+ for (const branch of names) {
18436
+ if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
18437
+ continue;
18438
+ if (facts.remoteHeads.has(branch))
18439
+ continue;
18440
+ const check = validateBranchNameFor(repo.dir, branch);
18441
+ if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
18442
+ continue;
18443
+ return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
18444
+ }
18445
+ return null;
18446
+ };
18411
18447
  var nudgeFor = (action, host, session) => {
18412
18448
  switch (action.kind) {
18413
18449
  case "commit": {
@@ -18470,7 +18506,7 @@ var unseenNudges = (input, nudges, store) => {
18470
18506
  function rawGitPre(input, command, options = {}) {
18471
18507
  try {
18472
18508
  if (!mentionsRawTool(command))
18473
- return NONE3;
18509
+ return NONE2;
18474
18510
  const dialect = dialectOf(input);
18475
18511
  const nudges = new Map;
18476
18512
  let store = null;
@@ -18478,32 +18514,43 @@ function rawGitPre(input, command, options = {}) {
18478
18514
  store ??= repo.location.dir;
18479
18515
  if (action.kind === "merge")
18480
18516
  return deny(refusal(action, null));
18481
- if (action.kind === "push")
18517
+ if (action.kind === "push" && toPushRemote(repo, action.remote)) {
18518
+ if (action.mirror)
18519
+ return deny(`workit: raw ${quote("git push --mirror")} rewrites and deletes every remote ref, protected branches and tags included. Push one branch with ${quote("workit git push")}.`);
18482
18520
  for (const target of action.targets) {
18483
18521
  const branch = target ?? repo.branch;
18484
- if (branch && guardedBranch(repo.dir, branch) && toPushRemote(repo, action.remote))
18485
- return deny(refusal(action, branch));
18522
+ if (branch && guardedBranch(repo.dir, branch))
18523
+ return deny(refusal(action, branch, repo.dir));
18486
18524
  }
18525
+ const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
18526
+ const facts = refFacts(repo, remote);
18527
+ const tagDeny = tagRefusal(action, facts);
18528
+ if (tagDeny)
18529
+ return deny(tagDeny);
18530
+ const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
18531
+ if (nameDeny)
18532
+ return deny(nameDeny);
18533
+ }
18487
18534
  const nudge = nudgeFor(action, input.host, input.session.id);
18488
18535
  if (nudge && !nudges.has(nudgeKind(action)))
18489
18536
  nudges.set(nudgeKind(action), nudge);
18490
18537
  }
18491
18538
  if (options.nudge === false || !store || nudges.size === 0)
18492
- return NONE3;
18539
+ return NONE2;
18493
18540
  if (workitInvocations(command, dialect).length > 0)
18494
- return NONE3;
18541
+ return NONE2;
18495
18542
  const shown = unseenNudges(input, nudges, store);
18496
18543
  return shown.length ? { kind: "context", text: shown.join(`
18497
- `) } : NONE3;
18544
+ `) } : NONE2;
18498
18545
  } catch {
18499
- return NONE3;
18546
+ return NONE2;
18500
18547
  }
18501
18548
  }
18502
18549
  var deny = (reason) => ({ kind: "deny", reason, unblock: null });
18503
18550
  var NEXT_COMMAND = "next";
18504
18551
  var MARKER_TTL_SECONDS = 3600;
18505
- var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash13("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
18506
- var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash13("sha256").update(command).digest("hex")}`;
18552
+ var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
18553
+ var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
18507
18554
  function noteRawCommit(input, command, key) {
18508
18555
  try {
18509
18556
  if (!input.session.id || !/\bcommit\b/.test(command))
@@ -18513,8 +18560,8 @@ function noteRawCommit(input, command, key) {
18513
18560
  if (action.kind !== "commit")
18514
18561
  continue;
18515
18562
  const file = markerFile(repo, input.session.id, key);
18516
- fs15.mkdirSync(path19.dirname(file), { recursive: true });
18517
- fs15.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
18563
+ fs16.mkdirSync(path19.dirname(file), { recursive: true });
18564
+ fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
18518
18565
  `);
18519
18566
  }
18520
18567
  } catch {}
@@ -18537,11 +18584,11 @@ var headCommit = (dir) => {
18537
18584
  var settle2 = (input, repo, file) => {
18538
18585
  let marker;
18539
18586
  try {
18540
- marker = JSON.parse(fs15.readFileSync(file, "utf8"));
18587
+ marker = JSON.parse(fs16.readFileSync(file, "utf8"));
18541
18588
  } catch {
18542
18589
  return;
18543
18590
  }
18544
- fs15.rmSync(file, { force: true });
18591
+ fs16.rmSync(file, { force: true });
18545
18592
  const session = input.session.id;
18546
18593
  const now = Date.now() / 1000;
18547
18594
  if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
@@ -18590,12 +18637,297 @@ function settlePendingCommit(input) {
18590
18637
  if (!repo)
18591
18638
  return;
18592
18639
  const file = markerFile(repo, input.session.id, NEXT_COMMAND);
18593
- if (fs15.existsSync(file))
18640
+ if (fs16.existsSync(file))
18594
18641
  settle2(input, repo, file);
18595
18642
  } catch {}
18596
18643
  }
18597
18644
 
18645
+ // packages/workit-core/src/core/route-intent.ts
18646
+ var UNKNOWABLE = /[$`*?[\]{}~]/u;
18647
+ var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
18648
+ var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
18649
+ var WORKTREE_CREATE = new Set(["-b", "-B"]);
18650
+ var BRANCH_NO_CREATE = new Set(["-d", "-D", "--delete", "-l", "--list", "-a", "--all", "-r", "--remotes", "--show-current"].concat(["-v", "-vv", "--verbose", "--contains", "--no-contains", "--merged", "--no-merged"]).concat(["--points-at", "--sort", "--format", "--column", "--no-column", "-u"]).concat(["--set-upstream-to", "--unset-upstream", "--edit-description", "-i", "--ignore-case"]));
18651
+ var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
18652
+ var flagValue = (args, flags) => {
18653
+ for (let index = 1;index < args.length; index++) {
18654
+ const arg = args[index];
18655
+ if (arg === "--")
18656
+ return null;
18657
+ if (flags.has(arg))
18658
+ return args[index + 1] ?? null;
18659
+ const eq = arg.indexOf("=");
18660
+ if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
18661
+ return arg.slice(eq + 1);
18662
+ if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
18663
+ return arg.slice(2);
18664
+ }
18665
+ return null;
18666
+ };
18667
+ var WORKTREE_VALUES = new Set(["--reason"]);
18668
+ var worktreeTarget = (args) => {
18669
+ const named = flagValue(args, WORKTREE_CREATE);
18670
+ if (named !== null)
18671
+ return named;
18672
+ const positionals = [];
18673
+ for (let index = 1;index < args.length; index++) {
18674
+ const arg = args[index];
18675
+ if (arg === "--detach" || arg === "-d")
18676
+ return null;
18677
+ if (WORKTREE_VALUES.has(arg))
18678
+ index++;
18679
+ else if (!arg.startsWith("-"))
18680
+ positionals.push(arg);
18681
+ }
18682
+ if (positionals.length !== 1)
18683
+ return null;
18684
+ const base = positionals[0].split(/[\\/]/u).findLast(Boolean) ?? "";
18685
+ return base && base !== "." && base !== ".." ? base : null;
18686
+ };
18687
+ var branchCommandTarget = (args) => {
18688
+ let rename = false;
18689
+ const positionals = [];
18690
+ for (let index = 1;index < args.length; index++) {
18691
+ const arg = args[index];
18692
+ if (arg === "--") {
18693
+ positionals.push(...args.slice(index + 1));
18694
+ break;
18695
+ }
18696
+ const flag = arg.split("=", 1)[0];
18697
+ if (BRANCH_NO_CREATE.has(flag))
18698
+ return null;
18699
+ if (BRANCH_RENAME.has(flag))
18700
+ rename = true;
18701
+ else if (!arg.startsWith("-"))
18702
+ positionals.push(arg);
18703
+ }
18704
+ if (positionals.length < 1 || positionals.length > 2)
18705
+ return null;
18706
+ return rename ? positionals.at(-1) ?? null : positionals[0];
18707
+ };
18708
+ var gitBranchCreation = (args) => {
18709
+ const [sub, second] = args;
18710
+ const target = sub === "switch" ? flagValue(args, SWITCH_CREATE) : sub === "checkout" ? flagValue(args, CHECKOUT_CREATE) : sub === "branch" ? branchCommandTarget(args) : sub === "worktree" && second === "add" ? worktreeTarget(args.slice(1)) : null;
18711
+ return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
18712
+ };
18713
+ var shellBranchTargets = (command, dialect = "posix") => {
18714
+ if (!/\bgit\b/u.test(command))
18715
+ return [];
18716
+ const found = [];
18717
+ for (const invocation of rawInvocations(command, dialect)) {
18718
+ if (invocation.tool !== "git")
18719
+ continue;
18720
+ const branch = gitBranchCreation(invocation.args);
18721
+ if (branch)
18722
+ found.push({ branch, dir: invocation.dir });
18723
+ }
18724
+ return found;
18725
+ };
18726
+ var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
18727
+ cwd: dir,
18728
+ stdio: "ignore",
18729
+ timeout: 5000
18730
+ }).status === 0;
18731
+ var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
18732
+ let judged = false;
18733
+ for (const target of shellBranchTargets(command, dialect)) {
18734
+ if (target.dir === undefined)
18735
+ continue;
18736
+ const dir = target.dir === null ? root : path20.resolve(root, target.dir);
18737
+ if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
18738
+ continue;
18739
+ if (localBranchExists(dir, target.branch))
18740
+ continue;
18741
+ judged = true;
18742
+ const check = validateBranchNameFor(dir, target.branch);
18743
+ if (!check.ok)
18744
+ return check;
18745
+ }
18746
+ return judged ? { ok: true } : null;
18747
+ };
18748
+
18749
+ // packages/workit-core/src/hooks/policy.ts
18750
+ var shellPolicy = (cwd, command, dialect = "posix") => {
18751
+ const policy = shellBranchPolicyViolation(cwd, command, dialect);
18752
+ return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
18753
+ };
18754
+ // packages/workit-core/src/hooks/write-gate.ts
18755
+ import { realpathSync as realpathSync5 } from "node:fs";
18756
+ import path21 from "node:path";
18757
+ var NONE3 = { kind: "none" };
18758
+ var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
18759
+ var canonical2 = (file) => {
18760
+ let rest = "";
18761
+ for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
18762
+ try {
18763
+ return path21.join(realpathSync5(dir), rest);
18764
+ } catch {
18765
+ if (path21.dirname(dir) === dir)
18766
+ return path21.resolve(file);
18767
+ rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
18768
+ }
18769
+ }
18770
+ };
18771
+ var exempt = (cwd, root, file, plans) => {
18772
+ const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
18773
+ const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
18774
+ if (relative.startsWith("..") || path21.isAbsolute(relative))
18775
+ return true;
18776
+ const posix = relative.split(path21.sep).join("/");
18777
+ const segments = posix.split("/");
18778
+ return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
18779
+ };
18780
+ var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
18781
+ var operands = (words) => words.filter((item) => !item.startsWith("-"));
18782
+ var editsOf = (input) => {
18783
+ let words = input;
18784
+ while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
18785
+ words = words.slice(1);
18786
+ const [verb, ...args] = words;
18787
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
18788
+ if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
18789
+ return [];
18790
+ switch (name) {
18791
+ case "touch":
18792
+ case "mkdir":
18793
+ case "rm":
18794
+ case "rmdir":
18795
+ case "tee":
18796
+ case "mv":
18797
+ case "truncate":
18798
+ return operands(args);
18799
+ case "cp":
18800
+ case "ln":
18801
+ case "install": {
18802
+ const target = args.indexOf("-t");
18803
+ if (target >= 0)
18804
+ return args[target + 1] ? [args[target + 1]] : null;
18805
+ const last = operands(args).at(-1);
18806
+ return last ? [last] : null;
18807
+ }
18808
+ case "dd":
18809
+ return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
18810
+ case "sed":
18811
+ case "perl": {
18812
+ if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
18813
+ return [];
18814
+ const scripted = args.some((item) => item === "-e" || item === "-f");
18815
+ const files = [];
18816
+ for (let index = 0;index < args.length; index++) {
18817
+ if (args[index] === "-e" || args[index] === "-f")
18818
+ index++;
18819
+ else if (!args[index].startsWith("-"))
18820
+ files.push(args[index]);
18821
+ }
18822
+ return scripted ? files : files.slice(1);
18823
+ }
18824
+ case "patch":
18825
+ return null;
18826
+ case "set-content":
18827
+ case "add-content":
18828
+ case "out-file":
18829
+ case "new-item":
18830
+ case "ni":
18831
+ case "remove-item":
18832
+ case "del":
18833
+ case "copy-item":
18834
+ case "move-item": {
18835
+ const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
18836
+ if (flag >= 0)
18837
+ return args[flag + 1] ? [args[flag + 1]] : null;
18838
+ const files = operands(args);
18839
+ if (name === "move-item")
18840
+ return files.length ? files : null;
18841
+ const file = name === "copy-item" ? files.at(-1) : files[0];
18842
+ return file ? [file] : null;
18843
+ }
18844
+ case "git": {
18845
+ const sub = operands(args)[0];
18846
+ if (sub === "apply")
18847
+ return null;
18848
+ if (sub === "restore" || sub === "mv" || sub === "rm")
18849
+ return operands(args).slice(1);
18850
+ if (sub === "checkout" && args.includes("--"))
18851
+ return args.slice(args.indexOf("--") + 1);
18852
+ return [];
18853
+ }
18854
+ default:
18855
+ return [];
18856
+ }
18857
+ };
18858
+ var shellWrites = (command) => {
18859
+ const targets = [];
18860
+ let unknown = false;
18861
+ for (const segment of segmentsOf(command)) {
18862
+ targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
18863
+ const edits = editsOf(segment.words);
18864
+ if (edits === null)
18865
+ unknown = true;
18866
+ else
18867
+ targets.push(...edits);
18868
+ }
18869
+ if (!unknown && targets.length === 0)
18870
+ return { writes: false, targets: null };
18871
+ return { writes: true, targets: unknown ? null : targets };
18872
+ };
18873
+ var cache2 = new Map;
18874
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
18875
+ var gateFor = (cwd) => {
18876
+ const store = new TaskStore(cwd);
18877
+ const listed = store.listTaskIndex();
18878
+ if (!listed.ok)
18879
+ return null;
18880
+ const key = store.currentKey();
18881
+ if (!key.ok)
18882
+ return null;
18883
+ const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
18884
+ if (!entry)
18885
+ return { blockers: [], root: store.root, plans: new Set };
18886
+ const ledger = ledgerPath(store.root);
18887
+ const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
18888
+ const hit = cache2.get(store.root);
18889
+ if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
18890
+ return hit;
18891
+ const task = store.readTask(entry.id);
18892
+ if (!task.ok)
18893
+ return null;
18894
+ const workspace = store.readWorkspace();
18895
+ const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
18896
+ const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
18897
+ const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
18898
+ return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
18899
+ });
18900
+ const gate = {
18901
+ blockers,
18902
+ root: store.root,
18903
+ plans,
18904
+ key: cacheKey,
18905
+ planKey: planKeyOf(store.root, plans)
18906
+ };
18907
+ cache2.set(store.root, gate);
18908
+ if (cache2.size > 32)
18909
+ cache2.delete(cache2.keys().next().value);
18910
+ return gate;
18911
+ };
18912
+ function writeGate(cwd, paths) {
18913
+ try {
18914
+ const gate = gateFor(cwd);
18915
+ if (!gate || gate.blockers.length === 0)
18916
+ return NONE3;
18917
+ if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
18918
+ return NONE3;
18919
+ const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
18920
+ return {
18921
+ kind: "deny",
18922
+ reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
18923
+ unblock: gate.blockers[0].reason
18924
+ };
18925
+ } catch {
18926
+ return NONE3;
18927
+ }
18928
+ }
18598
18929
  // packages/workit-core/src/hooks/stop.ts
18930
+ import { spawnSync as spawnSync13 } from "node:child_process";
18599
18931
  var NONE4 = { kind: "none" };
18600
18932
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
18601
18933
  var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
@@ -18616,7 +18948,7 @@ var asksUser = (message) => {
18616
18948
  };
18617
18949
  var short = (sha) => (sha ?? "?").slice(0, 12);
18618
18950
  var gitList = (cwd, args) => {
18619
- const run = spawnSync12("git", args, {
18951
+ const run = spawnSync13("git", args, {
18620
18952
  cwd,
18621
18953
  encoding: "utf8",
18622
18954
  timeout: 3000,
@@ -18979,13 +19311,13 @@ ${line}` };
18979
19311
  return decision;
18980
19312
  };
18981
19313
  // packages/workit-core/src/hooks/hosts/fields.ts
18982
- import { existsSync as existsSync6, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
18983
- import path20 from "node:path";
19314
+ import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
19315
+ import path22 from "node:path";
18984
19316
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
18985
19317
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
18986
19318
  var optionalText = (value) => typeof value === "string" ? value : null;
18987
19319
  var existingDirectory = (value) => {
18988
- if (!nonEmpty2(value) || !path20.isAbsolute(value) || !existsSync6(value))
19320
+ if (!nonEmpty2(value) || !path22.isAbsolute(value) || !existsSync7(value))
18989
19321
  return null;
18990
19322
  try {
18991
19323
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -19074,17 +19406,20 @@ var subagentStartText = (input, descriptor, event) => {
19074
19406
  const role = workitRole(descriptor, event.agentType);
19075
19407
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
19076
19408
  const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
19409
+ const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
19077
19410
  if (role === "implementer" && isolated)
19078
- return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} Use a policy-compliant branch (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
19411
+ return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
19079
19412
  if (isolated)
19080
19413
  return `${ISOLATED_GIT}
19081
19414
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
19082
19415
  if (role === "implementer")
19083
- return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
19416
+ return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
19084
19417
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
19085
19418
  if (role !== "judge" || !event.agentId)
19086
19419
  return readOnly;
19087
19420
  const own = subagentSession(session, event.agentId);
19421
+ if (session)
19422
+ recordIssuedSession(input.cwd || process.cwd(), session, own);
19088
19423
  return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> --session ${own} ...\` so the ledger tells it apart from the author.`;
19089
19424
  };
19090
19425
  function handleHook(input, deps) {
@@ -19118,7 +19453,7 @@ function handleHook(input, deps) {
19118
19453
  if (skillRead)
19119
19454
  recordSkillLoad(input, skillRead, "read");
19120
19455
  const canDeny = usable2(descriptor.shellPolicy.deny);
19121
- const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
19456
+ const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
19122
19457
  if (policy.kind !== "none")
19123
19458
  return policy;
19124
19459
  const postTool = usable2(descriptor.events["shell.post"].support);
@@ -19716,12 +20051,12 @@ var codexAdapter = {
19716
20051
  };
19717
20052
  },
19718
20053
  render: render2,
19719
- addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under its own session, never the author's: a workit-verifier or workit-reviewer agent uses the --session its SubagentStart context names; any other uses one the lead assigns (WORKIT_HOST=codex_cli WORKIT_SESSION_ID=<lead>-v<n>). Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`
20054
+ addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under its own session, never the author's: a workit-verifier or workit-reviewer agent uses the --session its SubagentStart context names; any other passes --as verifier (an id derived from its own thread). A hand-minted id such as <lead>-v1 is refused as forged_session. Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`
19720
20055
  };
19721
20056
 
19722
20057
  // packages/workit-core/src/hooks/hosts/cursor.ts
19723
- import fs16 from "node:fs";
19724
- import path21 from "node:path";
20058
+ import fs17 from "node:fs";
20059
+ import path23 from "node:path";
19725
20060
  var undocumented = { support: "undocumented", native: null };
19726
20061
  var none = { support: "none", native: null };
19727
20062
  var CURSOR_DESCRIPTOR = {
@@ -19918,17 +20253,17 @@ var protocolEvent2 = (input) => {
19918
20253
  };
19919
20254
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
19920
20255
  var lastAssistantText = (file) => {
19921
- if (!file || !path21.isAbsolute(file))
20256
+ if (!file || !path23.isAbsolute(file))
19922
20257
  return null;
19923
20258
  try {
19924
- const size = fs16.statSync(file).size;
20259
+ const size = fs17.statSync(file).size;
19925
20260
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
19926
20261
  const buffer = Buffer.alloc(size - start);
19927
- const fd = fs16.openSync(file, "r");
20262
+ const fd = fs17.openSync(file, "r");
19928
20263
  try {
19929
- fs16.readSync(fd, buffer, 0, buffer.length, start);
20264
+ fs17.readSync(fd, buffer, 0, buffer.length, start);
19930
20265
  } finally {
19931
- fs16.closeSync(fd);
20266
+ fs17.closeSync(fd);
19932
20267
  }
19933
20268
  const lines = buffer.toString("utf8").split(`
19934
20269
  `);
@@ -19955,7 +20290,7 @@ var lastAssistantText = (file) => {
19955
20290
  return null;
19956
20291
  }
19957
20292
  };
19958
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path21.sep) ? root : `${root}${path21.sep}`);
20293
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path23.sep) ? root : `${root}${path23.sep}`);
19959
20294
  var hookCwd = (input) => {
19960
20295
  const cwd = existingDirectory(input.cwd);
19961
20296
  if (cwd && input.hook_event_name === "beforeShellExecution")