@brainervirus/workit-cli 8.11.0 → 8.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1653 -721
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "@brainervirus/workit-cli",
68
- version: "8.11.0",
68
+ version: "8.12.1",
69
69
  private: false,
70
70
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
71
71
  keywords: [
@@ -103,9 +103,9 @@ var init_package = __esm(() => {
103
103
  },
104
104
  dependencies: {
105
105
  "@babel/parser": "7.29.9",
106
- "@brainervirus/workit-core": "^8.11.0",
107
- "@brainervirus/workit-cursor": "^8.11.0",
108
- "@brainervirus/workit-opencode": "^8.11.0",
106
+ "@brainervirus/workit-core": "^8.12.1",
107
+ "@brainervirus/workit-cursor": "^8.12.1",
108
+ "@brainervirus/workit-opencode": "^8.12.1",
109
109
  "@inkjs/ui": "2.0.0",
110
110
  ink: "7.1.1",
111
111
  react: "19.2.8"
@@ -298,7 +298,7 @@ function loadCheckConfig(cwd) {
298
298
  gates[gate] = names;
299
299
  }
300
300
  }
301
- return { source: CHECKS_FILE, root, checks, gates, error: null };
301
+ return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
302
302
  }
303
303
  function matchesNamedCheck(config, name, argv, cwd) {
304
304
  if (config.error || name === null || cwd !== ".")
@@ -9939,11 +9939,224 @@ var init_file_lock = __esm(() => {
9939
9939
  init_file_lock_sync();
9940
9940
  });
9941
9941
 
9942
+ // packages/workit-core/src/core/logger.ts
9943
+ import { appendFileSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
9944
+ import os from "node:os";
9945
+ import path4 from "node:path";
9946
+ var REDACTED = "[REDACTED]", DEFAULT_MAX_RATE = 20, DEFAULT_RATE_WINDOW_MS = 1000, DEFAULT_MAX_FIELD_LENGTH = 200, DEFAULT_MAX_STACK_LINES = 30, DAY_FILE, RETAINED_DAYS = 7, SECRET_VALUE, KEY_EQ_VALUE, URL_QUERY, splitKey = (key) => key.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/).map((word) => word.toLowerCase()), SENSITIVE_WORDS, STACK_WORDS, escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), resolveStateDir = () => {
9947
+ const override = process.env.WORKFLOW_TOOLKIT_STATE;
9948
+ if (override)
9949
+ return override;
9950
+ const home = os.homedir();
9951
+ if (process.env.XDG_STATE_HOME)
9952
+ return path4.join(process.env.XDG_STATE_HOME, "workit");
9953
+ switch (os.platform()) {
9954
+ case "darwin":
9955
+ return path4.join(home, "Library", "Application Support", "workit");
9956
+ case "win32":
9957
+ return path4.join(process.env.LOCALAPPDATA ?? path4.join(home, "AppData", "Local"), "workit");
9958
+ default:
9959
+ return path4.join(process.env.HOME ?? home, ".local", "state", "workit");
9960
+ }
9961
+ }, homePattern, applyPatterns = (value) => {
9962
+ let out = value;
9963
+ out = out.replace(SECRET_VALUE, REDACTED);
9964
+ out = out.replace(KEY_EQ_VALUE, "$1$2[REDACTED]");
9965
+ out = out.replace(URL_QUERY, "$1?[REDACTED]");
9966
+ out = out.replace(homePattern, "~");
9967
+ if (process.platform === "win32")
9968
+ out = out.replace(/\\/g, "/");
9969
+ return out;
9970
+ }, redactString = (value, options) => {
9971
+ const out = applyPatterns(value);
9972
+ if (out.length > options.maxFieldLength) {
9973
+ return `${out.slice(0, options.maxFieldLength)}…`;
9974
+ }
9975
+ return out;
9976
+ }, boundStack = (value, options) => {
9977
+ if (typeof value !== "string")
9978
+ return redact(value, options);
9979
+ const lines = value.split(`
9980
+ `);
9981
+ const kept = lines.slice(0, options.maxStackLines).map(applyPatterns);
9982
+ const dropped = lines.length - kept.length;
9983
+ const body = dropped > 0 ? [...kept, ` ... ${dropped} more`] : kept;
9984
+ return body.join(`
9985
+ `);
9986
+ }, redact = (value, options = DEFAULT_OPTIONS) => {
9987
+ if (value === null || value === undefined)
9988
+ return null;
9989
+ if (typeof value === "boolean" || typeof value === "number")
9990
+ return value;
9991
+ if (typeof value === "string")
9992
+ return redactString(value, options);
9993
+ if (value instanceof Date)
9994
+ return value.toISOString();
9995
+ if (Array.isArray(value))
9996
+ return value.map((entry) => redact(entry, options));
9997
+ if (typeof value === "object") {
9998
+ const out = {};
9999
+ for (const [key, entry] of Object.entries(value)) {
10000
+ const words = splitKey(key);
10001
+ if (words.some((word) => STACK_WORDS.has(word)))
10002
+ out[key] = boundStack(entry, options);
10003
+ else if (words.some((word) => SENSITIVE_WORDS.has(word)))
10004
+ out[key] = REDACTED;
10005
+ else
10006
+ out[key] = redact(entry, options);
10007
+ }
10008
+ return out;
10009
+ }
10010
+ return null;
10011
+ }, DEFAULT_OPTIONS, dailyFileName = (date) => {
10012
+ const y = date.getFullYear();
10013
+ const m = String(date.getMonth() + 1).padStart(2, "0");
10014
+ const d = String(date.getDate()).padStart(2, "0");
10015
+ return `workit-${y}-${m}-${d}.jsonl`;
10016
+ }, pruneOldFiles = (dir) => {
10017
+ try {
10018
+ const files = readdirSync(dir).filter((name) => DAY_FILE.test(name)).toSorted().toReversed();
10019
+ for (const file of files.slice(RETAINED_DAYS)) {
10020
+ try {
10021
+ unlinkSync(path4.join(dir, file));
10022
+ } catch {}
10023
+ }
10024
+ } catch {}
10025
+ }, createLogger = (options = {}) => {
10026
+ const stateDir = options.stateDir ?? resolveStateDir();
10027
+ const logDir = path4.join(stateDir, "logs");
10028
+ const now = options.now ?? (() => new Date);
10029
+ const maxRate = options.maxRate ?? DEFAULT_MAX_RATE;
10030
+ const rateWindowMs = options.rateWindowMs ?? DEFAULT_RATE_WINDOW_MS;
10031
+ const redactOptions = {
10032
+ maxFieldLength: options.maxFieldLength ?? DEFAULT_MAX_FIELD_LENGTH,
10033
+ maxStackLines: options.maxStackLines ?? DEFAULT_MAX_STACK_LINES
10034
+ };
10035
+ let windowStart = 0;
10036
+ const windowCounts = { debug: 0, info: 0, warn: 0, error: 0 };
10037
+ const emit = (level, message, context) => {
10038
+ const date = now();
10039
+ const elapsed = date.getTime() - windowStart;
10040
+ if (elapsed >= rateWindowMs || windowStart === 0) {
10041
+ windowStart = date.getTime();
10042
+ windowCounts.debug = 0;
10043
+ windowCounts.info = 0;
10044
+ windowCounts.warn = 0;
10045
+ windowCounts.error = 0;
10046
+ }
10047
+ windowCounts[level] += 1;
10048
+ if (windowCounts[level] > maxRate)
10049
+ return;
10050
+ let event = null;
10051
+ try {
10052
+ event = {
10053
+ level,
10054
+ time: date.toISOString(),
10055
+ message: redactString(message, redactOptions),
10056
+ context: redact(context ?? {}, redactOptions)
10057
+ };
10058
+ const line = `${JSON.stringify(event)}
10059
+ `;
10060
+ mkdirSync(logDir, { recursive: true, mode: 448 });
10061
+ appendFileSync(path4.join(logDir, dailyFileName(date)), line, { mode: 384 });
10062
+ pruneOldFiles(logDir);
10063
+ } catch {
10064
+ try {
10065
+ event = {
10066
+ level,
10067
+ time: date.toISOString(),
10068
+ message: redactString(message, redactOptions),
10069
+ context: { redaction_failed: true }
10070
+ };
10071
+ mkdirSync(logDir, { recursive: true, mode: 448 });
10072
+ appendFileSync(path4.join(logDir, dailyFileName(date)), `${JSON.stringify(event)}
10073
+ `, {
10074
+ mode: 384
10075
+ });
10076
+ } catch {}
10077
+ }
10078
+ for (const sink of [options.appLog, options.stderr]) {
10079
+ if (!sink)
10080
+ continue;
10081
+ if (!event)
10082
+ continue;
10083
+ try {
10084
+ sink(event);
10085
+ } catch {}
10086
+ }
10087
+ };
10088
+ return {
10089
+ debug: (message, context) => emit("debug", message, context),
10090
+ info: (message, context) => emit("info", message, context),
10091
+ warn: (message, context) => emit("warn", message, context),
10092
+ error: (message, context) => emit("error", message, context),
10093
+ guard: (name, fn) => {
10094
+ try {
10095
+ return fn();
10096
+ } catch (err) {
10097
+ emit("warn", "detector_failed", {
10098
+ detector: name,
10099
+ error: err instanceof Error ? err.message : String(err)
10100
+ });
10101
+ return;
10102
+ }
10103
+ }
10104
+ };
10105
+ };
10106
+ var init_logger = __esm(() => {
10107
+ DAY_FILE = /^workit-\d{4}-\d{2}-\d{2}\.jsonl$/;
10108
+ SECRET_VALUE = /\b(?:Bearer|Basic|Digest|Token)\s+\S+/gi;
10109
+ KEY_EQ_VALUE = /\b([A-Za-z0-9_-]*(?:token|secret|password|passwd|apikey|api[_-]?key|authorization|credential|bearer)[A-Za-z0-9_-]*)([:=]\s*).+/gi;
10110
+ URL_QUERY = /(https?:\/\/[^?#\s]+)\?[^#\s]*/g;
10111
+ SENSITIVE_WORDS = new Set([
10112
+ "token",
10113
+ "secret",
10114
+ "password",
10115
+ "passwd",
10116
+ "authorization",
10117
+ "credential",
10118
+ "credentials",
10119
+ "cookie",
10120
+ "api",
10121
+ "apikey",
10122
+ "apitoken",
10123
+ "accesstoken",
10124
+ "clientsecret",
10125
+ "refreshtoken",
10126
+ "key",
10127
+ "bearer",
10128
+ "prompt",
10129
+ "message",
10130
+ "messages",
10131
+ "content",
10132
+ "body",
10133
+ "args",
10134
+ "argument",
10135
+ "result",
10136
+ "results",
10137
+ "output",
10138
+ "issue",
10139
+ "description",
10140
+ "summary",
10141
+ "payload",
10142
+ "script",
10143
+ "command",
10144
+ "text",
10145
+ "query"
10146
+ ]);
10147
+ STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
10148
+ homePattern = new RegExp(`${escapeRegExp(os.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
10149
+ DEFAULT_OPTIONS = {
10150
+ maxFieldLength: DEFAULT_MAX_FIELD_LENGTH,
10151
+ maxStackLines: DEFAULT_MAX_STACK_LINES
10152
+ };
10153
+ });
10154
+
9942
10155
  // packages/workit-core/src/store/paths.ts
9943
10156
  import { spawnSync as spawnSync2 } from "node:child_process";
9944
10157
  import { createHash as createHash2 } from "node:crypto";
9945
10158
  import fs5 from "node:fs";
9946
- import path4 from "node:path";
10159
+ import path5 from "node:path";
9947
10160
  function fastGit(root) {
9948
10161
  if (DISCOVERY_ENV.some((name) => process.env[name]))
9949
10162
  return null;
@@ -9959,7 +10172,7 @@ function fastGit(root) {
9959
10172
  if (device !== null && stat.dev !== device)
9960
10173
  return { kind: "none" };
9961
10174
  device = stat.dev;
9962
- const dotGit = path4.join(dir, ".git");
10175
+ const dotGit = path5.join(dir, ".git");
9963
10176
  let entry = null;
9964
10177
  try {
9965
10178
  entry = fs5.statSync(dotGit);
@@ -9980,19 +10193,19 @@ function fastGit(root) {
9980
10193
  const match = /^gitdir: (.+)$/mu.exec(text);
9981
10194
  if (!match)
9982
10195
  return null;
9983
- gitDir = path4.resolve(dir, match[1].trim());
10196
+ gitDir = path5.resolve(dir, match[1].trim());
9984
10197
  } else
9985
10198
  return null;
9986
- if (!fs5.existsSync(path4.join(gitDir, "HEAD")))
10199
+ if (!fs5.existsSync(path5.join(gitDir, "HEAD")))
9987
10200
  return null;
9988
10201
  } else if (isGitDir(dir))
9989
10202
  gitDir = dir;
9990
10203
  if (gitDir) {
9991
10204
  let common = gitDir;
9992
10205
  try {
9993
- common = path4.resolve(gitDir, fs5.readFileSync(path4.join(gitDir, "commondir"), "utf8").trim());
10206
+ common = path5.resolve(gitDir, fs5.readFileSync(path5.join(gitDir, "commondir"), "utf8").trim());
9994
10207
  } catch {}
9995
- if (!fs5.existsSync(path4.join(common, "objects")))
10208
+ if (!fs5.existsSync(path5.join(common, "objects")))
9996
10209
  return null;
9997
10210
  return {
9998
10211
  kind: "repo",
@@ -10002,26 +10215,26 @@ function fastGit(root) {
10002
10215
  reftable: reftableRefs(common)
10003
10216
  };
10004
10217
  }
10005
- const parent = path4.dirname(dir);
10218
+ const parent = path5.dirname(dir);
10006
10219
  if (parent === dir)
10007
10220
  return { kind: "none" };
10008
10221
  dir = parent;
10009
10222
  }
10010
10223
  }
10011
10224
  function resolveStore(root) {
10012
- root = path4.resolve(root);
10225
+ root = path5.resolve(root);
10013
10226
  let directory = false;
10014
10227
  try {
10015
10228
  directory = fs5.statSync(root).isDirectory();
10016
10229
  } catch {}
10017
10230
  if (!directory)
10018
- return { dir: path4.join(root, ".workit"), shared: false, top: null };
10231
+ return { dir: path5.join(root, ".workit"), shared: false, top: null };
10019
10232
  const fast = fastGit(root);
10020
10233
  if (fast?.kind === "none")
10021
- return { dir: path4.join(root, ".workit"), shared: false, top: null };
10234
+ return { dir: path5.join(root, ".workit"), shared: false, top: null };
10022
10235
  if (fast?.kind === "repo")
10023
10236
  return {
10024
- dir: path4.join(fast.common, "workit"),
10237
+ dir: path5.join(fast.common, "workit"),
10025
10238
  shared: true,
10026
10239
  top: fast.top,
10027
10240
  git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
@@ -10031,13 +10244,13 @@ function resolveStore(root) {
10031
10244
  if (run.ok && common) {
10032
10245
  const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
10033
10246
  return {
10034
- dir: path4.join(real(path4.resolve(root, common)), "workit"),
10247
+ dir: path5.join(real(path5.resolve(root, common)), "workit"),
10035
10248
  shared: true,
10036
10249
  top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
10037
10250
  };
10038
10251
  }
10039
10252
  if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
10040
- return { dir: path4.join(root, ".workit"), shared: false, top: null };
10253
+ return { dir: path5.join(root, ".workit"), shared: false, top: null };
10041
10254
  return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
10042
10255
  `)[0] || "no output"}); fix the repository (git status should work here)`);
10043
10256
  }
@@ -10048,7 +10261,7 @@ function resolveTaskKey(root, location) {
10048
10261
  if (location.git && !location.git.reftable) {
10049
10262
  let head = "";
10050
10263
  try {
10051
- head = fs5.readFileSync(path4.join(location.git.gitDir, "HEAD"), "utf8").trim();
10264
+ head = fs5.readFileSync(path5.join(location.git.gitDir, "HEAD"), "utf8").trim();
10052
10265
  } catch {}
10053
10266
  const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
10054
10267
  if (ref && ref[1] !== ".invalid")
@@ -10079,7 +10292,7 @@ function resolveTaskKey(root, location) {
10079
10292
  return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
10080
10293
  }
10081
10294
  function checkoutRootOf(root, location) {
10082
- const resolved = path4.resolve(root);
10295
+ const resolved = path5.resolve(root);
10083
10296
  let canonical = resolved;
10084
10297
  try {
10085
10298
  canonical = fs5.realpathSync(resolved);
@@ -10110,13 +10323,13 @@ function branchRenames(location) {
10110
10323
  }
10111
10324
  for (const entry of entries)
10112
10325
  if (entry.isDirectory())
10113
- walk(path4.join(dir, entry.name));
10326
+ walk(path5.join(dir, entry.name));
10114
10327
  else
10115
- files.push(path4.join(dir, entry.name));
10328
+ files.push(path5.join(dir, entry.name));
10116
10329
  };
10117
- walk(path4.join(path4.dirname(location.dir), "logs", "refs", "heads"));
10330
+ walk(path5.join(path5.dirname(location.dir), "logs", "refs", "heads"));
10118
10331
  if (location.git)
10119
- files.push(path4.join(location.git.gitDir, "logs", "HEAD"));
10332
+ files.push(path5.join(location.git.gitDir, "logs", "HEAD"));
10120
10333
  const seen = new Set;
10121
10334
  const renames = [];
10122
10335
  for (const file of files) {
@@ -10177,20 +10390,20 @@ var NOT_A_REPO, gitRun = (cwd, args) => {
10177
10390
  }
10178
10391
  }, DISCOVERY_ENV, isGitDir = (dir) => {
10179
10392
  try {
10180
- return fs5.statSync(path4.join(dir, "HEAD")).isFile() && fs5.statSync(path4.join(dir, "objects")).isDirectory() && fs5.statSync(path4.join(dir, "refs")).isDirectory();
10393
+ return fs5.statSync(path5.join(dir, "HEAD")).isFile() && fs5.statSync(path5.join(dir, "objects")).isDirectory() && fs5.statSync(path5.join(dir, "refs")).isDirectory();
10181
10394
  } catch {
10182
10395
  return false;
10183
10396
  }
10184
10397
  }, reftableRefs = (common) => {
10185
10398
  try {
10186
- return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs5.readFileSync(path4.join(common, "config"), "utf8"));
10399
+ return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs5.readFileSync(path5.join(common, "config"), "utf8"));
10187
10400
  } catch {
10188
10401
  return false;
10189
10402
  }
10190
10403
  }, rebasingBranch = (gitDir) => {
10191
10404
  for (const state of ["rebase-merge", "rebase-apply"]) {
10192
10405
  try {
10193
- const ref = fs5.readFileSync(path4.join(gitDir, state, "head-name"), "utf8").trim();
10406
+ const ref = fs5.readFileSync(path5.join(gitDir, state, "head-name"), "utf8").trim();
10194
10407
  const match = /^refs\/heads\/(.+)$/u.exec(ref);
10195
10408
  if (match)
10196
10409
  return match[1];
@@ -10201,7 +10414,7 @@ var NOT_A_REPO, gitRun = (cwd, args) => {
10201
10414
  let canonical = real(root);
10202
10415
  if (process.platform === "win32")
10203
10416
  canonical = canonical.toLowerCase();
10204
- const base = path4.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
10417
+ const base = path5.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
10205
10418
  return `${base}-${shortHash(canonical)}`;
10206
10419
  };
10207
10420
  var init_paths = __esm(() => {
@@ -10218,7 +10431,7 @@ var init_paths = __esm(() => {
10218
10431
  // packages/workit-core/src/host-session.ts
10219
10432
  import { createHash as createHash3 } from "node:crypto";
10220
10433
  import fs6 from "node:fs";
10221
- import path5 from "node:path";
10434
+ import path6 from "node:path";
10222
10435
  function hostSessionFromEnv(env) {
10223
10436
  const value = (key) => env[key]?.trim() || null;
10224
10437
  let inner = null;
@@ -10247,6 +10460,36 @@ function realHostSession(env) {
10247
10460
  const hosted = declared.host !== null && declared.host !== "cli";
10248
10461
  return { ...declared, kind: hosted ? "declared" : "none" };
10249
10462
  }
10463
+ function migrateRepoIssued(cwd) {
10464
+ if (cwd === undefined)
10465
+ return;
10466
+ let old;
10467
+ try {
10468
+ const location = resolveStore(cwd);
10469
+ if (location instanceof Error)
10470
+ return;
10471
+ old = path6.join(location.dir, "hooks", "issued");
10472
+ if (!fs6.existsSync(old))
10473
+ return;
10474
+ } catch {
10475
+ return;
10476
+ }
10477
+ for (const lead of safeList(old)) {
10478
+ try {
10479
+ const from = path6.join(old, lead);
10480
+ if (!fs6.statSync(from).isDirectory())
10481
+ continue;
10482
+ const to = path6.join(userIssuedRoot(), lead);
10483
+ fs6.mkdirSync(to, { recursive: true, mode: 448 });
10484
+ for (const id of fs6.readdirSync(from)) {
10485
+ fs6.copyFileSync(path6.join(from, id), path6.join(to, id));
10486
+ fs6.rmSync(path6.join(from, id), { force: true });
10487
+ }
10488
+ removeIfEmpty(from);
10489
+ } catch {}
10490
+ }
10491
+ removeIfEmpty(old);
10492
+ }
10250
10493
  function sessionClaimRefusal(env, claimed, cwd) {
10251
10494
  const real = realHostSession(env);
10252
10495
  const host = family(real.host);
@@ -10271,9 +10514,9 @@ function sessionClaimRefusal(env, claimed, cwd) {
10271
10514
  issuedClaim = true;
10272
10515
  }
10273
10516
  const error = `forged_session: ${claimed} is neither this shell's session (${real.session}) nor an id its host issued to a child of it`;
10274
- if (issuedClaim && noIssuedRecord(cwd) && host !== null && PLUGIN_UPDATE[host])
10517
+ if (issuedClaim && noIssuedRecord(cwd, leadOf(claimed)) && host !== null && PLUGIN_UPDATE[host])
10275
10518
  return {
10276
- error: `${error}; no SubagentStart hook in this workspace has recorded an issued id`,
10519
+ error: `${error}; no SubagentStart hook has recorded an issued id for this lead`,
10277
10520
  unblock: `the workit plugin hook is probably older than this CLI and does not record the ids it issues: update the plugin (${PLUGIN_UPDATE[host]}), then relaunch the verifier so its SubagentStart runs the new hook (workit doctor checks this as hook_issued_sessions)`
10278
10521
  };
10279
10522
  const ownChildShell = host !== null && OWN_CHILD_SHELL.has(host);
@@ -10282,32 +10525,35 @@ function sessionClaimRefusal(env, claimed, cwd) {
10282
10525
  unblock: ownChildShell ? `record the verdict from the verifier's own ${host === "pi" ? "Pi worker" : "OpenCode child session"} shell, where WORKIT_SESSION_ID is already set; never set WORKIT_SESSION_ID or --session by hand` : "never name a session yourself: spawn a verifier or reviewer subagent and pass exactly the --session its SubagentStart context names, or pass --as verifier"
10283
10526
  };
10284
10527
  }
10285
- var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, hash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 32), issuedRoot = (cwd) => {
10286
- const location = resolveStore(cwd);
10287
- return location instanceof Error ? null : path5.join(location.dir, "hooks", "issued");
10288
- }, issuedDir = (cwd, lead) => {
10289
- const root = issuedRoot(cwd);
10290
- return root === null ? null : path5.join(root, hash(lead));
10291
- }, noIssuedRecord = (cwd) => {
10528
+ var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, hash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path6.join(resolveStateDir(), "issued"), safeList = (dir) => {
10529
+ try {
10530
+ return fs6.readdirSync(dir);
10531
+ } catch {
10532
+ return [];
10533
+ }
10534
+ }, removeIfEmpty = (dir) => {
10535
+ try {
10536
+ fs6.rmdirSync(dir);
10537
+ } catch {}
10538
+ }, noIssuedRecord = (cwd, lead) => {
10292
10539
  if (cwd === undefined)
10293
10540
  return false;
10294
10541
  try {
10295
- const root = issuedRoot(cwd);
10296
- return root !== null && !fs6.existsSync(root);
10542
+ migrateRepoIssued(cwd);
10543
+ return !fs6.existsSync(path6.join(userIssuedRoot(), hash(lead)));
10297
10544
  } catch {
10298
10545
  return false;
10299
10546
  }
10300
10547
  }, PLUGIN_UPDATE, wasIssued = (cwd, lead, claimed) => {
10301
- if (cwd === undefined)
10302
- return false;
10548
+ migrateRepoIssued(cwd);
10303
10549
  try {
10304
- const dir = issuedDir(cwd, lead);
10305
- return dir !== null && fs6.existsSync(path5.join(dir, hash(claimed)));
10550
+ return fs6.existsSync(path6.join(userIssuedRoot(), hash(lead), hash(claimed)));
10306
10551
  } catch {
10307
10552
  return false;
10308
10553
  }
10309
10554
  }, leadOf = (session) => session.slice(0, session.lastIndexOf(":")), OWN_CHILD_SHELL, HOST_SET_VARIABLE;
10310
10555
  var init_host_session = __esm(() => {
10556
+ init_logger();
10311
10557
  init_paths();
10312
10558
  HOST_SESSION_VARS = [
10313
10559
  ["OPENCODE_SESSION_ID", () => "opencode"],
@@ -10331,19 +10577,19 @@ var init_host_session = __esm(() => {
10331
10577
 
10332
10578
  // packages/workit-core/src/core/package-root.ts
10333
10579
  import { existsSync } from "node:fs";
10334
- import path6 from "node:path";
10580
+ import path7 from "node:path";
10335
10581
  import { fileURLToPath } from "node:url";
10336
10582
  var packageRoot = () => {
10337
- let dir = path6.dirname(fileURLToPath(import.meta.url));
10583
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
10338
10584
  while (true) {
10339
- const parent = path6.dirname(dir);
10340
- if (existsSync(path6.join(dir, "package.json")) || parent === dir)
10585
+ const parent = path7.dirname(dir);
10586
+ if (existsSync(path7.join(dir, "package.json")) || parent === dir)
10341
10587
  return dir;
10342
10588
  dir = parent;
10343
10589
  }
10344
10590
  }, assetRoot = () => {
10345
10591
  const root = packageRoot();
10346
- const assets = path6.join(root, "assets");
10592
+ const assets = path7.join(root, "assets");
10347
10593
  return existsSync(assets) ? assets : root;
10348
10594
  };
10349
10595
  var init_package_root = () => {};
@@ -10352,7 +10598,7 @@ var init_package_root = () => {};
10352
10598
  import { spawnSync as spawnSync3 } from "node:child_process";
10353
10599
  import fs7 from "node:fs";
10354
10600
  import { hostname } from "node:os";
10355
- import path7 from "node:path";
10601
+ import path8 from "node:path";
10356
10602
  var metadataLockSchema, FOREIGN_LOCK_TTL_MS, UNREADABLE_LOCK_TTL_MS = 30000, RECLAIM_GUARD_TTL_MS = 30000, defaultLockTimeoutMs = 250, defaultLockTimeout = () => defaultLockTimeoutMs, setDefaultLockTimeout = (ms) => {
10357
10603
  defaultLockTimeoutMs = ms;
10358
10604
  }, parseMetadataLock = (raw) => {
@@ -10456,13 +10702,13 @@ var metadataLockSchema, FOREIGN_LOCK_TTL_MS, UNREADABLE_LOCK_TTL_MS = 30000, REC
10456
10702
  return null;
10457
10703
  }
10458
10704
  }, lockPathFor = (root) => {
10459
- let real = path7.resolve(root);
10705
+ let real = path8.resolve(root);
10460
10706
  try {
10461
10707
  real = fs7.realpathSync(real);
10462
10708
  } catch {}
10463
10709
  const location = resolveStore(real);
10464
- const dir = location instanceof Error ? path7.join(real, ".workit") : location.dir;
10465
- return path7.join(dir, "checkouts", checkoutSlug(real), "metadata.lock");
10710
+ const dir = location instanceof Error ? path8.join(real, ".workit") : location.dir;
10711
+ return path8.join(dir, "checkouts", checkoutSlug(real), "metadata.lock");
10466
10712
  }, clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
10467
10713
  const guard = `${lockPath}.reclaim`;
10468
10714
  const age = ageOf(guard, nowMs);
@@ -10549,7 +10795,7 @@ var init_store_lock = __esm(() => {
10549
10795
  // packages/workit-core/src/store/event-log.ts
10550
10796
  import { randomUUID as randomUUID2 } from "node:crypto";
10551
10797
  import fs8 from "node:fs";
10552
- import path8 from "node:path";
10798
+ import path9 from "node:path";
10553
10799
  function readLog(file, from = 0, expectSeq = null) {
10554
10800
  let fd;
10555
10801
  try {
@@ -10622,7 +10868,7 @@ function appendEvent(file, event, validEnd) {
10622
10868
  fs8.closeSync(fd);
10623
10869
  }
10624
10870
  if (created)
10625
- fsyncDirectory(path8.dirname(file));
10871
+ fsyncDirectory(path9.dirname(file));
10626
10872
  return validEnd;
10627
10873
  }
10628
10874
  function replaceFile(file, bytes, durable) {
@@ -10638,7 +10884,7 @@ function replaceFile(file, bytes, durable) {
10638
10884
  }
10639
10885
  renameWithRetry(temporary, file);
10640
10886
  if (durable)
10641
- fsyncDirectory(path8.dirname(file));
10887
+ fsyncDirectory(path9.dirname(file));
10642
10888
  } catch (error) {
10643
10889
  try {
10644
10890
  fs8.unlinkSync(temporary);
@@ -10948,7 +11194,7 @@ var init_notes = __esm(() => {
10948
11194
  // packages/workit-core/src/core/task-store.ts
10949
11195
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
10950
11196
  import * as fs9 from "node:fs";
10951
- import path9 from "node:path";
11197
+ import path10 from "node:path";
10952
11198
 
10953
11199
  class TaskStore {
10954
11200
  root;
@@ -10960,7 +11206,7 @@ class TaskStore {
10960
11206
  migrationChecked = false;
10961
11207
  compactDue = new Set;
10962
11208
  constructor(root, options = {}) {
10963
- const resolved = path9.resolve(root);
11209
+ const resolved = path10.resolve(root);
10964
11210
  this.origin = fs9.existsSync(resolved) ? fs9.realpathSync(resolved) : resolved;
10965
11211
  this.located = resolveStore(this.origin);
10966
11212
  this.root = checkoutRootOf(this.origin, this.located);
@@ -10969,7 +11215,7 @@ class TaskStore {
10969
11215
  this.migrateOnRead = options.migrateOnRead;
10970
11216
  }
10971
11217
  location() {
10972
- if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs9.existsSync(path9.join(this.root, ".git"))) {
11218
+ if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs9.existsSync(path10.join(this.root, ".git"))) {
10973
11219
  this.located = undefined;
10974
11220
  this.migrationChecked = false;
10975
11221
  }
@@ -11125,7 +11371,7 @@ class TaskStore {
11125
11371
  const moved = this.bindLocked(again.data.id, key, input.now);
11126
11372
  return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
11127
11373
  }
11128
- const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path9.basename(this.root) || this.root;
11374
+ const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path10.basename(this.root) || this.root;
11129
11375
  const intent = {
11130
11376
  objective: input.objective?.trim() || `Work on ${label}`,
11131
11377
  scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
@@ -11185,14 +11431,14 @@ class TaskStore {
11185
11431
  const branch = key.ok ? key.data.branch : null;
11186
11432
  if (!branch || !location.ok)
11187
11433
  return success(null, null, null);
11188
- const common = path9.dirname(location.data.dir);
11189
- const heads = path9.join(common, "logs", "refs", "heads");
11190
- const headLog = location.data.git ? path9.join(location.data.git.gitDir, "logs", "HEAD") : "";
11434
+ const common = path10.dirname(location.data.dir);
11435
+ const heads = path10.join(common, "logs", "refs", "heads");
11436
+ const headLog = location.data.git ? path10.join(location.data.git.gitDir, "logs", "HEAD") : "";
11191
11437
  const signatures = [
11192
- path9.join(heads, branch),
11438
+ path10.join(heads, branch),
11193
11439
  headLog,
11194
11440
  heads,
11195
- path9.join(common, "packed-refs")
11441
+ path10.join(common, "packed-refs")
11196
11442
  ].map((file) => fileSignature(file) ?? "-");
11197
11443
  const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
11198
11444
  let resolved = origins.get(slot);
@@ -11204,7 +11450,7 @@ class TaskStore {
11204
11450
  aliases.push(rename.from);
11205
11451
  const start = branchStart(location.data, branch, aliases);
11206
11452
  const slash = start?.indexOf("/") ?? -1;
11207
- 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)}"]`));
11453
+ const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path10.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
11208
11454
  const name = start && (remote ? start.slice(slash + 1) : start);
11209
11455
  resolved = {
11210
11456
  signature: signatures.join("|"),
@@ -11442,13 +11688,13 @@ class TaskStore {
11442
11688
  if (!ready.ok)
11443
11689
  return ready;
11444
11690
  const dirs = this.paths();
11445
- const recovery = path9.join(this.root, ".workit", "recovery");
11691
+ const recovery = path10.join(this.root, ".workit", "recovery");
11446
11692
  if (fs9.existsSync(recovery)) {
11447
11693
  let files = 0;
11448
11694
  let bytes = 0;
11449
11695
  try {
11450
11696
  for (const name of fs9.readdirSync(recovery)) {
11451
- const stat = fs9.lstatSync(path9.join(recovery, name));
11697
+ const stat = fs9.lstatSync(path10.join(recovery, name));
11452
11698
  if (!stat.isFile())
11453
11699
  continue;
11454
11700
  files += 1;
@@ -11501,7 +11747,7 @@ class TaskStore {
11501
11747
  if (fs9.existsSync(dirs.blobs))
11502
11748
  for (const name of fs9.readdirSync(dirs.blobs)) {
11503
11749
  const digest = name.replace(/\.json$/, "");
11504
- const file = path9.join(dirs.blobs, name);
11750
+ const file = path10.join(dirs.blobs, name);
11505
11751
  let stat;
11506
11752
  try {
11507
11753
  stat = fs9.lstatSync(file);
@@ -11519,7 +11765,7 @@ class TaskStore {
11519
11765
  }
11520
11766
  return success(null, null, null);
11521
11767
  };
11522
- const swept = dryRun ? sweep() : this.withLock(path9.join(dirs.dir, "gc.lock"), sweep);
11768
+ const swept = dryRun ? sweep() : this.withLock(path10.join(dirs.dir, "gc.lock"), sweep);
11523
11769
  if (!swept.ok)
11524
11770
  return swept;
11525
11771
  const temporaryDirs = [
@@ -11527,7 +11773,7 @@ class TaskStore {
11527
11773
  dirs.tasks,
11528
11774
  dirs.blobs,
11529
11775
  dirs.checkout,
11530
- ...names.map((name) => path9.join(dirs.tasks, name))
11776
+ ...names.map((name) => path10.join(dirs.tasks, name))
11531
11777
  ];
11532
11778
  for (const directory of temporaryDirs) {
11533
11779
  if (!fs9.existsSync(directory))
@@ -11535,7 +11781,7 @@ class TaskStore {
11535
11781
  for (const name of fs9.readdirSync(directory)) {
11536
11782
  if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
11537
11783
  continue;
11538
- const file = path9.join(directory, name);
11784
+ const file = path10.join(directory, name);
11539
11785
  try {
11540
11786
  const stat = fs9.lstatSync(file);
11541
11787
  if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
@@ -11552,30 +11798,30 @@ class TaskStore {
11552
11798
  }
11553
11799
  paths() {
11554
11800
  const location = this.location();
11555
- const dir = location.ok ? location.data.dir : path9.join(this.root, ".workit");
11556
- const checkout = path9.join(dir, "checkouts", checkoutSlug(this.root));
11801
+ const dir = location.ok ? location.data.dir : path10.join(this.root, ".workit");
11802
+ const checkout = path10.join(dir, "checkouts", checkoutSlug(this.root));
11557
11803
  return {
11558
11804
  dir,
11559
11805
  shared: location.ok ? location.data.shared : false,
11560
- marker: path9.join(dir, "store.json"),
11806
+ marker: path10.join(dir, "store.json"),
11561
11807
  checkout,
11562
- workspace: path9.join(checkout, "workspace.json"),
11563
- checkoutLock: path9.join(checkout, "metadata.lock"),
11564
- tasks: path9.join(dir, "tasks"),
11565
- index: path9.join(dir, "task-index.json"),
11566
- blobs: path9.join(dir, "blobs", "candidates"),
11567
- legacy: path9.join(dir, "legacy", checkoutSlug(this.root), "v2"),
11568
- v2: path9.join(this.root, ".workit")
11808
+ workspace: path10.join(checkout, "workspace.json"),
11809
+ checkoutLock: path10.join(checkout, "metadata.lock"),
11810
+ tasks: path10.join(dir, "tasks"),
11811
+ index: path10.join(dir, "task-index.json"),
11812
+ blobs: path10.join(dir, "blobs", "candidates"),
11813
+ legacy: path10.join(dir, "legacy", checkoutSlug(this.root), "v2"),
11814
+ v2: path10.join(this.root, ".workit")
11569
11815
  };
11570
11816
  }
11571
11817
  eventsPath(taskId) {
11572
- return path9.join(this.paths().tasks, taskId, "events.jsonl");
11818
+ return path10.join(this.paths().tasks, taskId, "events.jsonl");
11573
11819
  }
11574
11820
  snapshotPath(taskId) {
11575
- return path9.join(this.paths().tasks, taskId, "snapshot.json");
11821
+ return path10.join(this.paths().tasks, taskId, "snapshot.json");
11576
11822
  }
11577
11823
  taskLockPath(taskId) {
11578
- return path9.join(this.paths().tasks, taskId, "lock");
11824
+ return path10.join(this.paths().tasks, taskId, "lock");
11579
11825
  }
11580
11826
  ready(write = false) {
11581
11827
  const location = this.location();
@@ -11614,13 +11860,13 @@ class TaskStore {
11614
11860
  return {
11615
11861
  read: (digest) => {
11616
11862
  try {
11617
- return JSON.parse(fs9.readFileSync(path9.join(dir, `${digest}.json`), "utf8"));
11863
+ return JSON.parse(fs9.readFileSync(path10.join(dir, `${digest}.json`), "utf8"));
11618
11864
  } catch (error) {
11619
11865
  throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
11620
11866
  }
11621
11867
  },
11622
11868
  write: (digest, value) => {
11623
- const file = path9.join(dir, `${digest}.json`);
11869
+ const file = path10.join(dir, `${digest}.json`);
11624
11870
  if (fs9.existsSync(file)) {
11625
11871
  const stamp = new Date;
11626
11872
  try {
@@ -11812,7 +12058,7 @@ class TaskStore {
11812
12058
  }
11813
12059
  }
11814
12060
  openTask(record, type, key, legacy = null) {
11815
- const dir = path9.join(this.paths().tasks, record.id);
12061
+ const dir = path10.join(this.paths().tasks, record.id);
11816
12062
  fs9.mkdirSync(dir, { recursive: true });
11817
12063
  const blobs = this.blobStore();
11818
12064
  const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
@@ -11905,7 +12151,7 @@ class TaskStore {
11905
12151
  }
11906
12152
  withKeyLock(key, operation) {
11907
12153
  const digest = createHash5("sha256").update(key).digest("hex").slice(0, 32);
11908
- return this.withLock(path9.join(this.paths().dir, "keys", `${digest}.lock`), operation);
12154
+ return this.withLock(path10.join(this.paths().dir, "keys", `${digest}.lock`), operation);
11909
12155
  }
11910
12156
  bindLocked(taskId, key, timestamp) {
11911
12157
  const current = this.readWorkspace();
@@ -12046,7 +12292,7 @@ class TaskStore {
12046
12292
  writeWorkspace(record) {
12047
12293
  const file = this.paths().workspace;
12048
12294
  try {
12049
- fs9.mkdirSync(path9.dirname(file), { recursive: true });
12295
+ fs9.mkdirSync(path10.dirname(file), { recursive: true });
12050
12296
  replaceFile(file, `${canonicalJson(record)}
12051
12297
  `, true);
12052
12298
  return success(null, null, null);
@@ -12124,23 +12370,23 @@ class TaskStore {
12124
12370
  return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
12125
12371
  }
12126
12372
  v2Dirs() {
12127
- return [...new Set([this.root, this.origin])].map((dir) => path9.join(dir, ".workit"));
12373
+ return [...new Set([this.root, this.origin])].map((dir) => path10.join(dir, ".workit"));
12128
12374
  }
12129
12375
  pendingMigrations() {
12130
12376
  return this.v2Dirs().filter((dir) => {
12131
- const raw = this.readV2Workspace(path9.join(dir, "workspace.json"));
12377
+ const raw = this.readV2Workspace(path10.join(dir, "workspace.json"));
12132
12378
  return raw !== null && raw !== TOMBSTONE;
12133
12379
  });
12134
12380
  }
12135
12381
  migrateV2(v2) {
12136
12382
  const dirs = this.paths();
12137
- const v2Workspace = path9.join(v2, "workspace.json");
12138
- const v2Tasks = path9.join(v2, "tasks");
12139
- const legacy = path9.join(dirs.dir, "legacy", checkoutSlug(path9.dirname(v2)), "v2");
12383
+ const v2Workspace = path10.join(v2, "workspace.json");
12384
+ const v2Tasks = path10.join(v2, "tasks");
12385
+ const legacy = path10.join(dirs.dir, "legacy", checkoutSlug(path10.dirname(v2)), "v2");
12140
12386
  const raw = this.readV2Workspace(v2Workspace);
12141
12387
  if (raw === null || raw === TOMBSTONE)
12142
12388
  return success(null, null, null);
12143
- return this.withLock(dirs.checkoutLock, () => this.withLock(path9.join(v2, "metadata.lock"), () => {
12389
+ return this.withLock(dirs.checkoutLock, () => this.withLock(path10.join(v2, "metadata.lock"), () => {
12144
12390
  const workspace = this.readV2Workspace(v2Workspace);
12145
12391
  if (workspace === null || workspace === TOMBSTONE)
12146
12392
  return success(null, null, null);
@@ -12149,7 +12395,7 @@ class TaskStore {
12149
12395
  return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
12150
12396
  let files = [];
12151
12397
  try {
12152
- files = fs9.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path9.join(v2Tasks, name)));
12398
+ files = fs9.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path10.join(v2Tasks, name)));
12153
12399
  } catch {}
12154
12400
  const current = this.readWorkspace();
12155
12401
  if (!current.ok)
@@ -12167,21 +12413,21 @@ class TaskStore {
12167
12413
  let skipped = 0;
12168
12414
  const done = new Map;
12169
12415
  try {
12170
- fs9.mkdirSync(path9.join(legacy, "tasks"), { recursive: true });
12416
+ fs9.mkdirSync(path10.join(legacy, "tasks"), { recursive: true });
12171
12417
  for (const name of ["workspace.json", "index.json"])
12172
- if (fs9.existsSync(path9.join(v2, name)))
12173
- fs9.copyFileSync(path9.join(v2, name), path9.join(legacy, name));
12418
+ if (fs9.existsSync(path10.join(v2, name)))
12419
+ fs9.copyFileSync(path10.join(v2, name), path10.join(legacy, name));
12174
12420
  for (const name of files) {
12175
12421
  const id = name.slice(0, -5);
12176
- const file = path9.join(v2Tasks, name);
12422
+ const file = path10.join(v2Tasks, name);
12177
12423
  const bytes = fs9.readFileSync(file, "utf8");
12178
12424
  const digest = sha256(bytes);
12179
- fs9.writeFileSync(path9.join(legacy, "tasks", name), bytes);
12425
+ fs9.writeFileSync(path10.join(legacy, "tasks", name), bytes);
12180
12426
  const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
12181
12427
  if (!parsed.ok || parsed.data.id !== id)
12182
12428
  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 });
12183
12429
  const record = { ...parsed.data, workspaceId: target.id };
12184
- const origin = { path: path9.join(legacy, "tasks", name), digest };
12430
+ const origin = { path: path10.join(legacy, "tasks", name), digest };
12185
12431
  if (!fs9.existsSync(this.eventsPath(id))) {
12186
12432
  this.openTask(record, "migrated.from_v2", null, origin);
12187
12433
  migrated += 1;
@@ -12217,7 +12463,7 @@ class TaskStore {
12217
12463
  try {
12218
12464
  let complete = true;
12219
12465
  for (const [name, digest] of done) {
12220
- const file = path9.join(v2Tasks, name);
12466
+ const file = path10.join(v2Tasks, name);
12221
12467
  if (sha256(fs9.readFileSync(file, "utf8")) !== digest) {
12222
12468
  complete = false;
12223
12469
  continue;
@@ -12228,7 +12474,7 @@ class TaskStore {
12228
12474
  return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
12229
12475
  path: v2
12230
12476
  });
12231
- fs9.rmSync(path9.join(v2, "index.json"), { force: true });
12477
+ fs9.rmSync(path10.join(v2, "index.json"), { force: true });
12232
12478
  this.writeTombstone(v2, target.id);
12233
12479
  } catch (error) {
12234
12480
  return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
@@ -12248,7 +12494,7 @@ class TaskStore {
12248
12494
  }
12249
12495
  adoptLocalStore() {
12250
12496
  const dirs = this.paths();
12251
- const marker = path9.join(dirs.v2, "store.json");
12497
+ const marker = path10.join(dirs.v2, "store.json");
12252
12498
  if (!dirs.shared || !fs9.existsSync(marker))
12253
12499
  return success(null, null, null);
12254
12500
  return this.withLock(dirs.checkoutLock, () => {
@@ -12258,7 +12504,7 @@ class TaskStore {
12258
12504
  try {
12259
12505
  const merge = (from, to) => {
12260
12506
  if (!fs9.existsSync(to)) {
12261
- fs9.mkdirSync(path9.dirname(to), { recursive: true });
12507
+ fs9.mkdirSync(path10.dirname(to), { recursive: true });
12262
12508
  try {
12263
12509
  fs9.renameSync(from, to);
12264
12510
  } catch {
@@ -12269,24 +12515,24 @@ class TaskStore {
12269
12515
  }
12270
12516
  if (fs9.lstatSync(from).isDirectory() && fs9.lstatSync(to).isDirectory())
12271
12517
  for (const name of fs9.readdirSync(from))
12272
- merge(path9.join(from, name), path9.join(to, name));
12518
+ merge(path10.join(from, name), path10.join(to, name));
12273
12519
  };
12274
12520
  for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
12275
- const from = path9.join(dirs.v2, sub);
12521
+ const from = path10.join(dirs.v2, sub);
12276
12522
  if (!fs9.existsSync(from))
12277
12523
  continue;
12278
12524
  if (sub === "tasks") {
12279
12525
  for (const name of fs9.readdirSync(from))
12280
- if (validId(name) && !fs9.existsSync(path9.join(dirs.dir, sub, name)))
12526
+ if (validId(name) && !fs9.existsSync(path10.join(dirs.dir, sub, name)))
12281
12527
  tasks += 1;
12282
12528
  }
12283
- merge(from, path9.join(dirs.dir, sub));
12529
+ merge(from, path10.join(dirs.dir, sub));
12284
12530
  }
12285
- const ledger = path9.join(dirs.v2, "ledger");
12286
- if (fs9.existsSync(ledger) && !fs9.existsSync(path9.join(dirs.dir, "ledger")))
12287
- merge(ledger, path9.join(dirs.dir, "ledger"));
12288
- fs9.rmSync(path9.join(dirs.v2, "task-index.json"), { force: true });
12289
- fs9.renameSync(marker, path9.join(dirs.v2, "store.moved.json"));
12531
+ const ledger = path10.join(dirs.v2, "ledger");
12532
+ if (fs9.existsSync(ledger) && !fs9.existsSync(path10.join(dirs.dir, "ledger")))
12533
+ merge(ledger, path10.join(dirs.dir, "ledger"));
12534
+ fs9.rmSync(path10.join(dirs.v2, "task-index.json"), { force: true });
12535
+ fs9.renameSync(marker, path10.join(dirs.v2, "store.moved.json"));
12290
12536
  } catch (error) {
12291
12537
  return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
12292
12538
  path: dirs.v2
@@ -12324,7 +12570,7 @@ class TaskStore {
12324
12570
  schemaVersion: SCHEMA_VERSION,
12325
12571
  id: workspaceId ?? newId(),
12326
12572
  revision: newRevision(),
12327
- root: path9.dirname(v2),
12573
+ root: path10.dirname(v2),
12328
12574
  runtime: { createdWith: version, updatedWith: version },
12329
12575
  writer: null,
12330
12576
  store: {
@@ -12336,11 +12582,11 @@ class TaskStore {
12336
12582
  critical: ["store"]
12337
12583
  };
12338
12584
  fs9.mkdirSync(v2, { recursive: true });
12339
- const ignore = path9.join(v2, ".gitignore");
12585
+ const ignore = path10.join(v2, ".gitignore");
12340
12586
  if (!fs9.existsSync(ignore))
12341
12587
  fs9.writeFileSync(ignore, `*
12342
12588
  `);
12343
- replaceFile(path9.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
12589
+ replaceFile(path10.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
12344
12590
  `, true);
12345
12591
  }
12346
12592
  stubFor(taskId) {
@@ -12364,8 +12610,8 @@ class TaskStore {
12364
12610
  }
12365
12611
  writeTombstoneIfMissing() {
12366
12612
  try {
12367
- const v2 = path9.join(this.root, ".workit");
12368
- if (fs9.existsSync(path9.join(v2, "workspace.json")))
12613
+ const v2 = path10.join(this.root, ".workit");
12614
+ if (fs9.existsSync(path10.join(v2, "workspace.json")))
12369
12615
  return;
12370
12616
  let id = null;
12371
12617
  try {
@@ -12382,7 +12628,7 @@ class TaskStore {
12382
12628
  replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
12383
12629
  `, false);
12384
12630
  if (!dirs.shared) {
12385
- const ignore = path9.join(dirs.dir, ".gitignore");
12631
+ const ignore = path10.join(dirs.dir, ".gitignore");
12386
12632
  let current = null;
12387
12633
  try {
12388
12634
  current = fs9.readFileSync(ignore, "utf8");
@@ -12442,7 +12688,7 @@ class TaskStore {
12442
12688
  withLock(lockPath, operation) {
12443
12689
  try {
12444
12690
  this.initializeStorage();
12445
- fs9.mkdirSync(path9.dirname(lockPath), { recursive: true });
12691
+ fs9.mkdirSync(path10.dirname(lockPath), { recursive: true });
12446
12692
  } catch (error) {
12447
12693
  return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
12448
12694
  path: this.paths().dir
@@ -12505,7 +12751,7 @@ class TaskStore {
12505
12751
  if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
12506
12752
  return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
12507
12753
  path: lockPath,
12508
- guidance: `Check permissions and the read-only attribute on ${path9.dirname(lockPath)}.`
12754
+ guidance: `Check permissions and the read-only attribute on ${path10.dirname(lockPath)}.`
12509
12755
  });
12510
12756
  if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
12511
12757
  let lock = null;
@@ -12563,7 +12809,7 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
12563
12809
  }, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), oldestFirst = (left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id), cachedRuntimeVersion = null, runtimeVersion = () => {
12564
12810
  if (cachedRuntimeVersion === null) {
12565
12811
  try {
12566
- const pkg = JSON.parse(fs9.readFileSync(path9.join(packageRoot(), "package.json"), "utf8"));
12812
+ const pkg = JSON.parse(fs9.readFileSync(path10.join(packageRoot(), "package.json"), "utf8"));
12567
12813
  cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
12568
12814
  } catch {
12569
12815
  cachedRuntimeVersion = "0.0.0";
@@ -12590,7 +12836,7 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
12590
12836
  return "";
12591
12837
  }
12592
12838
  }, REFLOG_LINE, branchStart = (location, branch, aliases) => {
12593
- const first = readText(path9.join(path9.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
12839
+ const first = readText(path10.join(path10.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
12594
12840
  `, 1)[0];
12595
12841
  const created = REFLOG_LINE.exec(first);
12596
12842
  const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
@@ -12600,7 +12846,7 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
12600
12846
  return start;
12601
12847
  if (!location.git)
12602
12848
  return null;
12603
- for (const line of readText(path9.join(location.git.gitDir, "logs", "HEAD")).split(`
12849
+ for (const line of readText(path10.join(location.git.gitDir, "logs", "HEAD")).split(`
12604
12850
  `)) {
12605
12851
  const entry = REFLOG_LINE.exec(line);
12606
12852
  const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
@@ -12609,12 +12855,12 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
12609
12855
  return moved[1];
12610
12856
  }
12611
12857
  return null;
12612
- }, refExists = (common, ref) => fs9.existsSync(path9.join(common, ref)) || readText(path9.join(common, "packed-refs")).includes(` ${ref}
12858
+ }, refExists = (common, ref) => fs9.existsSync(path10.join(common, ref)) || readText(path10.join(common, "packed-refs")).includes(` ${ref}
12613
12859
  `), origins, sameDirectoryIdentity = (left, right) => {
12614
- if (!path9.isAbsolute(left) || !path9.isAbsolute(right))
12860
+ if (!path10.isAbsolute(left) || !path10.isAbsolute(right))
12615
12861
  return false;
12616
- const normalizedLeft = path9.resolve(left);
12617
- const normalizedRight = path9.resolve(right);
12862
+ const normalizedLeft = path10.resolve(left);
12863
+ const normalizedRight = path10.resolve(right);
12618
12864
  try {
12619
12865
  const a = fs9.statSync(normalizedLeft, { bigint: true });
12620
12866
  const b = fs9.statSync(normalizedRight, { bigint: true });
@@ -12673,8 +12919,8 @@ var init_workers = __esm(() => {
12673
12919
  import { spawnSync as spawnSync4 } from "node:child_process";
12674
12920
  import { createHash as createHash6 } from "node:crypto";
12675
12921
  import fs10 from "node:fs";
12676
- import os from "node:os";
12677
- import path10 from "node:path";
12922
+ import os2 from "node:os";
12923
+ import path11 from "node:path";
12678
12924
  function networkGitInvocation(cwd, args, timeoutMs) {
12679
12925
  const seconds = Math.max(1, Math.ceil(timeoutMs / 1000));
12680
12926
  const env = {
@@ -12732,9 +12978,9 @@ function worktreeTree(cwd, options = {}) {
12732
12978
  return null;
12733
12979
  const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
12734
12980
  const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
12735
- const scratch = fs10.mkdtempSync(path10.join(os.tmpdir(), "workit-tree-"));
12981
+ const scratch = fs10.mkdtempSync(path11.join(os2.tmpdir(), "workit-tree-"));
12736
12982
  try {
12737
- const tempIndex = path10.join(scratch, "index");
12983
+ const tempIndex = path11.join(scratch, "index");
12738
12984
  if (fs10.existsSync(indexPath))
12739
12985
  seedIndex(indexPath, tempIndex);
12740
12986
  const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
@@ -12747,7 +12993,7 @@ function worktreeTree(cwd, options = {}) {
12747
12993
  const skipped = [];
12748
12994
  for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
12749
12995
  try {
12750
- const stat = fs10.statSync(path10.join(top, file));
12996
+ const stat = fs10.statSync(path11.join(top, file));
12751
12997
  if (stat.isFile() && stat.size > cap)
12752
12998
  skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
12753
12999
  } catch {}
@@ -12793,7 +13039,7 @@ function worktreeSignal(cwd, options = {}) {
12793
13039
  if (entry.length < 4)
12794
13040
  continue;
12795
13041
  const file = entry.slice(3);
12796
- const absolute = path10.join(top, file);
13042
+ const absolute = path11.join(top, file);
12797
13043
  let stat = "-";
12798
13044
  try {
12799
13045
  const info = fs10.lstatSync(absolute, { bigint: true });
@@ -12972,15 +13218,15 @@ function parseRemoteUrl(raw) {
12972
13218
  path: cleanPath(decodeURIComponent(url.pathname))
12973
13219
  };
12974
13220
  }
12975
- function readSshConfig(home = os.homedir()) {
13221
+ function readSshConfig(home = os2.homedir()) {
12976
13222
  try {
12977
- return { text: fs10.readFileSync(path10.join(home, ".ssh", "config"), "utf8"), home };
13223
+ return { text: fs10.readFileSync(path11.join(home, ".ssh", "config"), "utf8"), home };
12978
13224
  } catch {
12979
13225
  return { text: "", home };
12980
13226
  }
12981
13227
  }
12982
13228
  function resolveSshHost(alias, sshConfig) {
12983
- const source = typeof sshConfig === "string" ? { text: sshConfig, home: os.homedir() } : sshConfig;
13229
+ const source = typeof sshConfig === "string" ? { text: sshConfig, home: os2.homedir() } : sshConfig;
12984
13230
  let hostname = null;
12985
13231
  let port = null;
12986
13232
  const evaluate = (text, initiallyActive, neverMatch, depth) => {
@@ -13176,15 +13422,15 @@ child.on("exit", (code) => {
13176
13422
  return [];
13177
13423
  return [{ key: match[1].toLowerCase(), args: tokens(match[2]) }];
13178
13424
  }), includeFiles = (spec, home) => {
13179
- const expanded = spec.startsWith("~") ? path10.join(home, spec.slice(1)) : spec;
13180
- const absolute = path10.isAbsolute(expanded) ? expanded : path10.join(home, ".ssh", expanded);
13181
- const dir = path10.dirname(absolute);
13182
- const base = path10.basename(absolute);
13425
+ const expanded = spec.startsWith("~") ? path11.join(home, spec.slice(1)) : spec;
13426
+ const absolute = path11.isAbsolute(expanded) ? expanded : path11.join(home, ".ssh", expanded);
13427
+ const dir = path11.dirname(absolute);
13428
+ const base = path11.basename(absolute);
13183
13429
  if (!/[*?]/u.test(base))
13184
13430
  return [absolute];
13185
13431
  try {
13186
13432
  const pattern = globToRegExp(base);
13187
- return fs10.readdirSync(dir).filter((name) => pattern.test(name)).toSorted().map((name) => path10.join(dir, name));
13433
+ return fs10.readdirSync(dir).filter((name) => pattern.test(name)).toSorted().map((name) => path11.join(dir, name));
13188
13434
  } catch {
13189
13435
  return [];
13190
13436
  }
@@ -13229,13 +13475,13 @@ import {
13229
13475
  copyFileSync as copyFileSync2,
13230
13476
  cpSync as cpSync2,
13231
13477
  existsSync as existsSync3,
13232
- mkdirSync as mkdirSync2,
13478
+ mkdirSync as mkdirSync3,
13233
13479
  readFileSync as readFileSync2,
13234
- readdirSync as readdirSync2,
13480
+ readdirSync as readdirSync3,
13235
13481
  writeFileSync as writeFileSync2
13236
13482
  } from "node:fs";
13237
- import os2 from "node:os";
13238
- import path11 from "node:path";
13483
+ import os3 from "node:os";
13484
+ import path12 from "node:path";
13239
13485
  var diagnosticLogger, setDiagnosticLogger = (logger) => {
13240
13486
  diagnosticLogger = logger;
13241
13487
  }, getDiagnosticLogger = () => diagnosticLogger, PRESETS, mergePreset = (preset, input = {}, current = {
@@ -13247,14 +13493,14 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
13247
13493
  allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
13248
13494
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
13249
13495
  };
13250
- }, resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path11.join(process.env.XDG_CONFIG_HOME || path11.join(os2.homedir(), ".config"), "workit"), migratedDir = null, migrationFailed = false, ensureConfigDir = (dir = resolveConfigDir()) => {
13496
+ }, resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path12.join(process.env.XDG_CONFIG_HOME || path12.join(os3.homedir(), ".config"), "workit"), migratedDir = null, migrationFailed = false, ensureConfigDir = (dir = resolveConfigDir()) => {
13251
13497
  if (migratedDir === dir)
13252
13498
  return dir;
13253
13499
  if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
13254
13500
  migratedDir = dir;
13255
13501
  return dir;
13256
13502
  }
13257
- const legacy = path11.join(process.env.XDG_CONFIG_HOME || path11.join(os2.homedir(), ".config"), "workflow-toolkit");
13503
+ const legacy = path12.join(process.env.XDG_CONFIG_HOME || path12.join(os3.homedir(), ".config"), "workflow-toolkit");
13258
13504
  if (!existsSync3(legacy)) {
13259
13505
  migratedDir = dir;
13260
13506
  return dir;
@@ -13264,11 +13510,11 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
13264
13510
  return dir;
13265
13511
  }
13266
13512
  migrationFailed = false;
13267
- mkdirSync2(dir, { recursive: true });
13513
+ mkdirSync3(dir, { recursive: true });
13268
13514
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
13269
- for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
13270
- const src = path11.join(legacy, entry.name);
13271
- const dest = path11.join(dir, entry.name);
13515
+ for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
13516
+ const src = path12.join(legacy, entry.name);
13517
+ const dest = path12.join(dir, entry.name);
13272
13518
  if (existsSync3(dest))
13273
13519
  continue;
13274
13520
  try {
@@ -13323,7 +13569,7 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
13323
13569
  unchosenBranchPolicy.add(config);
13324
13570
  return { status: "valid", path: file, config };
13325
13571
  }, readConfigTyped = (dir) => {
13326
- const file = path11.join(dir ?? configDir(), "config.json");
13572
+ const file = path12.join(dir ?? configDir(), "config.json");
13327
13573
  return parseConfigResult(readSafe(file), file);
13328
13574
  }, readConfig = () => {
13329
13575
  const result = readConfigTyped();
@@ -13335,7 +13581,7 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
13335
13581
  if (result.status === "malformed")
13336
13582
  throw new Error(result.error);
13337
13583
  return result.config ?? DEFAULTS;
13338
- }, vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path11.join(configDir(), "vcs.json"), branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"), resolveBranchPolicy = (config, workspace, repoPreset) => {
13584
+ }, vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json"), branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"), resolveBranchPolicy = (config, workspace, repoPreset) => {
13339
13585
  const wp = workspace?.branchPolicy ?? {};
13340
13586
  const workspacePreset = Object.hasOwn(PRESETS, wp.preset);
13341
13587
  const detected = !workspacePreset && unchosenBranchPolicy.has(config) ? repoPreset?.() ?? null : null;
@@ -13466,7 +13712,7 @@ var init_branch_policy = __esm(() => {
13466
13712
  import { spawnSync as spawnSync6 } from "node:child_process";
13467
13713
  import { createHash as createHash7 } from "node:crypto";
13468
13714
  import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
13469
- import path12 from "node:path";
13715
+ import path13 from "node:path";
13470
13716
  function workspaceDefaultTarget(cwd, globalDefault) {
13471
13717
  const ws = resolveRuntimeWorkspaceVcs(cwd);
13472
13718
  const wp = ws?.branchPolicy ?? {};
@@ -13511,7 +13757,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
13511
13757
  document: parsed
13512
13758
  };
13513
13759
  }, readWorkspacesResult = (dir = configDir()) => {
13514
- const file = path12.join(dir, "workspaces.json");
13760
+ const file = path13.join(dir, "workspaces.json");
13515
13761
  let raw;
13516
13762
  try {
13517
13763
  raw = readFileSync3(file, "utf8");
@@ -13611,9 +13857,9 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
13611
13857
  let main = null;
13612
13858
  const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
13613
13859
  const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
13614
- if (gitDir && common && path12.resolve(gitDir) !== path12.resolve(common)) {
13860
+ if (gitDir && common && path13.resolve(gitDir) !== path13.resolve(common)) {
13615
13861
  const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
13616
- main = listed?.trim() || (path12.basename(common) === ".git" ? path12.dirname(common) : common);
13862
+ main = listed?.trim() || (path13.basename(common) === ".git" ? path13.dirname(common) : common);
13617
13863
  }
13618
13864
  mainCheckouts.set(cwd, main);
13619
13865
  return main;
@@ -13690,8 +13936,8 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
13690
13936
  profiles: record(nonBlank, profileSchema).optional()
13691
13937
  }).passthrough();
13692
13938
  return entrySchema;
13693
- }, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
13694
- const file = path12.join(configDir(), "workspaces.json");
13939
+ }, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd, dir = configDir()) => {
13940
+ const file = path13.join(dir, "workspaces.json");
13695
13941
  let raw;
13696
13942
  try {
13697
13943
  raw = readFileSync3(file, "utf8");
@@ -13720,10 +13966,27 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
13720
13966
  const matches = matchingEntries(cwd, entries ?? []);
13721
13967
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
13722
13968
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
13723
- return selected ? { file, selected } : null;
13969
+ return selected ? { file, selected, matches } : null;
13724
13970
  }, resolveRuntimeReleaseTracks = (cwd) => {
13725
13971
  const matched = matchRuntimeWorkspace(cwd);
13726
13972
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
13973
+ }, workspaceTrailers = (cwd, dir) => {
13974
+ let matched;
13975
+ try {
13976
+ matched = matchRuntimeWorkspace(cwd, dir);
13977
+ } catch {
13978
+ return { enabled: true, workspace: null, from: null };
13979
+ }
13980
+ if (!matched)
13981
+ return { enabled: true, workspace: null, from: null };
13982
+ const { selected, matches } = matched;
13983
+ const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
13984
+ const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
13985
+ return {
13986
+ enabled: source?.trailers !== false,
13987
+ workspace: selected.name,
13988
+ from: source?.name ?? null
13989
+ };
13727
13990
  }, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
13728
13991
  const matched = matchRuntimeWorkspace(cwd);
13729
13992
  if (!matched)
@@ -13854,7 +14117,7 @@ var init_workspaces = __esm(() => {
13854
14117
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
13855
14118
  }
13856
14119
  }
13857
- if (track.versionSource?.kind === "package-json" && (path12.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
14120
+ if (track.versionSource?.kind === "package-json" && (path13.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
13858
14121
  context.addIssue({
13859
14122
  code: "custom",
13860
14123
  path: ["versionSource", "path"],
@@ -13874,6 +14137,7 @@ var init_workspaces = __esm(() => {
13874
14137
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
13875
14138
  defaultEndpoint: unknown().optional(),
13876
14139
  verification: _enum(["self", "independent"]).optional(),
14140
+ trailers: boolean2().optional(),
13877
14141
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
13878
14142
  kind,
13879
14143
  union([boolean2(), literal("verified")]).optional()
@@ -14191,7 +14455,7 @@ function gitTrackProbe(cwd) {
14191
14455
  const created = own.at(-1);
14192
14456
  if (!created || created.subject !== "branch: Created from HEAD")
14193
14457
  return null;
14194
- const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
14458
+ const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp2(branch)}$`, "u");
14195
14459
  const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
14196
14460
  return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
14197
14461
  },
@@ -14339,7 +14603,7 @@ var ReleaseTrackError, KNOWN_FIELDS, BRANCH_FIELDS, PLACEHOLDER, HAS_PLACEHOLDER
14339
14603
  return run.status === 0 ? (run.stdout ?? "").trim() : null;
14340
14604
  }, recordedBaseKey = (branch) => `branch.${branch}.workitBase`, recordBranchBase = (cwd, branch, base) => {
14341
14605
  git3(cwd, ["config", recordedBaseKey(branch), base]);
14342
- }, escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"), reflog = (cwd, ref) => (git3(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
14606
+ }, escapeRegExp2 = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"), reflog = (cwd, ref) => (git3(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
14343
14607
  `).map((line) => {
14344
14608
  const [sha = "", selector = "", ...subject] = line.split("\t");
14345
14609
  return {
@@ -14389,7 +14653,7 @@ var init_release_tracks = __esm(() => {
14389
14653
 
14390
14654
  // packages/workit-core/src/core/vcs-config.ts
14391
14655
  import fs11 from "node:fs";
14392
- import path13 from "node:path";
14656
+ import path14 from "node:path";
14393
14657
  import { spawnSync as spawnSync8 } from "node:child_process";
14394
14658
  function vcsConfig(mode, cwd, options = {}) {
14395
14659
  const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
@@ -14452,7 +14716,7 @@ function vcsConfig(mode, cwd, options = {}) {
14452
14716
  const prov = cfg[provider] ?? {};
14453
14717
  const out = {
14454
14718
  ok: true,
14455
- configPath: path13.resolve(cfgPath),
14719
+ configPath: path14.resolve(cfgPath),
14456
14720
  provider,
14457
14721
  defaultTargetBranch: workspaceDefault,
14458
14722
  pr: cfg.pr ?? {},
@@ -14472,7 +14736,7 @@ function vcsConfig(mode, cwd, options = {}) {
14472
14736
  }
14473
14737
  return out;
14474
14738
  }
14475
- var workspacesPath = () => path13.join(configDir(), "workspaces.json"), vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd(), remoteProvider = (cwd) => {
14739
+ var workspacesPath = () => path14.join(configDir(), "workspaces.json"), vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd(), remoteProvider = (cwd) => {
14476
14740
  const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
14477
14741
  if (r.status !== 0)
14478
14742
  return null;
@@ -14519,7 +14783,7 @@ var init_vcs_config = __esm(() => {
14519
14783
  import { spawnSync as spawnSync9 } from "node:child_process";
14520
14784
  import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
14521
14785
  import fs12 from "node:fs";
14522
- import path14 from "node:path";
14786
+ import path15 from "node:path";
14523
14787
  function resolveCommit(cwd, ref) {
14524
14788
  if (!safeRef(ref))
14525
14789
  return null;
@@ -14585,14 +14849,14 @@ function diffHash(cwd, base, head) {
14585
14849
  return `sha256:${hash.digest("hex")}`;
14586
14850
  }
14587
14851
  function storeRoot(cwd) {
14588
- const location = resolveStore(path14.resolve(cwd));
14852
+ const location = resolveStore(path15.resolve(cwd));
14589
14853
  if (location instanceof Error)
14590
14854
  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");
14591
14855
  return { ok: true, value: { root: location.dir, shared: location.shared } };
14592
14856
  }
14593
14857
  function ledgerPath(cwd) {
14594
14858
  const root = storeRoot(cwd);
14595
- return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
14859
+ return root.ok ? { ok: true, value: path15.join(root.value.root, "ledger", "ledger.jsonl") } : root;
14596
14860
  }
14597
14861
  function actorFromEnv(env) {
14598
14862
  const value = (key) => {
@@ -14654,8 +14918,8 @@ function appendLine(file, build, options) {
14654
14918
  }
14655
14919
  };
14656
14920
  try {
14657
- fs12.mkdirSync(path14.dirname(file), { recursive: true });
14658
- if (!options.forceLock && !onNetworkFs(path14.dirname(file)))
14921
+ fs12.mkdirSync(path15.dirname(file), { recursive: true });
14922
+ if (!options.forceLock && !onNetworkFs(path15.dirname(file)))
14659
14923
  return write();
14660
14924
  const locked = withLedgerLock(file, write);
14661
14925
  return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
@@ -15334,7 +15598,7 @@ function summarizeRow(row) {
15334
15598
  };
15335
15599
  }
15336
15600
  function stackPosition(root, branch) {
15337
- const dir = path14.join(root, "stacks");
15601
+ const dir = path15.join(root, "stacks");
15338
15602
  let names = [];
15339
15603
  try {
15340
15604
  names = fs12.readdirSync(dir).filter((name) => name.endsWith(".json"));
@@ -15344,7 +15608,7 @@ function stackPosition(root, branch) {
15344
15608
  for (const name of names.toSorted()) {
15345
15609
  let parsed;
15346
15610
  try {
15347
- parsed = JSON.parse(fs12.readFileSync(path14.join(dir, name), "utf8"));
15611
+ parsed = JSON.parse(fs12.readFileSync(path15.join(dir, name), "utf8"));
15348
15612
  } catch {
15349
15613
  continue;
15350
15614
  }
@@ -15489,12 +15753,12 @@ var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, VERDICT_RESULTS, PASSING, FAILING
15489
15753
  }
15490
15754
  }, LOCK_WAIT_MS = 5000, LOCK_STALE_MS = 30000, LOCK_OWNER = "owner", lockToken = (lock) => {
15491
15755
  try {
15492
- return fs12.readFileSync(path14.join(lock, LOCK_OWNER), "utf8");
15756
+ return fs12.readFileSync(path15.join(lock, LOCK_OWNER), "utf8");
15493
15757
  } catch {
15494
15758
  return null;
15495
15759
  }
15496
15760
  }, lockAgeMs = (lock) => {
15497
- for (const target of [path14.join(lock, LOCK_OWNER), lock]) {
15761
+ for (const target of [path15.join(lock, LOCK_OWNER), lock]) {
15498
15762
  try {
15499
15763
  return Date.now() - fs12.statSync(target).mtimeMs;
15500
15764
  } catch {}
@@ -15664,7 +15928,7 @@ var init_ledger = __esm(() => {
15664
15928
  throw error;
15665
15929
  }
15666
15930
  const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
15667
- fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
15931
+ fs12.writeFileSync(path15.join(lock, LOCK_OWNER), token);
15668
15932
  return token;
15669
15933
  },
15670
15934
  release(lock, token) {
@@ -15717,7 +15981,7 @@ var init_ledger = __esm(() => {
15717
15981
 
15718
15982
  // packages/workit-core/src/core/policy/derive.ts
15719
15983
  import { existsSync as existsSync4 } from "node:fs";
15720
- import path15 from "node:path";
15984
+ import path16 from "node:path";
15721
15985
  function deriveRequirements(judgment, scope, constraints, verification = "self") {
15722
15986
  const out = [];
15723
15987
  const base = { scope, dependentAction: null };
@@ -15858,7 +16122,7 @@ var RULES, derived2 = (spec) => ({
15858
16122
  decisionIds: []
15859
16123
  };
15860
16124
  }, planRecorded = (task, root) => {
15861
- const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path15.resolve(root, ref.path)));
16125
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path16.resolve(root, ref.path)));
15862
16126
  if (cited)
15863
16127
  return {
15864
16128
  met: true,
@@ -15907,7 +16171,7 @@ var init_derive = __esm(() => {
15907
16171
  import { createHash as createHash10 } from "node:crypto";
15908
16172
  import * as fs13 from "node:fs";
15909
16173
  import { spawnSync as spawnSync10 } from "node:child_process";
15910
- import path16 from "node:path";
16174
+ import path17 from "node:path";
15911
16175
  function captureCandidate(root, scope, environment = []) {
15912
16176
  let checkout;
15913
16177
  try {
@@ -15949,7 +16213,7 @@ function captureCandidate(root, scope, environment = []) {
15949
16213
  for (const item of [...names].toSorted()) {
15950
16214
  if (!scopeMatches(item, normalizedScope))
15951
16215
  continue;
15952
- const target = path16.join(checkout, item);
16216
+ const target = path17.join(checkout, item);
15953
16217
  try {
15954
16218
  const stat = fs13.lstatSync(target);
15955
16219
  if (stat.isSymbolicLink()) {
@@ -16322,12 +16586,12 @@ function evaluateClosure(requestedOutcome, view) {
16322
16586
  requirementIds: evaluations.map((item) => item.requirementId)
16323
16587
  });
16324
16588
  }
16325
- var digestBytes = (value) => createHash10("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path16.sep}`), relative = (root, candidate) => path16.relative(root, candidate).split(path16.sep).join("/") || ".", canonicalPath = (value) => {
16589
+ var digestBytes = (value) => createHash10("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path17.sep}`), relative = (root, candidate) => path17.relative(root, candidate).split(path17.sep).join("/") || ".", canonicalPath = (value) => {
16326
16590
  if (typeof value !== "string" || !value)
16327
16591
  return null;
16328
16592
  if (value.includes("\\") || value.split("/").includes(".."))
16329
16593
  return null;
16330
- const normalized = path16.posix.normalize(value);
16594
+ const normalized = path17.posix.normalize(value);
16331
16595
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
16332
16596
  return null;
16333
16597
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -16369,14 +16633,14 @@ var digestBytes = (value) => createHash10("sha256").update(value).digest("hex"),
16369
16633
  git: !/not a git repository/i.test(stderr)
16370
16634
  };
16371
16635
  }
16372
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")) : [];
16636
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")) : [];
16373
16637
  const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
16374
16638
  if (stagedDeleted.status !== 0) {
16375
16639
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
16376
16640
  if (!/not a git repository/i.test(stderr))
16377
16641
  return { paths, uncertain: true, git: true };
16378
16642
  } else if (stagedDeleted.stdout) {
16379
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")));
16643
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")));
16380
16644
  }
16381
16645
  return { paths: [...new Set(paths)], uncertain: false, git: true };
16382
16646
  }, walk = (root, directory, output) => {
@@ -16390,7 +16654,7 @@ var digestBytes = (value) => createHash10("sha256").update(value).digest("hex"),
16390
16654
  for (const name of names) {
16391
16655
  if (name === ".git" || name === ".workit")
16392
16656
  continue;
16393
- const target = path16.join(directory, name);
16657
+ const target = path17.join(directory, name);
16394
16658
  let stat;
16395
16659
  try {
16396
16660
  stat = fs13.lstatSync(target);
@@ -16407,12 +16671,12 @@ var digestBytes = (value) => createHash10("sha256").update(value).digest("hex"),
16407
16671
  }, scopeRoots = (root, scope) => {
16408
16672
  const roots = [];
16409
16673
  for (const item of scope.paths.length ? scope.paths : ["."]) {
16410
- const target = path16.resolve(root, item);
16674
+ const target = path17.resolve(root, item);
16411
16675
  if (!inside(root, target))
16412
16676
  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` }] });
16413
16677
  let ancestor = target;
16414
16678
  while (!fs13.existsSync(ancestor) && ancestor !== root)
16415
- ancestor = path16.dirname(ancestor);
16679
+ ancestor = path17.dirname(ancestor);
16416
16680
  try {
16417
16681
  if (!inside(root, fs13.realpathSync(ancestor)))
16418
16682
  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` }] });
@@ -16476,23 +16740,23 @@ var digestBytes = (value) => createHash10("sha256").update(value).digest("hex"),
16476
16740
  continue;
16477
16741
  if (!reference.digest)
16478
16742
  return failure2("invalid_input", "document references require a byte digest");
16479
- const target = path16.resolve(root, reference.path);
16480
- if (target !== root && !target.startsWith(`${root}${path16.sep}`))
16743
+ const target = path17.resolve(root, reference.path);
16744
+ if (target !== root && !target.startsWith(`${root}${path17.sep}`))
16481
16745
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
16482
16746
  fields: [
16483
16747
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
16484
16748
  ]
16485
16749
  });
16486
16750
  try {
16487
- const below = path16.relative(root, target);
16751
+ const below = path17.relative(root, target);
16488
16752
  let current = root;
16489
- for (const segment of below.split(path16.sep)) {
16753
+ for (const segment of below.split(path17.sep)) {
16490
16754
  if (!segment)
16491
16755
  continue;
16492
- current = path16.join(current, segment);
16756
+ current = path17.join(current, segment);
16493
16757
  const stat = fs13.lstatSync(current);
16494
16758
  const real = fs13.realpathSync(current);
16495
- if (real !== root && !real.startsWith(`${root}${path16.sep}`))
16759
+ if (real !== root && !real.startsWith(`${root}${path17.sep}`))
16496
16760
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
16497
16761
  fields: [
16498
16762
  {
@@ -16773,7 +17037,7 @@ var init_git = () => {};
16773
17037
  // packages/workit-core/src/core/branch.ts
16774
17038
  import { execFileSync } from "node:child_process";
16775
17039
  import { createHash as createHash11 } from "node:crypto";
16776
- import path17 from "node:path";
17040
+ import path18 from "node:path";
16777
17041
  function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
16778
17042
  const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
16779
17043
  const config = readConfig();
@@ -16972,7 +17236,7 @@ var withReleaseTracks = (workspaceRoot, policy) => {
16972
17236
  let raw;
16973
17237
  try {
16974
17238
  raw = execFileSync("git", ["status", "--porcelain=v1"], {
16975
- cwd: path17.resolve(workspaceRoot),
17239
+ cwd: path18.resolve(workspaceRoot),
16976
17240
  encoding: "utf8",
16977
17241
  stdio: ["pipe", "pipe", "pipe"]
16978
17242
  });
@@ -17210,13 +17474,13 @@ import {
17210
17474
  fchmodSync,
17211
17475
  fsyncSync,
17212
17476
  lstatSync as lstatSync3,
17213
- mkdirSync as mkdirSync3,
17477
+ mkdirSync as mkdirSync4,
17214
17478
  openSync as openSync2,
17215
17479
  renameSync as renameSync2,
17216
- unlinkSync,
17480
+ unlinkSync as unlinkSync2,
17217
17481
  writeFileSync as writeFileSync3
17218
17482
  } from "node:fs";
17219
- import path18 from "node:path";
17483
+ import path19 from "node:path";
17220
17484
  function writeFileExclusive(file, content, mode) {
17221
17485
  try {
17222
17486
  writeFileSync3(file, content, { encoding: "utf8", flag: "wx", mode });
@@ -17228,9 +17492,9 @@ function writeFileExclusive(file, content, mode) {
17228
17492
  }
17229
17493
  }
17230
17494
  function writeFileAtomic(file, content, mode) {
17231
- const absolute = path18.resolve(file);
17232
- let parent = path18.dirname(absolute);
17233
- const root = path18.parse(parent).root;
17495
+ const absolute = path19.resolve(file);
17496
+ let parent = path19.dirname(absolute);
17497
+ const root = path19.parse(parent).root;
17234
17498
  for (;; ) {
17235
17499
  try {
17236
17500
  if (lstatSync3(parent).isSymbolicLink())
@@ -17239,13 +17503,13 @@ function writeFileAtomic(file, content, mode) {
17239
17503
  if (error.code !== "ENOENT")
17240
17504
  throw error;
17241
17505
  }
17242
- const next = path18.dirname(parent);
17506
+ const next = path19.dirname(parent);
17243
17507
  if (parent === root || next === parent)
17244
17508
  break;
17245
17509
  parent = next;
17246
17510
  }
17247
- const directory = path18.dirname(absolute);
17248
- mkdirSync3(directory, { recursive: true });
17511
+ const directory = path19.dirname(absolute);
17512
+ mkdirSync4(directory, { recursive: true });
17249
17513
  let existing = null;
17250
17514
  try {
17251
17515
  existing = lstatSync3(absolute);
@@ -17274,7 +17538,7 @@ function writeFileAtomic(file, content, mode) {
17274
17538
  } catch {}
17275
17539
  }
17276
17540
  try {
17277
- unlinkSync(temporary);
17541
+ unlinkSync2(temporary);
17278
17542
  } catch {}
17279
17543
  throw error;
17280
17544
  }
@@ -17283,8 +17547,8 @@ var init_safe_write = () => {};
17283
17547
 
17284
17548
  // packages/workit-core/src/autonomy.ts
17285
17549
  import { copyFileSync as copyFileSync3, existsSync as existsSync6 } from "node:fs";
17286
- import os3 from "node:os";
17287
- import path19 from "node:path";
17550
+ import os4 from "node:os";
17551
+ import path20 from "node:path";
17288
17552
  function endpointOf(entry) {
17289
17553
  const raw = entry?.defaultEndpoint;
17290
17554
  if (raw === undefined)
@@ -17523,13 +17787,13 @@ function listGrants(dir = grantsDir()) {
17523
17787
  }
17524
17788
  var accountHome = () => {
17525
17789
  try {
17526
- return os3.userInfo().homedir;
17790
+ return os4.userInfo().homedir;
17527
17791
  } catch {
17528
- return os3.homedir();
17792
+ return os4.homedir();
17529
17793
  }
17530
- }, grantsHome, grantsDir = () => path19.join(grantsHome.resolve(), ".config", "workit"), grantsOverride = () => {
17531
- const configured = path19.resolve(resolveConfigDir());
17532
- return configured === path19.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 ${path19.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
17794
+ }, grantsHome, grantsDir = () => path20.join(grantsHome.resolve(), ".config", "workit"), grantsOverride = () => {
17795
+ const configured = path20.resolve(resolveConfigDir());
17796
+ return configured === path20.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 ${path20.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
17533
17797
  }, GRANT_KINDS, DEFAULT_GRANTS, DEFAULT_ENDPOINTS, DEFAULT_ENDPOINT = "commit", isDefaultEndpoint = (value) => typeof value === "string" && DEFAULT_ENDPOINTS.includes(value), raisesEndpoint = (current, next) => DEFAULT_ENDPOINTS.indexOf(next) > DEFAULT_ENDPOINTS.indexOf(current), DEFAULT_VERIFICATION = "self", verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self", isGrantKind = (value) => typeof value === "string" && GRANT_KINDS.includes(value), isGrantValue = (value) => value === true || value === false || value === "verified", normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value, fromAutoApprove = (value) => {
17534
17798
  if (value === true)
17535
17799
  return { push: true, pr: true, merge: "verified" };
@@ -19273,12 +19537,12 @@ var init_pr_body = __esm(() => {
19273
19537
  });
19274
19538
 
19275
19539
  // packages/workit-core/src/core/pr-create.ts
19276
- import path20 from "node:path";
19540
+ import path21 from "node:path";
19277
19541
  import { spawnSync as spawnSync11 } from "node:child_process";
19278
19542
  import { pathToFileURL } from "node:url";
19279
19543
  var safePushUrl = (remote) => {
19280
19544
  const raw = remote.trim();
19281
- if (path20.isAbsolute(raw))
19545
+ if (path21.isAbsolute(raw))
19282
19546
  return pathToFileURL(raw).href;
19283
19547
  if (!raw.includes("://")) {
19284
19548
  const scp = /^(?:([^@\s]+)@)?([^:]+):(.+)$/u.exec(raw);
@@ -19339,7 +19603,7 @@ var init_scripts = __esm(() => {
19339
19603
 
19340
19604
  // packages/workit-core/src/core/youtrack.ts
19341
19605
  import fs14 from "node:fs";
19342
- import path21 from "node:path";
19606
+ import path22 from "node:path";
19343
19607
  function readYouTrackConfig(required) {
19344
19608
  const cfgPath = youTrackConfigPath();
19345
19609
  if (!fs14.existsSync(cfgPath)) {
@@ -19363,7 +19627,7 @@ function youTrackConfigLoad() {
19363
19627
  }
19364
19628
  const cfgPath = loaded.path;
19365
19629
  const tokenFile = String(loaded.config.tokenFile ?? "");
19366
- const tokenPath = tokenFile ? path21.isAbsolute(tokenFile) ? path21.resolve(tokenFile) : path21.resolve(process.cwd(), tokenFile) : "";
19630
+ const tokenPath = tokenFile ? path22.isAbsolute(tokenFile) ? path22.resolve(tokenFile) : path22.resolve(process.cwd(), tokenFile) : "";
19367
19631
  if (!tokenPath || !fs14.existsSync(tokenPath)) {
19368
19632
  return { error: "missing youtrack.token" };
19369
19633
  }
@@ -19377,8 +19641,8 @@ function youTrackConfigLoad() {
19377
19641
  const redacted = { ...loaded.config };
19378
19642
  delete redacted.tokenFile;
19379
19643
  redacted.tokenPresent = true;
19380
- redacted.configPath = path21.resolve(cfgPath);
19381
- redacted.tokenPath = path21.resolve(tokenPath);
19644
+ redacted.configPath = path22.resolve(cfgPath);
19645
+ redacted.tokenPath = path22.resolve(tokenPath);
19382
19646
  return { data: redacted };
19383
19647
  }
19384
19648
  function tzParts(date, tz) {
@@ -19701,7 +19965,7 @@ async function postUpdate({
19701
19965
  }
19702
19966
  return { ok: true, issueId, postedComment: true };
19703
19967
  }
19704
- var ISSUE_RE, TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE", youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path21.join(configDir(), "youtrack.json"), youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path21.join(resolveConfigDir(), "youtrack.json"), youTrackTokenModeOk = (p) => {
19968
+ var ISSUE_RE, TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE", youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path22.join(configDir(), "youtrack.json"), youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path22.join(resolveConfigDir(), "youtrack.json"), youTrackTokenModeOk = (p) => {
19705
19969
  if (process.platform === "win32")
19706
19970
  return true;
19707
19971
  const mode = fs14.statSync(p).mode & 511;
@@ -19737,7 +20001,7 @@ var ISSUE_RE, TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE", youTrackConfigPath = () =>
19737
20001
  if ("error" in loaded)
19738
20002
  return loaded;
19739
20003
  const tokenFile = String(loaded.config.tokenFile ?? "");
19740
- const tokenPath = tokenFile ? path21.isAbsolute(tokenFile) ? path21.resolve(tokenFile) : path21.resolve(process.cwd(), tokenFile) : "";
20004
+ const tokenPath = tokenFile ? path22.isAbsolute(tokenFile) ? path22.resolve(tokenFile) : path22.resolve(process.cwd(), tokenFile) : "";
19741
20005
  if (!tokenPath || !fs14.existsSync(tokenPath))
19742
20006
  return { error: "missing youtrack.token" };
19743
20007
  if (!youTrackTokenModeOk(tokenPath))
@@ -19842,18 +20106,18 @@ var init_context_read = __esm(() => {
19842
20106
 
19843
20107
  // packages/workit-core/src/core.ts
19844
20108
  import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
19845
- import path22 from "node:path";
20109
+ import path23 from "node:path";
19846
20110
  function resolveInside(root, candidate) {
19847
20111
  const base = realpathSync5(root);
19848
- const target = path22.resolve(base, candidate);
19849
- if (target !== base && !target.startsWith(base + path22.sep)) {
20112
+ const target = path23.resolve(base, candidate);
20113
+ if (target !== base && !target.startsWith(base + path23.sep)) {
19850
20114
  throw new Error("path must stay inside repository root");
19851
20115
  }
19852
20116
  let ancestor = target;
19853
20117
  while (!existsSync7(ancestor))
19854
- ancestor = path22.dirname(ancestor);
20118
+ ancestor = path23.dirname(ancestor);
19855
20119
  const canonicalAncestor = realpathSync5(ancestor);
19856
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path22.sep)) {
20120
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path23.sep)) {
19857
20121
  throw new Error("path must stay inside repository root");
19858
20122
  }
19859
20123
  return target;
@@ -19879,7 +20143,7 @@ var init_core3 = __esm(() => {
19879
20143
 
19880
20144
  // packages/workit-cli/src/task.ts
19881
20145
  import { readFileSync as readFileSync7 } from "node:fs";
19882
- import path23 from "node:path";
20146
+ import path24 from "node:path";
19883
20147
  async function readInput(input) {
19884
20148
  if (typeof input === "string")
19885
20149
  return input;
@@ -20198,7 +20462,7 @@ var TASK_FAMILIES, workspaceRootFor = (deps = {}) => {
20198
20462
  return explicit;
20199
20463
  const cwd = deps.cwd ?? process.cwd();
20200
20464
  const top = checkRoot(cwd);
20201
- return sameDirectoryIdentity(top, path23.resolve(cwd)) ? cwd : top;
20465
+ return sameDirectoryIdentity(top, path24.resolve(cwd)) ? cwd : top;
20202
20466
  }, TASK_ACTIONS, outOf = (deps) => deps.out ?? process.stdout, errOf = (deps) => deps.err ?? process.stderr, write = (stream, value) => stream.write(value.endsWith(`
20203
20467
  `) ? value : `${value}
20204
20468
  `), jsonResult = (stream, result) => write(stream, JSON.stringify(result)), usage = (message) => failure2("invalid_input", message), parseUsage = (message, json) => ({
@@ -21488,7 +21752,7 @@ var init_environment = __esm(() => {
21488
21752
 
21489
21753
  // node_modules/ansi-escapes/base.js
21490
21754
  import process2 from "node:process";
21491
- import os4 from "node:os";
21755
+ import os5 from "node:os";
21492
21756
  var ESC = "\x1B[", SEP = ";", isTerminalApp, isWindows2, isTmux, cwdFunction, cursorTo = (x, y) => {
21493
21757
  if (typeof x !== "number") {
21494
21758
  throw new TypeError("The `x` argument is required");
@@ -21510,7 +21774,7 @@ var ESC = "\x1B[", SEP = ";", isTerminalApp, isWindows2, isTmux, cwdFunction, cu
21510
21774
  if (isBrowser || !isWindows2) {
21511
21775
  return false;
21512
21776
  }
21513
- const parts = os4.release().split(".");
21777
+ const parts = os5.release().split(".");
21514
21778
  const major = Number(parts[0]);
21515
21779
  const build = Number(parts[2] ?? 0);
21516
21780
  if (major < 10) {
@@ -57173,7 +57437,7 @@ var init_ansi_styles2 = __esm(() => {
57173
57437
 
57174
57438
  // node_modules/chalk/source/vendor/supports-color/index.js
57175
57439
  import process5 from "node:process";
57176
- import os5 from "node:os";
57440
+ import os6 from "node:os";
57177
57441
  import tty2 from "node:tty";
57178
57442
  function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process5.argv) {
57179
57443
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
@@ -57231,7 +57495,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
57231
57495
  return min;
57232
57496
  }
57233
57497
  if (process5.platform === "win32") {
57234
- const osRelease = os5.release().split(".");
57498
+ const osRelease = os6.release().split(".");
57235
57499
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
57236
57500
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
57237
57501
  }
@@ -63060,7 +63324,7 @@ var init_gitignore = __esm(() => {
63060
63324
 
63061
63325
  // packages/workit-core/src/core/changelog.ts
63062
63326
  import fs17 from "node:fs";
63063
- import path24 from "node:path";
63327
+ import path25 from "node:path";
63064
63328
  import { createHash as createHash13 } from "node:crypto";
63065
63329
  function normalizeBullet(text) {
63066
63330
  text = text.trim();
@@ -63220,7 +63484,7 @@ function applyChangelog(target, entries, normalizeOnly) {
63220
63484
  const rendered = renderChangelog(source.text, entries, normalizeOnly);
63221
63485
  if ("error" in rendered)
63222
63486
  return { error: rendered.error };
63223
- fs17.mkdirSync(path24.dirname(target), { recursive: true });
63487
+ fs17.mkdirSync(path25.dirname(target), { recursive: true });
63224
63488
  fs17.writeFileSync(target, rendered.text, "utf8");
63225
63489
  return { ...rendered.result, path: target };
63226
63490
  }
@@ -63363,14 +63627,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
63363
63627
  }, changelogTarget = (workspaceRoot, changelogPath) => {
63364
63628
  try {
63365
63629
  const cwd = fs17.realpathSync(resolveWorkspaceRoot(workspaceRoot));
63366
- const target = path24.resolve(cwd, changelogPath || "CHANGELOG.md");
63367
- if (target !== cwd && !target.startsWith(cwd + path24.sep))
63630
+ const target = path25.resolve(cwd, changelogPath || "CHANGELOG.md");
63631
+ if (target !== cwd && !target.startsWith(cwd + path25.sep))
63368
63632
  return { error: "changelog path must be inside workspace_root" };
63369
63633
  let ancestor = target;
63370
63634
  while (!fs17.existsSync(ancestor))
63371
- ancestor = path24.dirname(ancestor);
63635
+ ancestor = path25.dirname(ancestor);
63372
63636
  const canonicalAncestor = fs17.realpathSync(ancestor);
63373
- if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path24.sep))
63637
+ if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path25.sep))
63374
63638
  return { error: "changelog path must be inside workspace_root" };
63375
63639
  return { cwd, target: fs17.existsSync(target) ? fs17.realpathSync(target) : target };
63376
63640
  } catch {
@@ -63389,22 +63653,22 @@ var init_changelog = __esm(() => {
63389
63653
 
63390
63654
  // packages/workit-core/src/core/hygiene.ts
63391
63655
  import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
63392
- import path25 from "node:path";
63393
- var repoRoot2, templatesDir = () => path25.join(repoRoot2, "templates", "hygiene"), packageJson = (root) => {
63394
- if (!existsSync9(path25.join(root, "package.json")))
63656
+ import path26 from "node:path";
63657
+ var repoRoot2, templatesDir = () => path26.join(repoRoot2, "templates", "hygiene"), packageJson = (root) => {
63658
+ if (!existsSync9(path26.join(root, "package.json")))
63395
63659
  return null;
63396
63660
  try {
63397
- return JSON.parse(readFileSync9(path25.join(root, "package.json"), "utf8"));
63661
+ return JSON.parse(readFileSync9(path26.join(root, "package.json"), "utf8"));
63398
63662
  } catch {
63399
63663
  return null;
63400
63664
  }
63401
63665
  }, isOpenSource = (root) => {
63402
- if (existsSync9(path25.join(root, "LICENSE")))
63666
+ if (existsSync9(path26.join(root, "LICENSE")))
63403
63667
  return true;
63404
63668
  const pkg = packageJson(root);
63405
63669
  if (pkg && !pkg.private)
63406
63670
  return true;
63407
- return path25.basename(root) === "workflow-toolkit";
63671
+ return path26.basename(root) === "workflow-toolkit";
63408
63672
  }, licenseHolder = (root) => {
63409
63673
  const pkg = packageJson(root);
63410
63674
  if (pkg) {
@@ -63425,13 +63689,13 @@ var repoRoot2, templatesDir = () => path25.join(repoRoot2, "templates", "hygiene
63425
63689
  files.push("LICENSE", "CONTRIBUTING.md");
63426
63690
  const planned = [];
63427
63691
  for (const file of files) {
63428
- if (existsSync9(path25.join(root, file)))
63692
+ if (existsSync9(path26.join(root, file)))
63429
63693
  continue;
63430
- const tpl = path25.join(templatesDir(), file);
63694
+ const tpl = path26.join(templatesDir(), file);
63431
63695
  if (!existsSync9(tpl))
63432
63696
  continue;
63433
- const content = readFileSync9(tpl, "utf8").replace(/<PROJECT>/g, path25.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
63434
- planned.push({ path: path25.join(root, file), content });
63697
+ const content = readFileSync9(tpl, "utf8").replace(/<PROJECT>/g, path26.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
63698
+ planned.push({ path: path26.join(root, file), content });
63435
63699
  }
63436
63700
  return planned;
63437
63701
  };
@@ -63443,9 +63707,9 @@ var init_hygiene = __esm(() => {
63443
63707
 
63444
63708
  // packages/workit-cli/src/admin/setup-state.ts
63445
63709
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
63446
- import path26 from "node:path";
63710
+ import path27 from "node:path";
63447
63711
  var classifySetupFile = (dir, name) => {
63448
- const file = path26.join(dir, name);
63712
+ const file = path27.join(dir, name);
63449
63713
  let raw;
63450
63714
  try {
63451
63715
  raw = readFileSync10(file, "utf8");
@@ -63483,7 +63747,7 @@ var init_setup_state = __esm(() => {
63483
63747
  });
63484
63748
 
63485
63749
  // packages/workit-cli/src/admin/registration.ts
63486
- import path27 from "node:path";
63750
+ import path28 from "node:path";
63487
63751
  function isWorkitPlugin(value) {
63488
63752
  const s = String(value).replaceAll("\\", "/");
63489
63753
  const url = s.startsWith("file://") || s.startsWith("git+file://");
@@ -63516,11 +63780,11 @@ function mergeCursorEnabledPlugins(enabled) {
63516
63780
  }
63517
63781
  function mergeCursorPluginDirs(pluginDirs, pluginDir) {
63518
63782
  const strip = (p) => {
63519
- const j = path27.join(p);
63520
- return path27.dirname(j) === j ? j : j.replace(/[\\/]+$/, "");
63783
+ const j = path28.join(p);
63784
+ return path28.dirname(j) === j ? j : j.replace(/[\\/]+$/, "");
63521
63785
  };
63522
63786
  const normalized = strip(pluginDir);
63523
- const legacy = strip(path27.join(path27.dirname(pluginDir), "workflow-toolkit"));
63787
+ const legacy = strip(path28.join(path28.dirname(pluginDir), "workflow-toolkit"));
63524
63788
  const prev = Array.isArray(pluginDirs) ? pluginDirs.map(String) : [];
63525
63789
  const kept = prev.filter((d) => strip(d) !== legacy);
63526
63790
  const exists = kept.some((d) => strip(d) === normalized);
@@ -63608,7 +63872,7 @@ function cursorMcpServerEntry(_packageDir) {
63608
63872
  function cursorHooksEntry(pluginDir) {
63609
63873
  return { command: `${cursorHookLauncher(pluginDir)} workit-cursor-session-start`, args: [] };
63610
63874
  }
63611
- var OPENCODE_NPM_PIN = "@brainervirus/workit-opencode", isRecord4 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), named = (s, name) => s === name || s.startsWith(`${name}@`), CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest", cursorHookLauncher = (pluginDir) => pluginDir ? `node "${shellQuotedPath(path27.join(pluginDir, "hooks", "launch.mjs"))}"` : 'node "${CURSOR_PLUGIN_ROOT}/hooks/launch.mjs"', shellQuotedPath = (file) => path27.sep === "\\" ? file : file.replace(/([$`"\\])/g, "\\$1"), CURSOR_HOOK_RUN_COMMAND, CURSOR_PRETOOLUSE_MATCHER = "Write|Edit|Delete|Shell|Remove|ApplyPatch|Apply_Patch|Patch|Rename|Mkdir|Mv|Cp|Touch", CURSOR_HOOK_EVENTS, canonicalHookEntry = (event, pluginDir) => {
63875
+ var OPENCODE_NPM_PIN = "@brainervirus/workit-opencode", isRecord4 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), named = (s, name) => s === name || s.startsWith(`${name}@`), CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest", cursorHookLauncher = (pluginDir) => pluginDir ? `node "${shellQuotedPath(path28.join(pluginDir, "hooks", "launch.mjs"))}"` : 'node "${CURSOR_PLUGIN_ROOT}/hooks/launch.mjs"', shellQuotedPath = (file) => path28.sep === "\\" ? file : file.replace(/([$`"\\])/g, "\\$1"), CURSOR_HOOK_RUN_COMMAND, CURSOR_PRETOOLUSE_MATCHER = "Write|Edit|Delete|Shell|Remove|ApplyPatch|Apply_Patch|Patch|Rename|Mkdir|Mv|Cp|Touch", CURSOR_HOOK_EVENTS, canonicalHookEntry = (event, pluginDir) => {
63612
63876
  const command = `${cursorHookLauncher(pluginDir)} workit-cursor-hook`;
63613
63877
  if (event === "preToolUse")
63614
63878
  return { command, matcher: CURSOR_PRETOOLUSE_MATCHER, failClosed: false };
@@ -63641,219 +63905,6 @@ var init_support_matrix = __esm(() => {
63641
63905
  };
63642
63906
  });
63643
63907
 
63644
- // packages/workit-core/src/core/logger.ts
63645
- import { appendFileSync, mkdirSync as mkdirSync4, readdirSync as readdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
63646
- import os6 from "node:os";
63647
- import path28 from "node:path";
63648
- var REDACTED = "[REDACTED]", DEFAULT_MAX_RATE = 20, DEFAULT_RATE_WINDOW_MS = 1000, DEFAULT_MAX_FIELD_LENGTH = 200, DEFAULT_MAX_STACK_LINES = 30, DAY_FILE, RETAINED_DAYS = 7, SECRET_VALUE, KEY_EQ_VALUE, URL_QUERY, splitKey = (key) => key.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/).map((word) => word.toLowerCase()), SENSITIVE_WORDS, STACK_WORDS, escapeRegExp3 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), resolveStateDir = () => {
63649
- const override = process.env.WORKFLOW_TOOLKIT_STATE;
63650
- if (override)
63651
- return override;
63652
- const home = os6.homedir();
63653
- if (process.env.XDG_STATE_HOME)
63654
- return path28.join(process.env.XDG_STATE_HOME, "workit");
63655
- switch (os6.platform()) {
63656
- case "darwin":
63657
- return path28.join(home, "Library", "Application Support", "workit");
63658
- case "win32":
63659
- return path28.join(process.env.LOCALAPPDATA ?? path28.join(home, "AppData", "Local"), "workit");
63660
- default:
63661
- return path28.join(process.env.HOME ?? home, ".local", "state", "workit");
63662
- }
63663
- }, homePattern, applyPatterns = (value) => {
63664
- let out = value;
63665
- out = out.replace(SECRET_VALUE, REDACTED);
63666
- out = out.replace(KEY_EQ_VALUE, "$1$2[REDACTED]");
63667
- out = out.replace(URL_QUERY, "$1?[REDACTED]");
63668
- out = out.replace(homePattern, "~");
63669
- if (process.platform === "win32")
63670
- out = out.replace(/\\/g, "/");
63671
- return out;
63672
- }, redactString = (value, options) => {
63673
- const out = applyPatterns(value);
63674
- if (out.length > options.maxFieldLength) {
63675
- return `${out.slice(0, options.maxFieldLength)}…`;
63676
- }
63677
- return out;
63678
- }, boundStack = (value, options) => {
63679
- if (typeof value !== "string")
63680
- return redact(value, options);
63681
- const lines = value.split(`
63682
- `);
63683
- const kept = lines.slice(0, options.maxStackLines).map(applyPatterns);
63684
- const dropped = lines.length - kept.length;
63685
- const body = dropped > 0 ? [...kept, ` ... ${dropped} more`] : kept;
63686
- return body.join(`
63687
- `);
63688
- }, redact = (value, options = DEFAULT_OPTIONS) => {
63689
- if (value === null || value === undefined)
63690
- return null;
63691
- if (typeof value === "boolean" || typeof value === "number")
63692
- return value;
63693
- if (typeof value === "string")
63694
- return redactString(value, options);
63695
- if (value instanceof Date)
63696
- return value.toISOString();
63697
- if (Array.isArray(value))
63698
- return value.map((entry) => redact(entry, options));
63699
- if (typeof value === "object") {
63700
- const out = {};
63701
- for (const [key, entry] of Object.entries(value)) {
63702
- const words = splitKey(key);
63703
- if (words.some((word) => STACK_WORDS.has(word)))
63704
- out[key] = boundStack(entry, options);
63705
- else if (words.some((word) => SENSITIVE_WORDS.has(word)))
63706
- out[key] = REDACTED;
63707
- else
63708
- out[key] = redact(entry, options);
63709
- }
63710
- return out;
63711
- }
63712
- return null;
63713
- }, DEFAULT_OPTIONS, dailyFileName = (date) => {
63714
- const y = date.getFullYear();
63715
- const m = String(date.getMonth() + 1).padStart(2, "0");
63716
- const d = String(date.getDate()).padStart(2, "0");
63717
- return `workit-${y}-${m}-${d}.jsonl`;
63718
- }, pruneOldFiles = (dir) => {
63719
- try {
63720
- const files = readdirSync4(dir).filter((name) => DAY_FILE.test(name)).toSorted().toReversed();
63721
- for (const file of files.slice(RETAINED_DAYS)) {
63722
- try {
63723
- unlinkSync2(path28.join(dir, file));
63724
- } catch {}
63725
- }
63726
- } catch {}
63727
- }, createLogger = (options = {}) => {
63728
- const stateDir = options.stateDir ?? resolveStateDir();
63729
- const logDir = path28.join(stateDir, "logs");
63730
- const now = options.now ?? (() => new Date);
63731
- const maxRate = options.maxRate ?? DEFAULT_MAX_RATE;
63732
- const rateWindowMs = options.rateWindowMs ?? DEFAULT_RATE_WINDOW_MS;
63733
- const redactOptions = {
63734
- maxFieldLength: options.maxFieldLength ?? DEFAULT_MAX_FIELD_LENGTH,
63735
- maxStackLines: options.maxStackLines ?? DEFAULT_MAX_STACK_LINES
63736
- };
63737
- let windowStart = 0;
63738
- const windowCounts = { debug: 0, info: 0, warn: 0, error: 0 };
63739
- const emit = (level, message, context) => {
63740
- const date = now();
63741
- const elapsed = date.getTime() - windowStart;
63742
- if (elapsed >= rateWindowMs || windowStart === 0) {
63743
- windowStart = date.getTime();
63744
- windowCounts.debug = 0;
63745
- windowCounts.info = 0;
63746
- windowCounts.warn = 0;
63747
- windowCounts.error = 0;
63748
- }
63749
- windowCounts[level] += 1;
63750
- if (windowCounts[level] > maxRate)
63751
- return;
63752
- let event = null;
63753
- try {
63754
- event = {
63755
- level,
63756
- time: date.toISOString(),
63757
- message: redactString(message, redactOptions),
63758
- context: redact(context ?? {}, redactOptions)
63759
- };
63760
- const line = `${JSON.stringify(event)}
63761
- `;
63762
- mkdirSync4(logDir, { recursive: true, mode: 448 });
63763
- appendFileSync(path28.join(logDir, dailyFileName(date)), line, { mode: 384 });
63764
- pruneOldFiles(logDir);
63765
- } catch {
63766
- try {
63767
- event = {
63768
- level,
63769
- time: date.toISOString(),
63770
- message: redactString(message, redactOptions),
63771
- context: { redaction_failed: true }
63772
- };
63773
- mkdirSync4(logDir, { recursive: true, mode: 448 });
63774
- appendFileSync(path28.join(logDir, dailyFileName(date)), `${JSON.stringify(event)}
63775
- `, {
63776
- mode: 384
63777
- });
63778
- } catch {}
63779
- }
63780
- for (const sink of [options.appLog, options.stderr]) {
63781
- if (!sink)
63782
- continue;
63783
- if (!event)
63784
- continue;
63785
- try {
63786
- sink(event);
63787
- } catch {}
63788
- }
63789
- };
63790
- return {
63791
- debug: (message, context) => emit("debug", message, context),
63792
- info: (message, context) => emit("info", message, context),
63793
- warn: (message, context) => emit("warn", message, context),
63794
- error: (message, context) => emit("error", message, context),
63795
- guard: (name, fn) => {
63796
- try {
63797
- return fn();
63798
- } catch (err) {
63799
- emit("warn", "detector_failed", {
63800
- detector: name,
63801
- error: err instanceof Error ? err.message : String(err)
63802
- });
63803
- return;
63804
- }
63805
- }
63806
- };
63807
- };
63808
- var init_logger = __esm(() => {
63809
- DAY_FILE = /^workit-\d{4}-\d{2}-\d{2}\.jsonl$/;
63810
- SECRET_VALUE = /\b(?:Bearer|Basic|Digest|Token)\s+\S+/gi;
63811
- KEY_EQ_VALUE = /\b([A-Za-z0-9_-]*(?:token|secret|password|passwd|apikey|api[_-]?key|authorization|credential|bearer)[A-Za-z0-9_-]*)([:=]\s*).+/gi;
63812
- URL_QUERY = /(https?:\/\/[^?#\s]+)\?[^#\s]*/g;
63813
- SENSITIVE_WORDS = new Set([
63814
- "token",
63815
- "secret",
63816
- "password",
63817
- "passwd",
63818
- "authorization",
63819
- "credential",
63820
- "credentials",
63821
- "cookie",
63822
- "api",
63823
- "apikey",
63824
- "apitoken",
63825
- "accesstoken",
63826
- "clientsecret",
63827
- "refreshtoken",
63828
- "key",
63829
- "bearer",
63830
- "prompt",
63831
- "message",
63832
- "messages",
63833
- "content",
63834
- "body",
63835
- "args",
63836
- "argument",
63837
- "result",
63838
- "results",
63839
- "output",
63840
- "issue",
63841
- "description",
63842
- "summary",
63843
- "payload",
63844
- "script",
63845
- "command",
63846
- "text",
63847
- "query"
63848
- ]);
63849
- STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
63850
- homePattern = new RegExp(`${escapeRegExp3(os6.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
63851
- DEFAULT_OPTIONS = {
63852
- maxFieldLength: DEFAULT_MAX_FIELD_LENGTH,
63853
- maxStackLines: DEFAULT_MAX_STACK_LINES
63854
- };
63855
- });
63856
-
63857
63908
  // packages/workit-cli/src/admin/host-install.ts
63858
63909
  import { spawnSync as spawnSync12 } from "node:child_process";
63859
63910
  import {
@@ -64447,6 +64498,26 @@ function installSessionHook(cwd, env = process.env) {
64447
64498
  detail: `${verb} the Workit ${SESSION_HOOK_NAME} hook at ${status.hookPath}${helper}`
64448
64499
  };
64449
64500
  }
64501
+ function removeSessionHook(cwd, env = process.env) {
64502
+ const status = inspectSessionHook(cwd, env);
64503
+ if (status.state === "not_git")
64504
+ return { action: "skipped", removed: [], status, detail: "not inside a git work tree" };
64505
+ const removed = [];
64506
+ if (workitHookIn(status)) {
64507
+ rmSync2(status.hookPath, { force: true });
64508
+ removed.push(status.hookPath);
64509
+ }
64510
+ if (helperIn(status)) {
64511
+ rmSync2(status.helperPath, { force: true });
64512
+ removed.push(status.helperPath);
64513
+ }
64514
+ return {
64515
+ action: removed.length ? "removed" : "unchanged",
64516
+ removed,
64517
+ status: inspectSessionHook(cwd, env),
64518
+ detail: removed.length ? `removed ${removed.join(" and ")}` : "no Workit session hook or helper to remove"
64519
+ };
64520
+ }
64450
64521
  var SESSION_HOOK_NAME = "prepare-commit-msg", SESSION_HOOK_MARKER = "# workit-session-trailer-hook", HELPER, LEGACY_CHAINED = "commit-msg.workit-chained", SESSION_TRAILER_SCRIPT = `#!/bin/sh
64451
64522
  # Workit (https://github.com/BrainerVirus/workit) wrote this file with
64452
64523
  # \`workit doctor --fix\`. A prepare-commit-msg hook runs it with its own
@@ -64474,6 +64545,19 @@ fi
64474
64545
  [ -n "$session" ] || exit 0
64475
64546
  [ \${#session} -le 128 ] || exit 0
64476
64547
  case "$session" in *[!A-Za-z0-9_.:@/+-]*) exit 0 ;; esac
64548
+ # A workspace with \`trailers: false\` keeps authorship in the ledger only.
64549
+ # \`workit git commit\` decides it already (WORKIT_TRAILERS); otherwise ask
64550
+ # workit, bounded, and add the trailer when it cannot answer.
64551
+ case "\${WORKIT_TRAILERS-}" in
64552
+ off) exit 0 ;;
64553
+ on) ;;
64554
+ *)
64555
+ if command -v workit >/dev/null 2>&1; then
64556
+ t=; ! command -v timeout >/dev/null 2>&1 || t="timeout 5"
64557
+ [ "$($t workit git trailers 2>/dev/null)" != off ] || exit 0
64558
+ fi
64559
+ ;;
64560
+ esac
64477
64561
  add_trailer() {
64478
64562
  [ -f "$1" ] && [ -r "$1" ] || return 0
64479
64563
  body=$(sed '/^# -\\{24\\} >8 -\\{24\\}$/,$d' "$1" | git stripspace --strip-comments)
@@ -64617,7 +64701,7 @@ exit 0
64617
64701
  } finally {
64618
64702
  rmSync2(staged, { force: true });
64619
64703
  }
64620
- };
64704
+ }, workitHookIn = (status) => status.hookPath !== null && (readText2(status.hookPath)?.includes(SESSION_HOOK_MARKER) ?? false), helperIn = (status) => status.helperPath !== null && exists(status.helperPath);
64621
64705
  var init_session_hook = __esm(() => {
64622
64706
  init_rev();
64623
64707
  HELPER = ["workit", "session-trailer.sh"];
@@ -66052,12 +66136,34 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66052
66136
  };
66053
66137
  }
66054
66138
  }, checkSessionHook = (res) => {
66055
- if (!matchedWorkspace(res))
66139
+ const workspace = matchedWorkspace(res);
66140
+ if (!workspace)
66056
66141
  return {
66057
66142
  id: "session_hook",
66058
66143
  status: "pass",
66059
66144
  detail: `not in a Workit workspace — skipping the ${SESSION_HOOK_NAME} session hook`
66060
66145
  };
66146
+ const trailers = workspaceTrailers(res.cwd, res.configDir);
66147
+ if (!trailers.enabled) {
66148
+ const setting = `workspace ${trailers.from ?? workspace.name} sets trailers: false`;
66149
+ const left = inspectSessionHook(res.cwd, res.env);
66150
+ const files = [
66151
+ ...workitHookIn(left) ? [left.hookPath] : [],
66152
+ ...helperIn(left) ? [left.helperPath] : []
66153
+ ];
66154
+ if (files.length)
66155
+ return {
66156
+ id: "session_hook",
66157
+ status: "warn",
66158
+ detail: `${setting}, but Workit's ${files.join(" and ")} ${files.length > 1 ? "are" : "is"} still installed: plain git commits still get a Workit-Session trailer whenever the helper cannot ask workit (it fails open)`,
66159
+ fix: SESSION_HOOK_FIX
66160
+ };
66161
+ return {
66162
+ id: "session_hook",
66163
+ status: "pass",
66164
+ detail: `${setting} (authorship in the ledger only) — no ${SESSION_HOOK_NAME} session hook needed`
66165
+ };
66166
+ }
66061
66167
  const hook = inspectSessionHook(res.cwd, res.env);
66062
66168
  const finding = sessionHookFinding(hook);
66063
66169
  if (!hook.leftover)
@@ -66077,6 +66183,14 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66077
66183
  detail: `${res.cwd} is not in a Workit workspace; no hook installed`,
66078
66184
  workspace: null
66079
66185
  };
66186
+ if (!workspaceTrailers(res.cwd, res.configDir).enabled) {
66187
+ const removal = removeSessionHook(res.cwd, res.env);
66188
+ return {
66189
+ ...removal,
66190
+ detail: `trailers: false; no hook installed (${removal.detail})`,
66191
+ workspace: workspace.name
66192
+ };
66193
+ }
66080
66194
  return { ...installSessionHook(res.cwd, res.env), workspace: workspace.name };
66081
66195
  }, isPlaceholder = (p) => {
66082
66196
  try {
@@ -66359,7 +66473,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
66359
66473
  fix: repair,
66360
66474
  registryProbed
66361
66475
  };
66362
- }, HOOK_ISSUED_MARKER = "workit-issued-sessions/1", hookRecordsIssued = (root) => {
66476
+ }, HOOK_ISSUED_MARKER = "workit-issued-sessions/2", hookRecordsIssued = (root) => {
66363
66477
  let files;
66364
66478
  try {
66365
66479
  files = readdirSync7(path32.join(root, "dist")).filter((f) => f.endsWith(".js"));
@@ -72515,7 +72629,10 @@ function runCheckCommand(argv, options) {
72515
72629
  const buffer = new TailBuffer(MAX_LOG_BYTES);
72516
72630
  const redactor = new StreamRedactor((text) => buffer.push(text));
72517
72631
  const env = options.env ?? process.env;
72518
- const plan = planSpawn(argv, { platform, env, cwd: options.cwd, shell: options.shell });
72632
+ const wrapped = Boolean(options.wrapper?.length) && platform !== "win32";
72633
+ const command = wrapped ? [...options.wrapper, ...options.shell ? ["/bin/sh", "-c", argv[0]] : argv] : argv;
72634
+ const shell = wrapped ? false : options.shell;
72635
+ const plan = planSpawn(command, { platform, env, cwd: options.cwd, shell });
72519
72636
  return new Promise((resolve) => {
72520
72637
  let settled = false;
72521
72638
  let timedOut = false;
@@ -72578,6 +72695,8 @@ function runCheckCommand(argv, options) {
72578
72695
  finish(null, null, error.code ?? error.message);
72579
72696
  return;
72580
72697
  }
72698
+ if (child.pid)
72699
+ options.onSpawn?.(child.pid);
72581
72700
  if (!windows)
72582
72701
  for (const name of FORWARDED_SIGNALS) {
72583
72702
  const handler = () => {
@@ -72915,18 +73034,503 @@ var init_grant = __esm(() => {
72915
73034
  };
72916
73035
  });
72917
73036
 
73037
+ // packages/workit-core/src/core/resources.ts
73038
+ import { randomUUID as randomUUID5 } from "node:crypto";
73039
+ import fs20 from "node:fs";
73040
+ import os14 from "node:os";
73041
+ import path40 from "node:path";
73042
+ function memoryValue(value, host) {
73043
+ const text = typeof value === "number" ? String(value) : typeof value === "string" ? value : "";
73044
+ const percent = PERCENT.exec(text.trim());
73045
+ if (percent) {
73046
+ const share = Number(percent[1]);
73047
+ if (share <= 0 || share > 100)
73048
+ return new Error(`memoryMax ${text} must be 1-100%`);
73049
+ return Math.floor(host.totalMemBytes * share / 100);
73050
+ }
73051
+ const size = SIZE.exec(text.trim());
73052
+ if (size && Number(size[1]) > 0)
73053
+ return Math.floor(Number(size[1]) * UNITS[size[2].toUpperCase()]);
73054
+ return new Error(`memoryMax must be a percent of RAM ("40%") or a size ("8G"), got ${text}`);
73055
+ }
73056
+ function cpuValue(value, host) {
73057
+ if (typeof value === "number" && value > 0)
73058
+ return Math.round(value * 100);
73059
+ const percent = typeof value === "string" ? PERCENT.exec(value.trim()) : null;
73060
+ if (percent) {
73061
+ const share = Number(percent[1]);
73062
+ if (share <= 0 || share > 100)
73063
+ return new Error(`cpuQuota ${percent[0]} must be 1-100%`);
73064
+ return Math.max(1, Math.round(host.cpus * share));
73065
+ }
73066
+ return new Error(`cpuQuota must be a percent of the machine ("70%") or a CPU count, got ${String(value)}`);
73067
+ }
73068
+ function resolveResourceSettings(sources, host = hostShape()) {
73069
+ let memory = Math.floor(host.totalMemBytes * DEFAULT_MEMORY_SHARE);
73070
+ let cpu = Math.max(1, Math.round(host.cpus * DEFAULT_CPU_SHARE * 100));
73071
+ const settings = {
73072
+ slots: DEFAULT_SLOTS,
73073
+ heavy: [...DEFAULT_HEAVY_CHECKS],
73074
+ memoryMax: "",
73075
+ cpuQuota: "",
73076
+ waitMs: DEFAULT_WAIT_S * 1000,
73077
+ scope: true
73078
+ };
73079
+ const warnings = [];
73080
+ const ordered = [
73081
+ ...sources.filter((source) => source.trust === "machine"),
73082
+ ...sources.filter((source) => source.trust === "repo")
73083
+ ];
73084
+ for (const { label, trust, value } of ordered) {
73085
+ if (value === undefined || value === null)
73086
+ continue;
73087
+ if (!isRecord7(value)) {
73088
+ warnings.push(`${label}: resources must be an object`);
73089
+ continue;
73090
+ }
73091
+ const repo = trust === "repo";
73092
+ const warn = (field, message) => warnings.push(`${label}: ${field}: ${message}`);
73093
+ const machineOnly = (field) => warn(field, "is machine-wide; set it in your workspaces.json, not in the repo");
73094
+ if (value.slots !== undefined) {
73095
+ const slots = value.slots;
73096
+ if (repo)
73097
+ machineOnly("slots");
73098
+ else if (typeof slots === "number" && Number.isInteger(slots) && slots >= 1 && slots <= MAX_SLOTS)
73099
+ settings.slots = slots;
73100
+ else
73101
+ warn("slots", `must be an integer 1-${MAX_SLOTS}`);
73102
+ }
73103
+ if (value.scope !== undefined) {
73104
+ if (repo)
73105
+ machineOnly("scope");
73106
+ else if (typeof value.scope === "boolean")
73107
+ settings.scope = value.scope;
73108
+ else
73109
+ warn("scope", "must be true or false");
73110
+ }
73111
+ if (value.heavy !== undefined) {
73112
+ const heavy = isRecord7(value.heavy) ? Object.keys(value.heavy) : value.heavy;
73113
+ if (!Array.isArray(heavy) || !heavy.every((item) => typeof item === "string"))
73114
+ warn("heavy", "must be a list of check names");
73115
+ else
73116
+ settings.heavy = repo ? [...new Set([...settings.heavy, ...heavy])] : heavy;
73117
+ }
73118
+ if (value.memoryMax !== undefined) {
73119
+ const next = memoryValue(value.memoryMax, host);
73120
+ if (next instanceof Error)
73121
+ warn("memoryMax", next.message);
73122
+ else if (repo && next > memory)
73123
+ warn("memoryMax", "a repo can only lower it");
73124
+ else
73125
+ memory = next;
73126
+ }
73127
+ if (value.cpuQuota !== undefined) {
73128
+ const next = cpuValue(value.cpuQuota, host);
73129
+ if (next instanceof Error)
73130
+ warn("cpuQuota", next.message);
73131
+ else if (repo && next > cpu)
73132
+ warn("cpuQuota", "a repo can only lower it");
73133
+ else
73134
+ cpu = next;
73135
+ }
73136
+ if (value.wait !== undefined) {
73137
+ if (typeof value.wait === "number" && value.wait >= 0)
73138
+ settings.waitMs = value.wait * 1000;
73139
+ else
73140
+ warn("wait", "must be a number of seconds");
73141
+ }
73142
+ }
73143
+ settings.memoryMax = String(memory);
73144
+ settings.cpuQuota = `${cpu}%`;
73145
+ return { settings, warnings };
73146
+ }
73147
+ function stateDir(env, platform = process.platform) {
73148
+ if (env.WORKFLOW_TOOLKIT_STATE)
73149
+ return env.WORKFLOW_TOOLKIT_STATE;
73150
+ if (env.XDG_STATE_HOME)
73151
+ return path40.join(env.XDG_STATE_HOME, "workit");
73152
+ const home = env.HOME || os14.homedir();
73153
+ if (platform === "darwin")
73154
+ return path40.join(home, "Library", "Application Support", "workit");
73155
+ if (platform === "win32")
73156
+ return path40.join(env.LOCALAPPDATA ?? path40.join(home, "AppData", "Local"), "workit");
73157
+ return path40.join(home, ".local", "state", "workit");
73158
+ }
73159
+ function scopeBusy(unit) {
73160
+ if (process.platform !== "linux" || !SCOPE_UNIT.test(unit))
73161
+ return false;
73162
+ const uid = process.getuid?.() ?? 0;
73163
+ const manager = `/sys/fs/cgroup/user.slice/user-${uid}.slice/user@${uid}.service`;
73164
+ return [`${manager}/app.slice/${unit}`, `${manager}/${unit}`].some((dir) => (readText3(`${dir}/cgroup.procs`) ?? "").trim() !== "");
73165
+ }
73166
+ function isAncestor(pid) {
73167
+ if (process.platform !== "linux")
73168
+ return true;
73169
+ let current = process.pid;
73170
+ for (let step = 0;step < 128 && current > 1; step += 1) {
73171
+ if (current === pid)
73172
+ return true;
73173
+ const ppid = Number(statFields(current)?.[1]);
73174
+ if (!Number.isInteger(ppid) || ppid <= 0)
73175
+ return false;
73176
+ current = ppid;
73177
+ }
73178
+ return current === pid;
73179
+ }
73180
+ function staleReason(lease, probe = {}) {
73181
+ const boot = probe.boot === undefined ? bootId() : probe.boot;
73182
+ if (lease.bootId && boot) {
73183
+ if (lease.bootId !== boot)
73184
+ return "taken before a reboot";
73185
+ } else if (typeof lease.bootTime === "number") {
73186
+ const now = probe.bootTime === undefined ? bootTime() : probe.bootTime;
73187
+ if (now !== null && Math.abs(now - lease.bootTime) > BOOT_SLACK_MS)
73188
+ return "taken before a reboot";
73189
+ }
73190
+ const start = lease.processStart ? (probe.start ?? processStart)(lease.pid) : null;
73191
+ const age = (probe.now ?? Date.now()) - Date.parse(lease.startedAt);
73192
+ if (!start && typeof lease.maxAgeMs === "number" && age > lease.maxAgeMs)
73193
+ return `older than its ${Math.round(lease.maxAgeMs / 1000)} s bound`;
73194
+ const holder = !(probe.alive ?? pidAlive2)(lease.pid) ? `holder pid ${lease.pid} is gone` : start && start !== lease.processStart ? `pid ${lease.pid} now belongs to another process` : null;
73195
+ if (!holder)
73196
+ return null;
73197
+ if (typeof lease.pgid === "number" && lease.pgid > 0 && (probe.group ?? groupAlive)(lease.pgid))
73198
+ return null;
73199
+ if (lease.scope && (probe.scope ?? scopeBusy)(lease.scope))
73200
+ return null;
73201
+ return holder;
73202
+ }
73203
+ function reclaim(file, stale) {
73204
+ const guard = `${file}.reclaim`;
73205
+ try {
73206
+ fs20.mkdirSync(guard);
73207
+ } catch {
73208
+ try {
73209
+ if (Date.now() - fs20.statSync(guard).mtimeMs > GUARD_TTL_MS)
73210
+ fs20.rmSync(guard, { force: true, recursive: true });
73211
+ } catch {}
73212
+ return false;
73213
+ }
73214
+ try {
73215
+ let ageMs = 0;
73216
+ try {
73217
+ ageMs = Date.now() - fs20.statSync(file).mtimeMs;
73218
+ } catch {
73219
+ return true;
73220
+ }
73221
+ if (!stale(parseLease(readText3(file)), ageMs))
73222
+ return false;
73223
+ fs20.rmSync(file, { force: true });
73224
+ return true;
73225
+ } finally {
73226
+ fs20.rmSync(guard, { force: true, recursive: true });
73227
+ }
73228
+ }
73229
+ function tryOnce(options, record, reclaimed) {
73230
+ const holders = [];
73231
+ const text = `${JSON.stringify(record)}
73232
+ `;
73233
+ for (let slot = 0;slot < options.slots; slot += 1) {
73234
+ const file = path40.join(options.dir, `slot-${slot}.json`);
73235
+ for (let attempt = 0;attempt < 2; attempt += 1) {
73236
+ try {
73237
+ const fd = fs20.openSync(file, "wx", 384);
73238
+ try {
73239
+ fs20.writeSync(fd, text);
73240
+ } finally {
73241
+ fs20.closeSync(fd);
73242
+ }
73243
+ return { slot, file };
73244
+ } catch (error) {
73245
+ if (error.code !== "EEXIST")
73246
+ throw error;
73247
+ }
73248
+ const lease = parseLease(readText3(file));
73249
+ let reason = null;
73250
+ const removed = reclaim(file, (current, ageMs) => {
73251
+ reason = current ? staleReason(current, options.probe) : ageMs > UNREADABLE_TTL_MS ? "unreadable" : null;
73252
+ return reason !== null;
73253
+ });
73254
+ if (removed) {
73255
+ if (lease && reason)
73256
+ reclaimed.push({ ...holderOf(lease), reason });
73257
+ continue;
73258
+ }
73259
+ if (lease)
73260
+ holders.push(holderOf(lease));
73261
+ break;
73262
+ }
73263
+ }
73264
+ return { holders };
73265
+ }
73266
+ function removeOwn(file, id) {
73267
+ if (parseLease(readText3(file))?.id === id)
73268
+ fs20.rmSync(file, { force: true });
73269
+ }
73270
+ async function acquireLease(options) {
73271
+ const pid = options.pid ?? process.pid;
73272
+ const boot = bootId();
73273
+ const record = {
73274
+ id: randomUUID5(),
73275
+ pid,
73276
+ processStart: processStart(pid),
73277
+ bootId: boot,
73278
+ bootTime: boot ? null : bootTime(),
73279
+ pgid: null,
73280
+ maxAgeMs: options.maxAgeMs ?? null,
73281
+ check: options.check,
73282
+ cwd: options.cwd,
73283
+ session: options.session,
73284
+ startedAt: new Date().toISOString(),
73285
+ scope: null
73286
+ };
73287
+ try {
73288
+ fs20.mkdirSync(options.dir, { recursive: true, mode: 448 });
73289
+ } catch (error) {
73290
+ return { ok: false, reason: "unavailable", error: error.message };
73291
+ }
73292
+ const started = Date.now();
73293
+ const reclaimed = [];
73294
+ let waitedOn = [];
73295
+ for (;; ) {
73296
+ let outcome;
73297
+ try {
73298
+ outcome = tryOnce(options, record, reclaimed);
73299
+ } catch (error) {
73300
+ return { ok: false, reason: "unavailable", error: error.message };
73301
+ }
73302
+ if ("slot" in outcome) {
73303
+ const { file, slot } = outcome;
73304
+ let released = false;
73305
+ const release = () => {
73306
+ if (released)
73307
+ return;
73308
+ released = true;
73309
+ process.off("exit", release);
73310
+ removeOwn(file, record.id);
73311
+ };
73312
+ process.on("exit", release);
73313
+ return {
73314
+ ok: true,
73315
+ lease: {
73316
+ record,
73317
+ slot,
73318
+ file,
73319
+ waitedOn,
73320
+ waitedMs: waitedOn.length ? Date.now() - started : 0,
73321
+ reclaimed,
73322
+ release
73323
+ }
73324
+ };
73325
+ }
73326
+ const waited = Date.now() - started;
73327
+ if (!waitedOn.length) {
73328
+ waitedOn = outcome.holders;
73329
+ options.onWait?.(outcome.holders);
73330
+ }
73331
+ if (waited >= options.waitMs)
73332
+ return { ok: false, reason: "timeout", holders: outcome.holders, waitedMs: waited };
73333
+ await sleep(Math.min(options.pollMs ?? 2000, Math.max(1, options.waitMs - waited)));
73334
+ }
73335
+ }
73336
+ function updateLease(lease, patch) {
73337
+ Object.assign(lease.record, patch);
73338
+ try {
73339
+ if (parseLease(readText3(lease.file))?.id !== lease.record.id)
73340
+ return;
73341
+ const temp = `${lease.file}.${process.pid}.tmp`;
73342
+ fs20.writeFileSync(temp, `${JSON.stringify(lease.record)}
73343
+ `, { mode: 384 });
73344
+ fs20.renameSync(temp, lease.file);
73345
+ } catch {}
73346
+ }
73347
+ function heldLease(dir, id, options = {}) {
73348
+ if (!id || !/^[0-9a-f-]{36}$/u.test(id))
73349
+ return null;
73350
+ let names;
73351
+ try {
73352
+ names = fs20.readdirSync(dir);
73353
+ } catch {
73354
+ return null;
73355
+ }
73356
+ for (const name of names) {
73357
+ if (!/^slot-\d+\.json$/u.test(name))
73358
+ continue;
73359
+ const lease = parseLease(readText3(path40.join(dir, name)));
73360
+ if (lease?.id === id && !staleReason(lease, options.probe) && (options.ancestor ?? isAncestor)(lease.pid))
73361
+ return lease;
73362
+ }
73363
+ return null;
73364
+ }
73365
+ function describeHolder(holder, now = Date.now()) {
73366
+ const seconds = Math.max(0, Math.round((now - Date.parse(holder.startedAt)) / 1000));
73367
+ const age = seconds >= 60 ? `${Math.floor(seconds / 60)}m ${seconds % 60}s` : `${seconds}s`;
73368
+ return `${holder.check}, pid ${holder.pid}${holder.session ? `, session ${holder.session}` : ""}, ${age}, ${holder.cwd}`;
73369
+ }
73370
+ function scopeSupport(env, platform = process.platform, read = readText3) {
73371
+ if (platform !== "linux")
73372
+ return { ok: false, reason: `no systemd scopes on ${platform}` };
73373
+ if (!onPath("systemd-run", env))
73374
+ return { ok: false, reason: "systemd-run is not on PATH" };
73375
+ const uid = process.getuid?.() ?? 0;
73376
+ const runtime = env.XDG_RUNTIME_DIR || `/run/user/${uid}`;
73377
+ if (!fs20.existsSync(path40.join(runtime, "systemd", "private")))
73378
+ return { ok: false, reason: "no user systemd manager is running" };
73379
+ const controllers = read(`/sys/fs/cgroup/user.slice/user-${uid}.slice/user@${uid}.service/cgroup.controllers`);
73380
+ if (controllers === null)
73381
+ return { ok: false, reason: "cgroup v2 is not mounted for the user manager" };
73382
+ const missing = ["memory", "cpu"].filter((name) => !controllers.split(/\s+/u).includes(name));
73383
+ if (missing.length)
73384
+ return { ok: false, reason: `the user manager is not delegated ${missing.join(" and ")}` };
73385
+ return { ok: true };
73386
+ }
73387
+ function scopePrefix(unit, settings) {
73388
+ return [
73389
+ "systemd-run",
73390
+ "--user",
73391
+ "--scope",
73392
+ "--quiet",
73393
+ "--collect",
73394
+ `--unit=${unit}`,
73395
+ "-p",
73396
+ `MemoryMax=${settings.memoryMax}`,
73397
+ "-p",
73398
+ `CPUQuota=${settings.cpuQuota}`,
73399
+ "--"
73400
+ ];
73401
+ }
73402
+ async function runScoped(run, wrapper, sinks) {
73403
+ if (!wrapper)
73404
+ return { result: await run(null, sinks), fallback: null };
73405
+ let state = "deciding";
73406
+ let firstLine = "";
73407
+ const held = [];
73408
+ const flush = () => {
73409
+ state = "passing";
73410
+ for (const [sink, text] of held.splice(0))
73411
+ sinks[sink](text);
73412
+ };
73413
+ const result = await run(wrapper, {
73414
+ onStdout: (text) => {
73415
+ if (state === "passing")
73416
+ return sinks.onStdout(text);
73417
+ held.push(["onStdout", text]);
73418
+ if (state === "deciding")
73419
+ flush();
73420
+ },
73421
+ onStderr: (text) => {
73422
+ if (state === "passing")
73423
+ return sinks.onStderr(text);
73424
+ held.push(["onStderr", text]);
73425
+ if (state === "held")
73426
+ return;
73427
+ firstLine += text;
73428
+ const newline = firstLine.indexOf(`
73429
+ `);
73430
+ if (newline < 0 && firstLine.length < 4096)
73431
+ return;
73432
+ if (SCOPE_START_FAILURE.test(firstLine.slice(0, newline < 0 ? undefined : newline)))
73433
+ state = "held";
73434
+ else
73435
+ flush();
73436
+ }
73437
+ });
73438
+ const lines = tailLines(result.log, 2);
73439
+ if (state !== "passing" && result.exitCode !== 0 && lines.length === 1 && SCOPE_START_FAILURE.test(lines[0]))
73440
+ return { result: await run(null, sinks), fallback: lines[0] };
73441
+ flush();
73442
+ return { result, fallback: null };
73443
+ }
73444
+ function firstNotice(env, session, key, now = Date.now()) {
73445
+ const id = (session ?? `ppid-${process.ppid}`).replace(/[^A-Za-z0-9_.-]/gu, "_").slice(0, 120);
73446
+ const dir = path40.join(stateDir(env), "notices");
73447
+ try {
73448
+ fs20.mkdirSync(dir, { recursive: true, mode: 448 });
73449
+ fs20.writeFileSync(path40.join(dir, `${key}-${id}`), `${new Date(now).toISOString()}
73450
+ `, {
73451
+ flag: "wx",
73452
+ mode: 384
73453
+ });
73454
+ } catch (error) {
73455
+ return error.code !== "EEXIST";
73456
+ }
73457
+ try {
73458
+ for (const name of fs20.readdirSync(dir)) {
73459
+ const file = path40.join(dir, name);
73460
+ if (now - fs20.statSync(file).mtimeMs > NOTICE_TTL_MS)
73461
+ fs20.rmSync(file, { force: true });
73462
+ }
73463
+ } catch {}
73464
+ return true;
73465
+ }
73466
+ var DEFAULT_HEAVY_CHECKS, DEFAULT_SLOTS = 1, DEFAULT_WAIT_S = 600, DEFAULT_MEMORY_SHARE = 0.4, DEFAULT_CPU_SHARE = 0.7, MAX_SLOTS = 64, UNREADABLE_TTL_MS = 30000, GUARD_TTL_MS = 30000, hostShape = () => ({
73467
+ totalMemBytes: os14.totalmem(),
73468
+ cpus: Math.max(1, os14.availableParallelism?.() ?? os14.cpus().length)
73469
+ }), isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SIZE, PERCENT, UNITS, leaseDir = (env) => path40.join(stateDir(env), "leases"), readText3 = (file) => {
73470
+ try {
73471
+ return fs20.readFileSync(file, "utf8");
73472
+ } catch {
73473
+ return null;
73474
+ }
73475
+ }, bootId = () => readText3("/proc/sys/kernel/random/boot_id")?.trim() || null, bootTime = () => Math.round((Date.now() - os14.uptime() * 1000) / 1000) * 1000, BOOT_SLACK_MS = 120000, statFields = (pid) => {
73476
+ const stat = readText3(`/proc/${pid}/stat`);
73477
+ return stat ? stat.slice(stat.lastIndexOf(")") + 2).split(" ") : null;
73478
+ }, processStart = (pid) => statFields(pid)?.[19] ?? null, pidAlive2 = (pid) => {
73479
+ try {
73480
+ process.kill(pid, 0);
73481
+ return true;
73482
+ } catch (error) {
73483
+ return error.code === "EPERM";
73484
+ }
73485
+ }, groupAlive = (pgid) => process.platform === "win32" ? pidAlive2(pgid) : pidAlive2(-pgid), SCOPE_UNIT, parseLease = (text) => {
73486
+ if (!text)
73487
+ return null;
73488
+ try {
73489
+ const value = JSON.parse(text);
73490
+ return isRecord7(value) && typeof value.id === "string" && typeof value.pid === "number" ? value : null;
73491
+ } catch {
73492
+ return null;
73493
+ }
73494
+ }, holderOf = (lease) => ({
73495
+ pid: lease.pid,
73496
+ check: lease.check,
73497
+ cwd: lease.cwd,
73498
+ session: lease.session,
73499
+ startedAt: lease.startedAt
73500
+ }), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), onPath = (command, env) => (env.PATH ?? "").split(path40.delimiter).filter(Boolean).some((dir) => {
73501
+ try {
73502
+ fs20.accessSync(path40.join(dir, command), fs20.constants.X_OK);
73503
+ return true;
73504
+ } catch {
73505
+ return false;
73506
+ }
73507
+ }), SCOPE_START_FAILURE, NOTICE_TTL_MS;
73508
+ var init_resources = __esm(() => {
73509
+ init_checks3();
73510
+ DEFAULT_HEAVY_CHECKS = ["test", "build", "typecheck"];
73511
+ SIZE = /^(\d+(?:\.\d+)?)([KMGT]?)$/iu;
73512
+ PERCENT = /^(\d+(?:\.\d+)?)%$/u;
73513
+ UNITS = { "": 1, K: 1024, M: 1024 ** 2, G: 1024 ** 3, T: 1024 ** 4 };
73514
+ SCOPE_UNIT = /^[\w.@-]+\.scope$/u;
73515
+ SCOPE_START_FAILURE = /^Failed to (?:connect to bus|create bus connection|start transient scope unit|parse \S+?=)/u;
73516
+ NOTICE_TTL_MS = 7 * 86400000;
73517
+ });
73518
+
72918
73519
  // packages/workit-cli/src/verbs/check.ts
72919
73520
  var exports_check = {};
72920
73521
  __export(exports_check, {
72921
73522
  run: () => run10
72922
73523
  });
72923
- import path40 from "node:path";
73524
+ import os15 from "node:os";
73525
+ import path41 from "node:path";
72924
73526
  function parse5(argv) {
72925
73527
  const split = argv.indexOf("--");
72926
73528
  const head = split >= 0 ? argv.slice(0, split) : [...argv];
72927
73529
  const options = {
72928
73530
  name: null,
72929
73531
  shell: false,
73532
+ heavy: false,
73533
+ noLease: false,
72930
73534
  timeoutMs: 0,
72931
73535
  task: null,
72932
73536
  base: null,
@@ -72946,6 +73550,13 @@ function parse5(argv) {
72946
73550
  options.shell = true;
72947
73551
  continue;
72948
73552
  }
73553
+ if (flag === "--heavy" || flag === "--no-lease") {
73554
+ if (flag === "--heavy")
73555
+ options.heavy = true;
73556
+ else
73557
+ options.noLease = true;
73558
+ continue;
73559
+ }
72949
73560
  if (flag === "--name" || flag === "--task" || flag === "--base" || flag === "--timeout") {
72950
73561
  const got = value();
72951
73562
  if (got instanceof Error)
@@ -73069,6 +73680,90 @@ function boundedArgv(argv) {
73069
73680
  const truncated = cut.length !== argv.length || cut.some((item, index) => item !== argv[index]);
73070
73681
  return { argv: cut, truncated };
73071
73682
  }
73683
+ async function workspaceResources(io) {
73684
+ const env = io.env;
73685
+ const dir = env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path41.join(env.XDG_CONFIG_HOME || path41.join(env.HOME || os15.homedir(), ".config"), "workit");
73686
+ try {
73687
+ await Promise.resolve().then(() => init_workspaces());
73688
+ const workspace = resolveWorkspaceFrom(io.cwd, dir, env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
73689
+ return { value: workspace?.resources, warning: null };
73690
+ } catch (error) {
73691
+ return { value: undefined, warning: `workspaces.json: ${error.message}` };
73692
+ }
73693
+ }
73694
+ async function prepareHeavy(io, settings, label, runCwd, timeoutMs) {
73695
+ const session = hostSessionFromEnv(io.env).session;
73696
+ const dir = leaseDir(io.env);
73697
+ const outer = heldLease(dir, io.env.WORKIT_LEASE);
73698
+ if (outer)
73699
+ return {
73700
+ lease: null,
73701
+ leaseReport: { slot: null, waitedMs: 0, holder: null, reclaimed: [], inherited: true },
73702
+ scope: { unit: outer.scope, reason: "inherited from the outer heavy check" },
73703
+ wrapper: null,
73704
+ env: io.env
73705
+ };
73706
+ const acquired = await acquireLease({
73707
+ dir,
73708
+ slots: settings.slots,
73709
+ check: label,
73710
+ cwd: runCwd,
73711
+ session,
73712
+ waitMs: settings.waitMs,
73713
+ pollMs: 1000,
73714
+ maxAgeMs: (timeoutMs || MAX_UNVERIFIED_RUN_MS) + 60000,
73715
+ onWait: (holders) => io.stderr(`workit: waiting for the heavy-check lease (${settings.slots} slot${settings.slots === 1 ? "" : "s"}): held by ${holders.map((holder) => describeHolder(holder)).join("; ")}
73716
+ `)
73717
+ });
73718
+ if (!acquired.ok && acquired.reason === "timeout") {
73719
+ const holders = acquired.holders.map((holder) => describeHolder(holder)).join("; ");
73720
+ emit(io, fail("busy", `the heavy-check lease stayed busy for ${Math.round(acquired.waitedMs / 1000)} s`, {
73721
+ data: { lease: { waitedMs: acquired.waitedMs, holders: acquired.holders } },
73722
+ unblock: `wait for ${holders || "the holders"} to finish, then run it again`
73723
+ }));
73724
+ return 4;
73725
+ }
73726
+ let lease = null;
73727
+ if (acquired.ok)
73728
+ lease = acquired.lease;
73729
+ else
73730
+ io.stderr(`workit: running without the heavy-check lease: ${acquired.error}
73731
+ `);
73732
+ for (const stale of lease?.reclaimed ?? [])
73733
+ io.stderr(`workit: reclaimed a stale heavy-check lease (${stale.reason}): ${describeHolder(stale)}
73734
+ `);
73735
+ const leaseReport = lease ? {
73736
+ slot: lease.slot,
73737
+ waitedMs: lease.waitedMs,
73738
+ holder: lease.waitedOn[0] ?? null,
73739
+ reclaimed: lease.reclaimed,
73740
+ inherited: false
73741
+ } : null;
73742
+ const env = lease ? { ...io.env, WORKIT_LEASE: lease.record.id } : io.env;
73743
+ const support = settings.scope ? scopeSupport(io.env) : { ok: false, reason: "resources.scope is false" };
73744
+ if (!support.ok) {
73745
+ if (settings.scope && firstNotice(io.env, session, "no-scope"))
73746
+ io.stderr(`workit: heavy checks run without a memory/CPU limit here: ${support.reason} (said once per session)
73747
+ `);
73748
+ return {
73749
+ lease,
73750
+ leaseReport,
73751
+ scope: { unit: null, reason: support.reason },
73752
+ wrapper: null,
73753
+ env
73754
+ };
73755
+ }
73756
+ const unit = `workit-check-${(lease?.record.id ?? "nolease0").slice(0, 8)}-${process.pid}`;
73757
+ if (lease)
73758
+ updateLease(lease, { scope: `${unit}.scope` });
73759
+ return {
73760
+ lease,
73761
+ leaseReport,
73762
+ scope: { unit: `${unit}.scope`, memoryMax: settings.memoryMax, cpuQuota: settings.cpuQuota },
73763
+ wrapper: scopePrefix(unit, settings),
73764
+ env
73765
+ };
73766
+ }
73072
73767
  async function run10(argv, io) {
73073
73768
  const options = parse5(argv);
73074
73769
  if (options instanceof Error)
@@ -73103,21 +73798,52 @@ async function run10(argv, io) {
73103
73798
  if (options.shell && command.length !== 1)
73104
73799
  return emit(io, fail("invalid_input", '--shell takes the command as one string: workit check --shell -- "<cmd>"'));
73105
73800
  }
73106
- const relCwd = posix(path40.relative(top, runCwd));
73801
+ const relCwd = posix(path41.relative(top, runCwd));
73107
73802
  const configured = !options.shell && matchesNamedCheck(config, name, command, relCwd);
73108
73803
  const actor = actorFromEnv(io.env);
73109
73804
  const branchKey = codeKey(io.cwd, { base: options.base });
73110
73805
  const detached = branchKey.tree === null ? worktreeTree(io.cwd) : null;
73111
73806
  const key = detached ? { ...branchKey, tree: detached.key, dirty: detached.dirty } : branchKey;
73112
73807
  const signal = key.tree === null ? null : worktreeSignal(io.cwd);
73113
- const result = await runCheckCommand(command, {
73114
- cwd: runCwd,
73115
- shell: options.shell,
73116
- timeoutMs: options.timeoutMs,
73117
- env: io.env,
73118
- onStdout: io.json ? io.stderr : io.stdout,
73119
- onStderr: io.stderr
73120
- });
73808
+ let heavy = null;
73809
+ if (!options.noLease && (options.heavy || options.command === null)) {
73810
+ const workspace = await workspaceResources(io);
73811
+ const { settings, warnings } = resolveResourceSettings([
73812
+ { label: "workspaces.json", trust: "machine", value: workspace.value },
73813
+ { label: CHECKS_FILE, trust: "repo", value: config.resources }
73814
+ ]);
73815
+ for (const warning of [workspace.warning, ...warnings])
73816
+ if (warning)
73817
+ io.stderr(`workit: resources: ${warning}
73818
+ `);
73819
+ if (options.heavy || name !== null && settings.heavy.includes(name)) {
73820
+ const prepared = await prepareHeavy(io, settings, name ?? command.join(" "), runCwd, options.timeoutMs);
73821
+ if (typeof prepared === "number")
73822
+ return prepared;
73823
+ heavy = prepared;
73824
+ }
73825
+ }
73826
+ const lease = heavy?.lease ?? null;
73827
+ let result;
73828
+ try {
73829
+ const scoped = await runScoped((wrapper, sinks) => runCheckCommand(command, {
73830
+ cwd: runCwd,
73831
+ shell: options.shell,
73832
+ timeoutMs: options.timeoutMs,
73833
+ env: heavy?.env ?? io.env,
73834
+ ...sinks,
73835
+ ...wrapper ? { wrapper } : {},
73836
+ ...lease ? { onSpawn: (pid) => updateLease(lease, { pgid: pid }) } : {}
73837
+ }), heavy?.wrapper ?? null, { onStdout: io.json ? io.stderr : io.stdout, onStderr: io.stderr });
73838
+ result = scoped.result;
73839
+ if (heavy && scoped.fallback) {
73840
+ io.stderr(`workit: the memory/CPU scope could not start (${scoped.fallback}); ran without it
73841
+ `);
73842
+ heavy.scope = { unit: null, reason: `systemd-run failed: ${scoped.fallback}` };
73843
+ }
73844
+ } finally {
73845
+ lease?.release();
73846
+ }
73121
73847
  const treeAfter = key.tree === null ? null : worktreeTree(io.cwd)?.key ?? null;
73122
73848
  const modifiedWorktree = key.tree !== null && treeAfter !== key.tree;
73123
73849
  const store = storeRoot(io.cwd);
@@ -73198,6 +73924,8 @@ async function run10(argv, io) {
73198
73924
  outputBytes: result.outputBytes,
73199
73925
  logTruncated: result.truncated,
73200
73926
  task: task.task,
73927
+ lease: heavy?.leaseReport ?? null,
73928
+ scope: heavy?.scope ?? null,
73201
73929
  ...task.note ? { taskNote: task.note } : {},
73202
73930
  satisfies: task.satisfies,
73203
73931
  stillUnsatisfied: task.stillUnsatisfied,
@@ -73227,7 +73955,7 @@ async function run10(argv, io) {
73227
73955
  return recordError.code === "busy" ? 4 : recordError.code === "invalid_input" ? 2 : 5;
73228
73956
  return 0;
73229
73957
  }
73230
- var USAGE3 = "workit check <name> | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--json] -- <cmd…>", RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(path40.sep).join("/") || ".", ENGINE_CODES2, FINGERPRINT_VARS;
73958
+ var USAGE3 = "workit check <name> [--no-lease] | workit check [--name <n>] [--shell] [--heavy] [--timeout <s>] [--task <id>] [--json] -- <cmd…>", MAX_UNVERIFIED_RUN_MS, RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(path41.sep).join("/") || ".", ENGINE_CODES2, FINGERPRINT_VARS;
73231
73959
  var init_check = __esm(() => {
73232
73960
  init_check_config();
73233
73961
  init_checks3();
@@ -73236,6 +73964,8 @@ var init_check = __esm(() => {
73236
73964
  init_ledger();
73237
73965
  init_output();
73238
73966
  init_host_session();
73967
+ init_resources();
73968
+ MAX_UNVERIFIED_RUN_MS = 6 * 3600000;
73239
73969
  ENGINE_CODES2 = {
73240
73970
  busy: "busy",
73241
73971
  not_found: "not_found",
@@ -73251,7 +73981,7 @@ var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock)
73251
73981
  // packages/workit-core/src/git/ops.ts
73252
73982
  import { spawnSync as spawnSync18 } from "node:child_process";
73253
73983
  import { existsSync as existsSync19, readFileSync as readFileSync19 } from "node:fs";
73254
- import path41 from "node:path";
73984
+ import path42 from "node:path";
73255
73985
  function operationInProgress(cwd) {
73256
73986
  const markers = [
73257
73987
  ["rebase-merge", "rebase"],
@@ -73264,18 +73994,18 @@ function operationInProgress(cwd) {
73264
73994
  if (!run.ok)
73265
73995
  return null;
73266
73996
  const found = run.stdout.split(`
73267
- `).map((line) => path41.resolve(cwd, line.trim()));
73997
+ `).map((line) => path42.resolve(cwd, line.trim()));
73268
73998
  const conflicted = () => git5(cwd, ["ls-files", "-u"]).stdout.trim() !== "";
73269
73999
  for (const [index, [, operation]] of markers.entries()) {
73270
74000
  const marker = found[index];
73271
74001
  if (!marker || !existsSync19(marker))
73272
74002
  continue;
73273
- if (index === 1 && existsSync19(path41.join(marker, "applying")))
74003
+ if (index === 1 && existsSync19(path42.join(marker, "applying")))
73274
74004
  return { operation: "am", commitAllowed: false, branch: null };
73275
- if (index === 0 && existsSync19(path41.join(marker, "amend")) && !conflicted()) {
74005
+ if (index === 0 && existsSync19(path42.join(marker, "amend")) && !conflicted()) {
73276
74006
  let headName = "";
73277
74007
  try {
73278
- headName = readFileSync19(path41.join(marker, "head-name"), "utf8").trim();
74008
+ headName = readFileSync19(path42.join(marker, "head-name"), "utf8").trim();
73279
74009
  } catch {}
73280
74010
  const branch = headName.startsWith("refs/heads/") ? headName.slice("refs/heads/".length) : null;
73281
74011
  return { operation, commitAllowed: branch !== null, branch };
@@ -73425,6 +74155,40 @@ function lintCommitMessage(cwd, message) {
73425
74155
  correction: `use a ${detected} commit subject`
73426
74156
  };
73427
74157
  }
74158
+ function stripSessionTrailers(text, options) {
74159
+ const end = text.length - (/\s*$/u.exec(text)?.[0].length ?? 0);
74160
+ const lines = text.slice(0, end).split(`
74161
+ `);
74162
+ let start = lines.length;
74163
+ while (start > 0 && lines[start - 1]?.trim() !== "")
74164
+ start -= 1;
74165
+ if (start === 0 && options.subject)
74166
+ return text;
74167
+ const block = lines.slice(start);
74168
+ if (!block.length || !TRAILER_LINE.test(block[0] ?? ""))
74169
+ return text;
74170
+ if (!block.every((line) => TRAILER_LINE.test(line) || /^\s+\S/u.test(line)))
74171
+ return text;
74172
+ const kept = [];
74173
+ let dropping = false;
74174
+ for (const line of block) {
74175
+ if (TRAILER_LINE.test(line))
74176
+ dropping = SESSION_KEY.test(line);
74177
+ if (!dropping)
74178
+ kept.push(line);
74179
+ }
74180
+ if (kept.length === block.length)
74181
+ return text;
74182
+ let head = lines.slice(0, start);
74183
+ if (!kept.length) {
74184
+ let cut = head.length;
74185
+ while (cut > 0 && head[cut - 1]?.trim() === "")
74186
+ cut -= 1;
74187
+ head = head.slice(0, cut);
74188
+ }
74189
+ return [...head, ...kept].join(`
74190
+ `) + text.slice(end);
74191
+ }
73428
74192
  function messageTrailers(cwd, message) {
73429
74193
  const run = git5(cwd, [...TRAILER_CONFIG, "interpret-trailers", "--parse", "--no-divider"], {
73430
74194
  input: `${message}
@@ -73484,6 +74248,12 @@ function gitCommit(cwd, input) {
73484
74248
  const given = input.message?.trim() ?? "";
73485
74249
  if (!given && !input.amend)
73486
74250
  return failure3("invalid_input", "a commit message is required (-m <msg> or -F <file>)");
74251
+ const extra = (input.trailers ?? []).map((trailer) => trailer.trim());
74252
+ const malformed = extra.find((trailer) => !EXTRA_TRAILER.test(trailer));
74253
+ if (malformed !== undefined)
74254
+ return failure3("invalid_input", `--trailer ${JSON.stringify(malformed)} is not one "Key: value" line`, `workit git commit -m '<msg>' --trailer "Co-Authored-By: Name <email>"`);
74255
+ if (extra.some((trailer) => /^workit-session:/iu.test(trailer)))
74256
+ return failure3("invalid_input", "--trailer cannot set Workit-Session: workit writes it from the acting session");
73487
74257
  const paths = input.paths ?? [];
73488
74258
  if (input.all && paths.length)
73489
74259
  return failure3("invalid_input", "pass --all or paths, not both");
@@ -73505,6 +74275,7 @@ function gitCommit(cwd, input) {
73505
74275
  }
73506
74276
  let message = given;
73507
74277
  let carried = [];
74278
+ let previousTrailers = [];
73508
74279
  if (given) {
73509
74280
  const lint = lintCommitMessage(cwd, given);
73510
74281
  if (!lint.ok)
@@ -73514,8 +74285,9 @@ function gitCommit(cwd, input) {
73514
74285
  const kept = git5(cwd, ["log", "-1", "--format=%B", previous]);
73515
74286
  if (!kept.ok)
73516
74287
  return failure3("failed", `could not read the message of ${branch}: ${gitError(kept)}`);
74288
+ previousTrailers = messageTrailers(cwd, kept.stdout.trim());
73517
74289
  if (given)
73518
- carried = messageTrailers(cwd, kept.stdout.trim());
74290
+ carried = previousTrailers;
73519
74291
  else
73520
74292
  message = kept.stdout.trim();
73521
74293
  }
@@ -73529,12 +74301,31 @@ function gitCommit(cwd, input) {
73529
74301
  args.push("--amend");
73530
74302
  if (input.allowEmpty || emptyAmend)
73531
74303
  args.push("--allow-empty");
73532
- args.push("-m", withTrailers(cwd, message, [...carried, ...session ? [`Workit-Session: ${session}`] : []]));
74304
+ const trailersOn = workspaceTrailers(cwd).enabled;
74305
+ const sessionTrailer = Boolean(session) && trailersOn;
74306
+ if (!trailersOn) {
74307
+ carried = carried.filter((trailer) => !SESSION_KEY.test(trailer));
74308
+ if (!given)
74309
+ message = stripSessionTrailers(message, { subject: true });
74310
+ }
74311
+ const finalMessage = withTrailers(cwd, message, [
74312
+ ...carried,
74313
+ ...extra,
74314
+ ...sessionTrailer ? [`Workit-Session: ${session}`] : []
74315
+ ]);
74316
+ const allowed = new Set([
74317
+ ...trailersOn ? sessionValues(previousTrailers) : [],
74318
+ ...sessionTrailer && session ? [session] : []
74319
+ ]);
74320
+ const forged = sessionValues(messageTrailers(cwd, finalMessage)).find((value) => !allowed.has(value));
74321
+ if (forged !== undefined)
74322
+ return failure3("invalid_input", `forged_session_trailer: the message would carry "Workit-Session: ${forged}", which workit did not add; workit writes that trailer itself`, "drop the Workit-Session line (or the trailer.<alias>.key config that expands into it)");
74323
+ args.push("-m", finalMessage);
73533
74324
  if (paths.length) {
73534
74325
  if (paths.some((value) => value.startsWith("-") || !value.trim()))
73535
74326
  return failure3("invalid_input", "paths must not be empty or start with -");
73536
74327
  const tracked = new Set(nulList(git5(cwd, ["ls-files", "-z", "--", ...paths]).stdout));
73537
- const addable = paths.filter((value) => existsSync19(path41.resolve(cwd, value)) || [...tracked].some((file) => file === value || file.startsWith(`${value}/`)));
74328
+ const addable = paths.filter((value) => existsSync19(path42.resolve(cwd, value)) || [...tracked].some((file) => file === value || file.startsWith(`${value}/`)));
73538
74329
  if (addable.length) {
73539
74330
  const added = git5(cwd, ["add", "-A", "--", ...addable]);
73540
74331
  if (!added.ok)
@@ -73560,7 +74351,7 @@ function gitCommit(cwd, input) {
73560
74351
  }
73561
74352
  const committed = git5(cwd, args, {
73562
74353
  timeoutMs: COMMIT_TIMEOUT_MS,
73563
- env: input.env
74354
+ env: { ...input.env ?? process.env, WORKIT_TRAILERS: trailersOn ? "on" : "off" }
73564
74355
  });
73565
74356
  if (!committed.ok)
73566
74357
  return failure3("failed", `git commit failed (a hook or git refused it):
@@ -73604,6 +74395,7 @@ ${gitOutput(committed)}`);
73604
74395
  files,
73605
74396
  leftDirty,
73606
74397
  session,
74398
+ sessionTrailer,
73607
74399
  amended: previous,
73608
74400
  notes,
73609
74401
  recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
@@ -73967,18 +74759,22 @@ ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trimEnd()).filter((va
73967
74759
  ...lines.slice(-half)
73968
74760
  ].join(`
73969
74761
  `);
73970
- }, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, TRAILER_CONFIG, nothingToCommit = (what) => failure3("failed", `nothing_to_commit: ${what}`, "workit git commit --allow-empty -m '<msg>' # only to record an empty commit on purpose"), SHA, SHA_PREFIX, RELEASE_TAG;
74762
+ }, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, EXTRA_TRAILER, SESSION_KEY, TRAILER_LINE, sessionValues = (trailers) => trailers.filter((trailer) => SESSION_KEY.test(trailer)).map((trailer) => trailer.slice(trailer.indexOf(":") + 1).trim()), TRAILER_CONFIG, nothingToCommit = (what) => failure3("failed", `nothing_to_commit: ${what}`, "workit git commit --allow-empty -m '<msg>' # only to record an empty commit on purpose"), SHA, SHA_PREFIX, RELEASE_TAG;
73971
74763
  var init_ops = __esm(() => {
73972
74764
  init_branch();
73973
74765
  init_commit_flavors();
73974
74766
  init_pr_create();
73975
74767
  init_release_tracks();
73976
74768
  init_vcs_config();
74769
+ init_workspaces();
73977
74770
  init_redact();
73978
74771
  init_ledger();
73979
74772
  init_rev();
73980
74773
  COMMIT_TIMEOUT_MS = 10 * 60000;
73981
74774
  SESSION_SAFE = /^[\w.:@/+-]{1,128}$/u;
74775
+ EXTRA_TRAILER = /^[A-Za-z0-9][A-Za-z0-9-]*: *\S[^\n\r]*$/u;
74776
+ SESSION_KEY = /^workit-session\s*:/iu;
74777
+ TRAILER_LINE = /^[A-Za-z0-9][A-Za-z0-9-]*\s*:/u;
73982
74778
  TRAILER_CONFIG = ["-c", "trailer.separators=:", "-c", "trailer.where=end"];
73983
74779
  SHA = /^[0-9a-f]{40,64}$/u;
73984
74780
  SHA_PREFIX = /^[0-9a-f]{7,39}$/u;
@@ -73986,11 +74782,11 @@ var init_ops = __esm(() => {
73986
74782
  });
73987
74783
 
73988
74784
  // packages/workit-core/src/forge/reruns.ts
73989
- import fs20 from "node:fs";
73990
- import path42 from "node:path";
74785
+ import fs21 from "node:fs";
74786
+ import path43 from "node:path";
73991
74787
  function rerunLogPath(cwd) {
73992
74788
  const common = gitCommonDir(cwd);
73993
- return common ? path42.join(common, "workit", "ci-reruns.jsonl") : null;
74789
+ return common ? path43.join(common, "workit", "ci-reruns.jsonl") : null;
73994
74790
  }
73995
74791
  function rerunCounts(cwd, key) {
73996
74792
  const counts = new Map;
@@ -73999,7 +74795,7 @@ function rerunCounts(cwd, key) {
73999
74795
  return counts;
74000
74796
  let text;
74001
74797
  try {
74002
- text = fs20.readFileSync(file, "utf8");
74798
+ text = fs21.readFileSync(file, "utf8");
74003
74799
  } catch {
74004
74800
  return counts;
74005
74801
  }
@@ -74025,8 +74821,8 @@ function recordReruns(cwd, rows) {
74025
74821
  if (!file)
74026
74822
  return false;
74027
74823
  try {
74028
- fs20.mkdirSync(path42.dirname(file), { recursive: true });
74029
- fs20.appendFileSync(file, rows.map((row) => `${JSON.stringify(row)}
74824
+ fs21.mkdirSync(path43.dirname(file), { recursive: true });
74825
+ fs21.appendFileSync(file, rows.map((row) => `${JSON.stringify(row)}
74030
74826
  `).join(""));
74031
74827
  return true;
74032
74828
  } catch {
@@ -74038,18 +74834,18 @@ function withRerunLock(cwd, fn) {
74038
74834
  if (!file)
74039
74835
  return fn();
74040
74836
  const lock = `${file.slice(0, -".jsonl".length)}.lock`;
74041
- fs20.mkdirSync(path42.dirname(lock), { recursive: true });
74837
+ fs21.mkdirSync(path43.dirname(lock), { recursive: true });
74042
74838
  const until = Date.now() + LOCK_WAIT_MS2;
74043
74839
  for (;; ) {
74044
74840
  try {
74045
- fs20.closeSync(fs20.openSync(lock, "wx"));
74841
+ fs21.closeSync(fs21.openSync(lock, "wx"));
74046
74842
  break;
74047
74843
  } catch (error) {
74048
74844
  if (error.code !== "EEXIST")
74049
74845
  throw error;
74050
74846
  try {
74051
- if (Date.now() - fs20.statSync(lock).mtimeMs > LOCK_STALE_MS2) {
74052
- fs20.rmSync(lock, { force: true });
74847
+ if (Date.now() - fs21.statSync(lock).mtimeMs > LOCK_STALE_MS2) {
74848
+ fs21.rmSync(lock, { force: true });
74053
74849
  continue;
74054
74850
  }
74055
74851
  } catch {
@@ -74063,7 +74859,7 @@ function withRerunLock(cwd, fn) {
74063
74859
  try {
74064
74860
  return fn();
74065
74861
  } finally {
74066
- fs20.rmSync(lock, { force: true });
74862
+ fs21.rmSync(lock, { force: true });
74067
74863
  }
74068
74864
  }
74069
74865
  var isRow = (value) => {
@@ -74647,6 +75443,56 @@ function nextHint(next, number) {
74647
75443
  return null;
74648
75444
  }
74649
75445
  }
75446
+ function squashMessageFor(cwd, resolved, input, doc) {
75447
+ if (input.method !== "squash")
75448
+ return null;
75449
+ if (input.subject === undefined && input.body === undefined && workspaceTrailers(cwd).enabled)
75450
+ return null;
75451
+ let subject = input.subject?.trim() ?? "";
75452
+ if (!subject) {
75453
+ const meta = resolved.forge.prMeta(doc.number);
75454
+ if (!meta.ok)
75455
+ return meta;
75456
+ subject = resolved.forge.kind === "github" ? `${meta.data.title} (#${doc.number})` : meta.data.title;
75457
+ }
75458
+ const body = input.body === undefined ? squashCommitList(cwd, resolved, doc.base, doc.head.sha) : stripSessionTrailers(input.body, { subject: false });
75459
+ return success2({ subject, body });
75460
+ }
75461
+ function squashCommitList(cwd, resolved, base, head) {
75462
+ const from = resolveRef(cwd, `refs/remotes/${resolved.baseRemote}/${base}`) ?? resolveRef(cwd, `refs/heads/${base}`);
75463
+ if (!from)
75464
+ return "";
75465
+ const log = spawnSync19("git", ["log", "--reverse", "--no-merges", "--format=%B%x1e", `${from}..${head}`], {
75466
+ cwd,
75467
+ encoding: "utf8",
75468
+ timeout: GIT_TIMEOUTS.local,
75469
+ killSignal: "SIGKILL",
75470
+ maxBuffer: 64 * 1024 * 1024
75471
+ });
75472
+ if (log.status !== 0)
75473
+ return "";
75474
+ const entries = log.stdout.split("\x1E").map((message) => stripSessionTrailers(message.replace(/^\n+/u, ""), { subject: true })).filter((message) => message.trim()).map((message) => {
75475
+ const [subject = "", ...rest] = message.trimEnd().split(`
75476
+ `);
75477
+ const body = rest.join(`
75478
+ `).replace(/^\n+/u, "");
75479
+ return body ? `* ${subject}
75480
+
75481
+ ${body}` : `* ${subject}`;
75482
+ });
75483
+ const listed = [];
75484
+ let size = 0;
75485
+ for (const entry of entries) {
75486
+ if (listed.length >= SQUASH_LIST_MAX_COMMITS || size + entry.length > SQUASH_LIST_MAX_CHARS)
75487
+ break;
75488
+ listed.push(entry);
75489
+ size += entry.length + 2;
75490
+ }
75491
+ const more = entries.length - listed.length;
75492
+ return [...listed, ...more ? [`… and ${more} more commit${more === 1 ? "" : "s"}`] : []].join(`
75493
+
75494
+ `);
75495
+ }
74650
75496
  async function mergePullRequest(cwd, resolved, input, sleep) {
74651
75497
  const grant = requireGrant(cwd, "merge");
74652
75498
  if (!grant.allowed)
@@ -74723,7 +75569,14 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
74723
75569
  return refuse(`head_mismatch: the verdict check reads ${branch} at ${check.head?.slice(0, 12) ?? "(missing)"} but ${label} is at ${head.slice(0, 12)}`, `git fetch && git switch ${branch} && git merge --ff-only @{u} # then re-verify that head`, { reason: "head_mismatch", verdict: summary });
74724
75570
  else if (!covered)
74725
75571
  return refuse(`NEEDS_VERDICT: ${label} has no accepted independent verdict for ${head.slice(0, 12)} (${check.accepted.reasons.join(", ")})`, `an independent session verifies the head and runs: workit ledger verdict verified --how "<what was exercised>" --branch ${branch}${grant.allowUnverified ? `; or, only if the user asks to merge unverified: workit pr merge --unverified --reason "<why>"` : ""}`, { reason: "needs_verdict", verdict: summary });
74726
- const merged = resolved.forge.merge(doc.number, { sha: head, method: input.method });
75572
+ const squashMessage = squashMessageFor(cwd, resolved, input, doc);
75573
+ if (squashMessage && !squashMessage.ok)
75574
+ return squashMessage;
75575
+ const merged = resolved.forge.merge(doc.number, {
75576
+ sha: head,
75577
+ method: input.method,
75578
+ ...squashMessage ? { squashMessage: squashMessage.data } : {}
75579
+ });
74727
75580
  if (!merged.ok)
74728
75581
  return merged;
74729
75582
  let state = "open";
@@ -74813,11 +75666,12 @@ var VERIFY_DELAYS_MS, noun = (resolved) => resolved.forge.kind === "github" ? "p
74813
75666
  error,
74814
75667
  unblock,
74815
75668
  refusal
74816
- });
75669
+ }), SQUASH_LIST_MAX_COMMITS = 100, SQUASH_LIST_MAX_CHARS = 60000;
74817
75670
  var init_pr_ops = __esm(() => {
74818
75671
  init_autonomy();
74819
75672
  init_branch();
74820
75673
  init_vcs_config();
75674
+ init_workspaces();
74821
75675
  init_release_tracks();
74822
75676
  init_ops();
74823
75677
  init_rev();
@@ -74836,8 +75690,8 @@ var init_pr_ops = __esm(() => {
74836
75690
  // packages/workit-core/src/stack.ts
74837
75691
  import { spawnSync as spawnSync20 } from "node:child_process";
74838
75692
  import { createHash as createHash17, randomBytes as randomBytes3 } from "node:crypto";
74839
- import fs21 from "node:fs";
74840
- import path43 from "node:path";
75693
+ import fs22 from "node:fs";
75694
+ import path44 from "node:path";
74841
75695
  function worktreeOf(cwd, branch) {
74842
75696
  const list = gitRun3(cwd, ["worktree", "list", "--porcelain"]);
74843
75697
  if (!list.ok)
@@ -74860,7 +75714,7 @@ function rebasingBranch2(dir) {
74860
75714
  if (!found.ok)
74861
75715
  continue;
74862
75716
  try {
74863
- return fs21.readFileSync(found.stdout.trim(), "utf8").trim();
75717
+ return fs22.readFileSync(found.stdout.trim(), "utf8").trim();
74864
75718
  } catch {}
74865
75719
  }
74866
75720
  return null;
@@ -74868,7 +75722,7 @@ function rebasingBranch2(dir) {
74868
75722
  function rebaseInProgress(dir) {
74869
75723
  for (const name of ["rebase-merge", "rebase-apply"]) {
74870
75724
  const found = gitRun3(dir, ["rev-parse", "--path-format=absolute", "--git-path", name]);
74871
- if (found.ok && fs21.existsSync(found.stdout.trim()))
75725
+ if (found.ok && fs22.existsSync(found.stdout.trim()))
74872
75726
  return true;
74873
75727
  }
74874
75728
  return false;
@@ -74880,8 +75734,8 @@ function gitRestack(cwd, input) {
74880
75734
  const scratch = scratchDir(cwd);
74881
75735
  if (!scratch)
74882
75736
  return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
74883
- fs21.mkdirSync(scratch, { recursive: true });
74884
- dir = path43.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes3(4).toString("hex")}`);
75737
+ fs22.mkdirSync(scratch, { recursive: true });
75738
+ dir = path44.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes3(4).toString("hex")}`);
74885
75739
  const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
74886
75740
  if (!added.ok)
74887
75741
  return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
@@ -74919,10 +75773,10 @@ function gitRestack(cwd, input) {
74919
75773
  }
74920
75774
  function pruneScratch(cwd) {
74921
75775
  const scratch = scratchDir(cwd);
74922
- if (!scratch || !fs21.existsSync(scratch))
75776
+ if (!scratch || !fs22.existsSync(scratch))
74923
75777
  return;
74924
- for (const name of fs21.readdirSync(scratch)) {
74925
- const dir = path43.join(scratch, name);
75778
+ for (const name of fs22.readdirSync(scratch)) {
75779
+ const dir = path44.join(scratch, name);
74926
75780
  if (!rebaseInProgress(dir) && !trackedDirt(dir))
74927
75781
  gitRun3(cwd, ["worktree", "remove", "--force", dir]);
74928
75782
  }
@@ -74932,10 +75786,10 @@ function stacksDir(cwd) {
74932
75786
  const root = storeRoot(cwd);
74933
75787
  if (!root.ok)
74934
75788
  return stackFail(root.code, root.error, root.unblock);
74935
- return { ok: true, data: path43.join(root.value.root, "stacks") };
75789
+ return { ok: true, data: path44.join(root.value.root, "stacks") };
74936
75790
  }
74937
75791
  function parseStack(raw) {
74938
- if (!isRecord7(raw) || !Array.isArray(raw.branches))
75792
+ if (!isRecord8(raw) || !Array.isArray(raw.branches))
74939
75793
  return null;
74940
75794
  const name = str3(raw.name);
74941
75795
  const trunk = str3(raw.trunk);
@@ -74943,12 +75797,12 @@ function parseStack(raw) {
74943
75797
  return null;
74944
75798
  const branches = [];
74945
75799
  for (const item of raw.branches) {
74946
- if (!isRecord7(item))
75800
+ if (!isRecord8(item))
74947
75801
  return null;
74948
75802
  const branch = str3(item.branch);
74949
75803
  if (!branch)
74950
75804
  return null;
74951
- const merged = isRecord7(item.merged) ? {
75805
+ const merged = isRecord8(item.merged) ? {
74952
75806
  pr: typeof item.merged.pr === "number" ? item.merged.pr : null,
74953
75807
  mergeSha: str3(item.merged.mergeSha),
74954
75808
  at: str3(item.merged.at) ?? ""
@@ -74961,7 +75815,7 @@ function parseStack(raw) {
74961
75815
  lastParentHead: str3(item.lastParentHead),
74962
75816
  patchId: str3(item.patchId),
74963
75817
  merged,
74964
- ...isRecord7(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
75818
+ ...isRecord8(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
74965
75819
  pendingRestack: {
74966
75820
  from: str3(item.pendingRestack.from),
74967
75821
  tip: str3(item.pendingRestack.tip),
@@ -74984,10 +75838,10 @@ function readStack(cwd, name) {
74984
75838
  const dir = stacksDir(cwd);
74985
75839
  if (!dir.ok)
74986
75840
  return dir;
74987
- const file = path43.join(dir.data, fileFor(name));
75841
+ const file = path44.join(dir.data, fileFor(name));
74988
75842
  let text;
74989
75843
  try {
74990
- text = fs21.readFileSync(file, "utf8");
75844
+ text = fs22.readFileSync(file, "utf8");
74991
75845
  } catch (error) {
74992
75846
  if (error.code === "ENOENT")
74993
75847
  return { ok: true, data: null };
@@ -75005,17 +75859,17 @@ function writeStack(cwd, stack, now = new Date) {
75005
75859
  const dir = stacksDir(cwd);
75006
75860
  if (!dir.ok)
75007
75861
  return dir;
75008
- const file = path43.join(dir.data, fileFor(stack.name));
75862
+ const file = path44.join(dir.data, fileFor(stack.name));
75009
75863
  const temp = `${file}.${process.pid}.tmp`;
75010
75864
  try {
75011
- fs21.mkdirSync(dir.data, { recursive: true });
75865
+ fs22.mkdirSync(dir.data, { recursive: true });
75012
75866
  stack.updatedAt = now.toISOString();
75013
- fs21.writeFileSync(temp, `${JSON.stringify(stack, null, 2)}
75867
+ fs22.writeFileSync(temp, `${JSON.stringify(stack, null, 2)}
75014
75868
  `);
75015
- fs21.renameSync(temp, file);
75869
+ fs22.renameSync(temp, file);
75016
75870
  return { ok: true, data: undefined };
75017
75871
  } catch (error) {
75018
- fs21.rmSync(temp, { force: true });
75872
+ fs22.rmSync(temp, { force: true });
75019
75873
  return stackFail("unavailable", `cannot write ${file}: ${error.message}`);
75020
75874
  }
75021
75875
  }
@@ -75025,7 +75879,7 @@ function listStacks(cwd) {
75025
75879
  return dir;
75026
75880
  let names = [];
75027
75881
  try {
75028
- names = fs21.readdirSync(dir.data).filter((name) => name.endsWith(".json"));
75882
+ names = fs22.readdirSync(dir.data).filter((name) => name.endsWith(".json"));
75029
75883
  } catch {
75030
75884
  return { ok: true, data: [] };
75031
75885
  }
@@ -75033,7 +75887,7 @@ function listStacks(cwd) {
75033
75887
  for (const name of names) {
75034
75888
  let parsed = null;
75035
75889
  try {
75036
- parsed = parseStack(JSON.parse(fs21.readFileSync(path43.join(dir.data, name), "utf8")));
75890
+ parsed = parseStack(JSON.parse(fs22.readFileSync(path44.join(dir.data, name), "utf8")));
75037
75891
  } catch {
75038
75892
  parsed = null;
75039
75893
  }
@@ -75080,7 +75934,7 @@ function stackLockStale(raw, age) {
75080
75934
  }
75081
75935
  function takeLock(lock) {
75082
75936
  try {
75083
- fs21.mkdirSync(lock);
75937
+ fs22.mkdirSync(lock);
75084
75938
  } catch (error) {
75085
75939
  if (error.code === "EEXIST")
75086
75940
  return null;
@@ -75092,24 +75946,24 @@ function takeLock(lock) {
75092
75946
  host: localLockHost(),
75093
75947
  nonce: randomBytes3(8).toString("hex")
75094
75948
  };
75095
- fs21.writeFileSync(path43.join(lock, OWNER), JSON.stringify(owner));
75949
+ fs22.writeFileSync(path44.join(lock, OWNER), JSON.stringify(owner));
75096
75950
  return owner;
75097
75951
  }
75098
75952
  function reapLock(lock, observed) {
75099
75953
  const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
75100
75954
  try {
75101
- fs21.renameSync(lock, tomb);
75955
+ fs22.renameSync(lock, tomb);
75102
75956
  } catch {
75103
75957
  return false;
75104
75958
  }
75105
75959
  if (readOwner(tomb).raw === observed) {
75106
- fs21.rmSync(tomb, { recursive: true, force: true });
75960
+ fs22.rmSync(tomb, { recursive: true, force: true });
75107
75961
  return true;
75108
75962
  }
75109
75963
  try {
75110
- fs21.renameSync(tomb, lock);
75964
+ fs22.renameSync(tomb, lock);
75111
75965
  } catch {
75112
- fs21.rmSync(tomb, { recursive: true, force: true });
75966
+ fs22.rmSync(tomb, { recursive: true, force: true });
75113
75967
  }
75114
75968
  return false;
75115
75969
  }
@@ -75119,8 +75973,8 @@ function releaseLock(lock, owner) {
75119
75973
  return;
75120
75974
  const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
75121
75975
  try {
75122
- fs21.renameSync(lock, tomb);
75123
- fs21.rmSync(tomb, { recursive: true, force: true });
75976
+ fs22.renameSync(lock, tomb);
75977
+ fs22.rmSync(tomb, { recursive: true, force: true });
75124
75978
  } catch {}
75125
75979
  }
75126
75980
  async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
@@ -75129,9 +75983,9 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
75129
75983
  return dir;
75130
75984
  if (!validName(name))
75131
75985
  return stackFail("invalid_input", "a stack name must be 1-200 printable characters");
75132
- const lock = path43.join(dir.data, `${fileFor(name)}.lock`);
75986
+ const lock = path44.join(dir.data, `${fileFor(name)}.lock`);
75133
75987
  try {
75134
- fs21.mkdirSync(dir.data, { recursive: true });
75988
+ fs22.mkdirSync(dir.data, { recursive: true });
75135
75989
  } catch (error) {
75136
75990
  return stackFail("unavailable", `cannot create ${dir.data}: ${error.message}`);
75137
75991
  }
@@ -75159,7 +76013,7 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
75159
76013
  const touch = () => {
75160
76014
  try {
75161
76015
  const now = new Date;
75162
- fs21.utimesSync(path43.join(lock, OWNER), now, now);
76016
+ fs22.utimesSync(path44.join(lock, OWNER), now, now);
75163
76017
  } catch {}
75164
76018
  };
75165
76019
  const beat = setInterval(touch, HEARTBEAT_MS);
@@ -75192,7 +76046,7 @@ function deriveChain(cwd, trunk, base) {
75192
76046
  const [name, sha] = line.trim().split(" ");
75193
76047
  if (!name || !sha || name === branch || name === trunk)
75194
76048
  continue;
75195
- if (sha === head || isAncestor(cwd, sha, base) || !isAncestor(cwd, sha, head))
76049
+ if (sha === head || isAncestor2(cwd, sha, base) || !isAncestor2(cwd, sha, head))
75196
76050
  continue;
75197
76051
  const count = gitRun3(cwd, ["rev-list", "--count", `${base}..${sha}`]);
75198
76052
  below.push({ branch: name, depth: Number(count.stdout.trim()) || 0 });
@@ -75238,17 +76092,17 @@ function planStack(cwd, resolved, input, existing) {
75238
76092
  const status = resolved.forge.prStatus(before.pr);
75239
76093
  still = status.ok && status.data.state === "merged";
75240
76094
  } else
75241
- still = hasOwnCommits(cwd, tip, before.lastParentHead) && isAncestor(cwd, tip, trunkSha);
76095
+ still = hasOwnCommits(cwd, tip, before.lastParentHead) && isAncestor2(cwd, tip, trunkSha);
75242
76096
  if (still) {
75243
76097
  entries.push(before);
75244
76098
  continue;
75245
76099
  }
75246
76100
  notes.push(`${branch} was recorded as merged but is not; it is back in the stack`);
75247
76101
  }
75248
- const onParent = isAncestor(cwd, parentSha, tip);
76102
+ const onParent = isAncestor2(cwd, parentSha, tip);
75249
76103
  let builtOn = parentSha;
75250
76104
  if (!onParent) {
75251
- if (before?.lastParentHead && isAncestor(cwd, before.lastParentHead, tip))
76105
+ if (before?.lastParentHead && isAncestor2(cwd, before.lastParentHead, tip))
75252
76106
  builtOn = before.lastParentHead;
75253
76107
  else {
75254
76108
  const old = oldParentVersion(cwd, parent, parentSha, tip) ?? oldParentLookalike(cwd, parentSha, tip);
@@ -75323,7 +76177,7 @@ function planStack(cwd, resolved, input, existing) {
75323
76177
  function hasOwnCommits(cwd, tip, base) {
75324
76178
  if (!base)
75325
76179
  return false;
75326
- return tip !== base && !isAncestor(cwd, tip, base);
76180
+ return tip !== base && !isAncestor2(cwd, tip, base);
75327
76181
  }
75328
76182
  function oldParentVersion(cwd, parent, parentSha, child) {
75329
76183
  if (!safeRef2(parent))
@@ -75335,7 +76189,7 @@ function oldParentVersion(cwd, parent, parentSha, child) {
75335
76189
  `).map((line) => line.trim())) {
75336
76190
  if (!sha || sha === parentSha)
75337
76191
  continue;
75338
- if (isAncestor(cwd, sha, child) && !isAncestor(cwd, sha, parentSha))
76192
+ if (isAncestor2(cwd, sha, child) && !isAncestor2(cwd, sha, parentSha))
75339
76193
  return sha;
75340
76194
  }
75341
76195
  return null;
@@ -75469,7 +76323,7 @@ function stackStatus(ctx, stack) {
75469
76323
  pr: entry.pr,
75470
76324
  merged,
75471
76325
  head,
75472
- onParent: merged || !head || !parentSha ? null : isAncestor(ctx.cwd, parentSha, head),
76326
+ onParent: merged || !head || !parentSha ? null : isAncestor2(ctx.cwd, parentSha, head),
75473
76327
  prState: doc?.state ?? null,
75474
76328
  prBase: doc?.base ?? null,
75475
76329
  baseMatches: doc && !merged ? doc.base === parentName : null,
@@ -75532,7 +76386,7 @@ function detectMerged(ctx, cwd, stack, trunkSha, state) {
75532
76386
  merged = read.data.state === "merged";
75533
76387
  } else {
75534
76388
  const tip = branchTip(cwd, entry.branch);
75535
- merged = tip !== null && hasOwnCommits(cwd, tip, entry.lastParentHead) && isAncestor(cwd, tip, trunkSha);
76389
+ merged = tip !== null && hasOwnCommits(cwd, tip, entry.lastParentHead) && isAncestor2(cwd, tip, trunkSha);
75536
76390
  }
75537
76391
  if (merged) {
75538
76392
  entry.merged = { pr: entry.pr, mergeSha: null, at: new Date().toISOString() };
@@ -75553,7 +76407,7 @@ function pushBranch(cwd, actor, branch, priorTip) {
75553
76407
  const grant = requireGrant(cwd, "push", { forge: !plan.data.local });
75554
76408
  if (!grant.allowed)
75555
76409
  return stackFail("blocked", grant.error, grant.unblock, { reason: grant.reason });
75556
- const force = remote.sha !== null && !isAncestor(cwd, remote.sha, plan.data.sha);
76410
+ const force = remote.sha !== null && !isAncestor2(cwd, remote.sha, plan.data.sha);
75557
76411
  const expect = !force ? null : remote.sha === priorTip ? priorTip : recordedRemoteTip(cwd, plan.data) ?? null;
75558
76412
  const pushed = executePush(cwd, plan.data, { forceWithLease: force, expect, actor });
75559
76413
  if (!pushed.ok)
@@ -75630,9 +76484,9 @@ function syncStack(cwd, ctx, stack, options, actor) {
75630
76484
  carried: null
75631
76485
  };
75632
76486
  newTip = null;
75633
- } else if (!isAncestor(cwd, parentTip, tip)) {
76487
+ } else if (!isAncestor2(cwd, parentTip, tip)) {
75634
76488
  const pending = entry.pendingRestack ?? null;
75635
- const from = pending && isAncestor(cwd, pending.onto, tip) ? pending.onto : entry.lastParentHead && isAncestor(cwd, entry.lastParentHead, tip) ? entry.lastParentHead : mergeBase(cwd, entry.lastParentHead ?? parentTip, tip) ?? parentTip;
76489
+ const from = pending && isAncestor2(cwd, pending.onto, tip) ? pending.onto : entry.lastParentHead && isAncestor2(cwd, entry.lastParentHead, tip) ? entry.lastParentHead : mergeBase(cwd, entry.lastParentHead ?? parentTip, tip) ?? parentTip;
75636
76490
  if (options.dryRun) {
75637
76491
  step.restack = { from, onto: parentTip, head: null, carried: null };
75638
76492
  newTip = null;
@@ -75680,7 +76534,7 @@ function syncStack(cwd, ctx, stack, options, actor) {
75680
76534
  }
75681
76535
  }
75682
76536
  if (!options.dryRun && newTip) {
75683
- const check = entry.pendingRestack && parentTip && isAncestor(cwd, parentTip, newTip) ? entry.pendingRestack : null;
76537
+ const check = entry.pendingRestack && parentTip && isAncestor2(cwd, parentTip, newTip) ? entry.pendingRestack : null;
75684
76538
  let forced = false;
75685
76539
  if (check && parentTip) {
75686
76540
  const before = {
@@ -75994,12 +76848,12 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
75994
76848
  stderr: run.error ? String(run.error.message) : run.stderr ?? "",
75995
76849
  timedOut: failure?.code === "ETIMEDOUT"
75996
76850
  };
75997
- }, safeRef2 = (value) => value.length > 0 && !value.startsWith("-"), isAncestor = (cwd, ancestor, descendant) => safeRef2(ancestor) && safeRef2(descendant) && gitRun3(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok, branchTip = (cwd, branch) => resolveRef(cwd, `refs/heads/${branch}`), trackedDirt = (dir) => {
76851
+ }, safeRef2 = (value) => value.length > 0 && !value.startsWith("-"), isAncestor2 = (cwd, ancestor, descendant) => safeRef2(ancestor) && safeRef2(descendant) && gitRun3(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok, branchTip = (cwd, branch) => resolveRef(cwd, `refs/heads/${branch}`), trackedDirt = (dir) => {
75998
76852
  const status = gitRun3(dir, ["status", "--porcelain", "--untracked-files=no"]);
75999
76853
  return !status.ok || status.stdout.trim() !== "";
76000
76854
  }, scratchDir = (cwd) => {
76001
76855
  const dir = stacksDir(cwd);
76002
- return dir.ok ? path43.join(dir.data, "worktrees") : null;
76856
+ return dir.ok ? path44.join(dir.data, "worktrees") : null;
76003
76857
  }, firstLine = (run) => (`${run.stderr}
76004
76858
  ${run.stdout}`.split(`
76005
76859
  `).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash17("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
@@ -76011,17 +76865,17 @@ ${run.stdout}`.split(`
76011
76865
  return false;
76012
76866
  }
76013
76867
  return true;
76014
- }, isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str3 = (value) => typeof value === "string" && value ? value : null, LOCK_WAIT_MS3 = 2000, HEARTBEAT_MS = 20000, HEARTBEAT_STALE_MS, OWNERLESS_STALE_MS = 30000, OWNER = "owner", readOwner = (lock) => {
76868
+ }, isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str3 = (value) => typeof value === "string" && value ? value : null, LOCK_WAIT_MS3 = 2000, HEARTBEAT_MS = 20000, HEARTBEAT_STALE_MS, OWNERLESS_STALE_MS = 30000, OWNER = "owner", readOwner = (lock) => {
76015
76869
  let raw = null;
76016
76870
  try {
76017
- raw = fs21.readFileSync(path43.join(lock, OWNER), "utf8");
76871
+ raw = fs22.readFileSync(path44.join(lock, OWNER), "utf8");
76018
76872
  } catch {
76019
76873
  raw = null;
76020
76874
  }
76021
76875
  let age = null;
76022
- for (const target of [path43.join(lock, OWNER), lock])
76876
+ for (const target of [path44.join(lock, OWNER), lock])
76023
76877
  try {
76024
- age = Date.now() - fs21.statSync(target).mtimeMs;
76878
+ age = Date.now() - fs22.statSync(target).mtimeMs;
76025
76879
  break;
76026
76880
  } catch {}
76027
76881
  return { raw, age };
@@ -76238,16 +77092,16 @@ var init_pr_edit = __esm(() => {
76238
77092
 
76239
77093
  // packages/workit-core/src/forge/exec.ts
76240
77094
  import { spawnSync as spawnSync22 } from "node:child_process";
76241
- import fs22 from "node:fs";
76242
- import path44 from "node:path";
77095
+ import fs23 from "node:fs";
77096
+ import path45 from "node:path";
76243
77097
  function findExecutable(bin, env) {
76244
- const dirs = (env.PATH ?? env.Path ?? "").split(path44.delimiter).filter(Boolean);
77098
+ const dirs = (env.PATH ?? env.Path ?? "").split(path45.delimiter).filter(Boolean);
76245
77099
  const exts = process.platform === "win32" ? [".exe"] : [""];
76246
77100
  for (const dir of dirs)
76247
77101
  for (const ext of exts) {
76248
- const candidate = path44.join(dir, `${bin}${ext}`);
77102
+ const candidate = path45.join(dir, `${bin}${ext}`);
76249
77103
  try {
76250
- const stat = fs22.statSync(candidate);
77104
+ const stat = fs23.statSync(candidate);
76251
77105
  if (stat.isFile())
76252
77106
  return candidate;
76253
77107
  } catch {}
@@ -76279,7 +77133,7 @@ function systemRunner(kind, apiHost, env = process.env) {
76279
77133
  timeout: options.timeoutMs,
76280
77134
  killSignal: "SIGKILL",
76281
77135
  windowsHide: true,
76282
- stdio: ["ignore", "pipe", "pipe"]
77136
+ ...options.input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input: options.input, stdio: ["pipe", "pipe", "pipe"] }
76283
77137
  });
76284
77138
  const error = result.error;
76285
77139
  return {
@@ -76355,8 +77209,8 @@ function apiText(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.lo
76355
77209
  return cliFailure(bin, apiHost, run, what, timeoutMs);
76356
77210
  return { ok: true, data: run.stdout };
76357
77211
  }
76358
- function apiWrite(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.api) {
76359
- const run = runner(bin, args, { timeoutMs });
77212
+ function apiWrite(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.api, input) {
77213
+ const run = runner(bin, args, input === undefined ? { timeoutMs } : { timeoutMs, input });
76360
77214
  if (run.status !== 0) {
76361
77215
  const text = redactText(`${run.stderr}
76362
77216
  ${run.stdout}`).trim();
@@ -76658,16 +77512,19 @@ function createGitHubForge(options) {
76658
77512
  });
76659
77513
  },
76660
77514
  merge(number, mergeOptions) {
77515
+ const squash = mergeOptions.method === "squash" ? mergeOptions.squashMessage : undefined;
76661
77516
  const merged = apiWrite(runner, "gh", apiHost, [
76662
77517
  "api",
76663
77518
  "-X",
76664
77519
  "PUT",
76665
77520
  `repos/${repo}/pulls/${number}/merge`,
76666
- "-f",
76667
- `sha=${mergeOptions.sha}`,
76668
- "-f",
76669
- `merge_method=${mergeOptions.method}`
76670
- ], `merge of pull request #${number}`);
77521
+ ...squash ? ["--input", "-"] : ["-f", `sha=${mergeOptions.sha}`, "-f", `merge_method=${mergeOptions.method}`]
77522
+ ], `merge of pull request #${number}`, undefined, squash ? JSON.stringify({
77523
+ sha: mergeOptions.sha,
77524
+ merge_method: mergeOptions.method,
77525
+ commit_title: squash.subject,
77526
+ commit_message: squash.body
77527
+ }) : undefined);
76671
77528
  if (!merged.ok)
76672
77529
  return merged;
76673
77530
  if (merged.data.merged === false)
@@ -77025,6 +77882,7 @@ function createGitLabForge(options) {
77025
77882
  });
77026
77883
  },
77027
77884
  merge(iid, mergeOptions) {
77885
+ const squash = mergeOptions.method === "squash" ? mergeOptions.squashMessage : undefined;
77028
77886
  if (mergeOptions.method === "rebase")
77029
77887
  return failure3("invalid_input", "GitLab merges with the project's merge method; pass --method squash or merge");
77030
77888
  const merged = apiWrite(runner, "glab", apiHost, [
@@ -77032,11 +77890,19 @@ function createGitLabForge(options) {
77032
77890
  "-X",
77033
77891
  "PUT",
77034
77892
  `${project}/merge_requests/${iid}/merge`,
77035
- "-f",
77036
- `sha=${mergeOptions.sha}`,
77037
- "-F",
77038
- `squash=${mergeOptions.method === "squash" ? "true" : "false"}`
77039
- ], `merge of merge request !${iid}`);
77893
+ ...squash ? ["--input", "-"] : [
77894
+ "-f",
77895
+ `sha=${mergeOptions.sha}`,
77896
+ "-F",
77897
+ `squash=${mergeOptions.method === "squash" ? "true" : "false"}`
77898
+ ]
77899
+ ], `merge of merge request !${iid}`, undefined, squash ? JSON.stringify({
77900
+ sha: mergeOptions.sha,
77901
+ squash: true,
77902
+ squash_commit_message: squash.body ? `${squash.subject}
77903
+
77904
+ ${squash.body}` : squash.subject
77905
+ }) : undefined);
77040
77906
  if (!merged.ok)
77041
77907
  return merged;
77042
77908
  const value = merged.data;
@@ -77181,7 +78047,7 @@ var init_gitlab = __esm(() => {
77181
78047
  });
77182
78048
 
77183
78049
  // packages/workit-core/src/forge/resolve.ts
77184
- import fs23 from "node:fs";
78050
+ import fs24 from "node:fs";
77185
78051
  function memo4(scope, key, compute, now = Date.now) {
77186
78052
  let entries = memoScopes.get(scope.owner);
77187
78053
  if (!entries)
@@ -77226,7 +78092,7 @@ function resolveCredential(workspace, derived, run, scope) {
77226
78092
  return failure3("blocked", `token_host_unverified: push host ${derived.host} only looks like ${kind}; workspace ${workspace?.name ?? "?"} vcs.tokenFile is not sent to it`, `if ${apiHost} is your ${kind} server, set ${kind}.host in ~/.config/workit/vcs.json (or vcs.host for the workspace); otherwise fix the push remote`);
77227
78093
  let token = "";
77228
78094
  try {
77229
- token = fs23.readFileSync(tokenFile.trim(), "utf8").trim();
78095
+ token = fs24.readFileSync(tokenFile.trim(), "utf8").trim();
77230
78096
  } catch {
77231
78097
  return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile cannot be read`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
77232
78098
  }
@@ -77424,16 +78290,16 @@ function parseDuration2(value) {
77424
78290
  return null;
77425
78291
  let total = 0;
77426
78292
  for (const [, amount, unit] of parts) {
77427
- const factor = UNITS.find(([pattern]) => pattern.test(unit))?.[1];
78293
+ const factor = UNITS2.find(([pattern]) => pattern.test(unit))?.[1];
77428
78294
  if (factor === undefined)
77429
78295
  return null;
77430
78296
  total += Number(amount) * factor;
77431
78297
  }
77432
78298
  return Math.round(total);
77433
78299
  }
77434
- var UNITS;
78300
+ var UNITS2;
77435
78301
  var init_duration = __esm(() => {
77436
- UNITS = [
78302
+ UNITS2 = [
77437
78303
  [/^(?:ms|milliseconds?)$/u, 1],
77438
78304
  [/^(?:s|secs?|seconds?)$/u, 1000],
77439
78305
  [/^(?:m|mins?|minutes?)$/u, 60000],
@@ -77598,11 +78464,11 @@ __export(exports_pr, {
77598
78464
  run: () => run11
77599
78465
  });
77600
78466
  import { readFileSync as readFileSync21 } from "node:fs";
77601
- import path45 from "node:path";
78467
+ import path46 from "node:path";
77602
78468
  function readBodyFile(io, file) {
77603
78469
  try {
77604
78470
  return {
77605
- body: file === "-" ? forgeDeps.readStdin() : readFileSync21(path45.resolve(io.cwd, file), "utf8")
78471
+ body: file === "-" ? forgeDeps.readStdin() : readFileSync21(path46.resolve(io.cwd, file), "utf8")
77606
78472
  };
77607
78473
  } catch {
77608
78474
  return `cannot read --body-file ${file}`;
@@ -77780,7 +78646,10 @@ async function merge3(argv, io) {
77780
78646
  method: "value",
77781
78647
  "delete-branch": "boolean",
77782
78648
  unverified: "boolean",
77783
- reason: "value"
78649
+ reason: "value",
78650
+ subject: "value",
78651
+ body: "value",
78652
+ "body-file": "value"
77784
78653
  });
77785
78654
  if (typeof flags === "string")
77786
78655
  return usage3(io, flags, MERGE_USAGE);
@@ -77795,6 +78664,21 @@ async function merge3(argv, io) {
77795
78664
  const method = flags.values.method ?? "squash";
77796
78665
  if (!METHODS2.has(method))
77797
78666
  return usage3(io, "--method must be squash, merge or rebase", MERGE_USAGE);
78667
+ const { subject } = flags.values;
78668
+ if (flags.values.body !== undefined && flags.values["body-file"] !== undefined)
78669
+ return usage3(io, "pass --body or --body-file, not both", MERGE_USAGE);
78670
+ let body = flags.values.body;
78671
+ if (flags.values["body-file"] !== undefined) {
78672
+ const read = readBodyFile(io, flags.values["body-file"]);
78673
+ if (typeof read === "string")
78674
+ return usage3(io, read, MERGE_USAGE);
78675
+ body = read.body;
78676
+ }
78677
+ if ((subject !== undefined || body !== undefined) && method !== "squash")
78678
+ return usage3(io, "--subject and --body set a squash message; use --method squash", MERGE_USAGE);
78679
+ if (subject !== undefined && (!subject.trim() || subject.includes(`
78680
+ `)))
78681
+ return usage3(io, "--subject must be one non-empty line", MERGE_USAGE);
77798
78682
  const connected = connect(io);
77799
78683
  if (!connected.ok)
77800
78684
  return forgeFail(io, connected);
@@ -77803,7 +78687,9 @@ async function merge3(argv, io) {
77803
78687
  method,
77804
78688
  deleteBranch: flags.booleans.has("delete-branch"),
77805
78689
  actor: actorFromEnv(io.env),
77806
- ...unverified ? { unverified } : {}
78690
+ ...unverified ? { unverified } : {},
78691
+ ...subject !== undefined ? { subject } : {},
78692
+ ...body !== undefined ? { body } : {}
77807
78693
  }, forgeDeps.sleep);
77808
78694
  if (!result.ok)
77809
78695
  return emit(io, fail(result.code, result.error, {
@@ -77967,7 +78853,7 @@ async function run11(argv, io) {
77967
78853
  return reply(rest, io);
77968
78854
  return usage3(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE4);
77969
78855
  }
77970
- var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch] [--unverified --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE4 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}`, METHODS2;
78856
+ var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch] [--unverified --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE4 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}`, METHODS2;
77971
78857
  var init_pr = __esm(() => {
77972
78858
  init_pr_body();
77973
78859
  init_pr_edit();
@@ -78205,7 +79091,7 @@ __export(exports_git, {
78205
79091
  run: () => run13
78206
79092
  });
78207
79093
  import { readFileSync as readFileSync22 } from "node:fs";
78208
- import path46 from "node:path";
79094
+ import path47 from "node:path";
78209
79095
  function kindHint(cwd, kind) {
78210
79096
  let kinds = [...BRANCH_KINDS];
78211
79097
  try {
@@ -78256,6 +79142,7 @@ function parseCommit(argv) {
78256
79142
  const out = {
78257
79143
  messages: [],
78258
79144
  files: [],
79145
+ trailers: [],
78259
79146
  all: false,
78260
79147
  amend: false,
78261
79148
  noEdit: false,
@@ -78277,14 +79164,15 @@ function parseCommit(argv) {
78277
79164
  }
78278
79165
  const valued = [
78279
79166
  ["-m", "--message", out.messages],
78280
- ["-F", "--file", out.files]
78281
- ].find(([shortName, longName]) => arg === shortName || arg === longName || arg.startsWith(`${longName}=`) || arg.startsWith(shortName) && arg.length > 2);
79167
+ ["-F", "--file", out.files],
79168
+ [null, "--trailer", out.trailers]
79169
+ ].find(([shortName, longName]) => arg === shortName || arg === longName || arg.startsWith(`${longName}=`) || shortName !== null && arg.startsWith(shortName) && arg.length > 2);
78282
79170
  if (valued) {
78283
79171
  const [shortName, longName, into] = valued;
78284
79172
  if (arg === shortName || arg === longName) {
78285
79173
  const value = argv[++index];
78286
79174
  if (value === undefined)
78287
- return `${arg} requires a ${shortName === "-m" ? "message" : "file"}`;
79175
+ return `${arg} requires a ${shortName === "-m" ? "message" : shortName === "-F" ? "file" : "Key: value trailer"}`;
78288
79176
  into.push(value);
78289
79177
  } else
78290
79178
  into.push(arg.startsWith(`${longName}=`) ? arg.slice(longName.length + 1) : arg.slice(2));
@@ -78307,7 +79195,7 @@ function commitMessage(parsed, io) {
78307
79195
 
78308
79196
  `);
78309
79197
  try {
78310
- return file === "-" ? readFileSync22(0, "utf8") : readFileSync22(path46.resolve(io.cwd, file), "utf8");
79198
+ return file === "-" ? readFileSync22(0, "utf8") : readFileSync22(path47.resolve(io.cwd, file), "utf8");
78311
79199
  } catch (error) {
78312
79200
  return {
78313
79201
  error: `cannot read -F ${file}: ${error instanceof Error ? error.message : String(error)}`
@@ -78338,6 +79226,7 @@ async function commit(argv, io) {
78338
79226
  paths: parsed.paths,
78339
79227
  amend: parsed.amend,
78340
79228
  allowEmpty: parsed.allowEmpty,
79229
+ trailers: parsed.trailers,
78341
79230
  actor: actorFromEnv(io.env),
78342
79231
  env: io.env
78343
79232
  });
@@ -78450,6 +79339,13 @@ async function releasePush(names, io) {
78450
79339
  ..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
78451
79340
  ]);
78452
79341
  }
79342
+ function trailers(argv, io) {
79343
+ const extra = argv.find((arg) => arg !== "--json");
79344
+ if (extra !== undefined)
79345
+ return usage3(io, `unexpected argument ${extra}`, "workit git trailers");
79346
+ const state = workspaceTrailers(io.cwd);
79347
+ return emit(io, ok(state), (data) => data.enabled ? "on" : "off");
79348
+ }
78453
79349
  async function run13(argv, io) {
78454
79350
  const [sub, ...rest] = argv;
78455
79351
  if (sub === "branch")
@@ -78458,13 +79354,16 @@ async function run13(argv, io) {
78458
79354
  return commit(rest, io);
78459
79355
  if (sub === "push")
78460
79356
  return push(rest, io);
79357
+ if (sub === "trailers")
79358
+ return trailers(rest, io);
78461
79359
  return usage3(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE5);
78462
79360
  }
78463
- var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…] [--json]", USAGE5 = "workit git branch|commit|push ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES;
79361
+ var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--trailer <key: value>]… [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…] [--json]", USAGE5 = "workit git branch|commit|push|trailers ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES;
78464
79362
  var init_git2 = __esm(() => {
78465
79363
  init_autonomy();
78466
79364
  init_branch();
78467
79365
  init_ops();
79366
+ init_workspaces();
78468
79367
  init_ledger();
78469
79368
  init_implicit_task();
78470
79369
  init_output();
@@ -79015,8 +79914,8 @@ var init_stack2 = __esm(() => {
79015
79914
  // packages/workit-core/src/fanout.ts
79016
79915
  import { spawnSync as spawnSync24 } from "node:child_process";
79017
79916
  import { createHash as createHash18 } from "node:crypto";
79018
- import fs24 from "node:fs";
79019
- import path47 from "node:path";
79917
+ import fs25 from "node:fs";
79918
+ import path48 from "node:path";
79020
79919
  function expandBraces(pattern, limit = 64) {
79021
79920
  for (let index = 0;index < pattern.length; index += 1) {
79022
79921
  if (pattern[index] === "\\") {
@@ -79162,7 +80061,7 @@ function inScope(plan, slice, file) {
79162
80061
  }
79163
80062
  function parsePlan(raw, defaults, name) {
79164
80063
  const problems = [];
79165
- if (!isRecord8(raw))
80064
+ if (!isRecord9(raw))
79166
80065
  return { ok: false, problems: ["the plan must be a JSON object"] };
79167
80066
  const planName = name ?? (typeof raw.name === "string" ? raw.name.trim() : "");
79168
80067
  if (!planName || planName.length > 200 || /\p{Cc}/u.test(planName))
@@ -79185,8 +80084,8 @@ function parsePlan(raw, defaults, name) {
79185
80084
  const seenIds = new Set;
79186
80085
  const seenBranches = new Map;
79187
80086
  raw.slices.forEach((item, index) => {
79188
- const where = isRecord8(item) && typeof item.id === "string" ? `slice ${item.id}` : `slice #${index + 1}`;
79189
- if (!isRecord8(item)) {
80087
+ const where = isRecord9(item) && typeof item.id === "string" ? `slice ${item.id}` : `slice #${index + 1}`;
80088
+ if (!isRecord9(item)) {
79190
80089
  problems.push(`${where}: must be an object`);
79191
80090
  return;
79192
80091
  }
@@ -79250,7 +80149,7 @@ function parsePlan(raw, defaults, name) {
79250
80149
  id,
79251
80150
  branch,
79252
80151
  base: optional("base") ?? "",
79253
- worktree: path47.resolve(defaults.root, optional("worktree") ?? path47.join(defaults.worktreeRoot, id)),
80152
+ worktree: path48.resolve(defaults.root, optional("worktree") ?? path48.join(defaults.worktreeRoot, id)),
79254
80153
  tier,
79255
80154
  dependsOn: [...new Set(dependsOn ?? [])],
79256
80155
  scope,
@@ -79479,11 +80378,11 @@ function defaultTrunk(cwd) {
79479
80378
  }
79480
80379
  function mainCheckout(cwd) {
79481
80380
  const common = gitCommonDir(cwd);
79482
- return common && path47.basename(common) === ".git" ? path47.dirname(common) : path47.resolve(cwd);
80381
+ return common && path48.basename(common) === ".git" ? path48.dirname(common) : path48.resolve(cwd);
79483
80382
  }
79484
80383
  function worktreeRoot(cwd) {
79485
80384
  const main = mainCheckout(cwd);
79486
- return path47.join(path47.dirname(main), `${path47.basename(main)}-wt`);
80385
+ return path48.join(path48.dirname(main), `${path48.basename(main)}-wt`);
79487
80386
  }
79488
80387
  function sliceHash(slice) {
79489
80388
  const {
@@ -79501,10 +80400,10 @@ function fanoutsDir(cwd) {
79501
80400
  const root = storeRoot(cwd);
79502
80401
  if (!root.ok)
79503
80402
  return fanoutFail(root.code, root.error, root.unblock);
79504
- return { ok: true, data: path47.join(root.value.root, "fanouts") };
80403
+ return { ok: true, data: path48.join(root.value.root, "fanouts") };
79505
80404
  }
79506
80405
  function parseStored(raw) {
79507
- if (!isRecord8(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
80406
+ if (!isRecord9(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
79508
80407
  return null;
79509
80408
  if (!Array.isArray(raw.slices))
79510
80409
  return null;
@@ -79521,7 +80420,7 @@ function parseStored(raw) {
79521
80420
  });
79522
80421
  const slices = [];
79523
80422
  for (const item of raw.slices) {
79524
- if (!isRecord8(item) || typeof item.id !== "string" || typeof item.branch !== "string")
80423
+ if (!isRecord9(item) || typeof item.id !== "string" || typeof item.branch !== "string")
79525
80424
  return null;
79526
80425
  slices.push({
79527
80426
  id: item.id,
@@ -79567,10 +80466,10 @@ function readFanout(cwd, name) {
79567
80466
  const dir = fanoutsDir(cwd);
79568
80467
  if (!dir.ok)
79569
80468
  return dir;
79570
- const file = path47.join(dir.data, stackFileName(name));
80469
+ const file = path48.join(dir.data, stackFileName(name));
79571
80470
  let parsed = null;
79572
80471
  try {
79573
- parsed = parseStored(JSON.parse(fs24.readFileSync(file, "utf8")));
80472
+ parsed = parseStored(JSON.parse(fs25.readFileSync(file, "utf8")));
79574
80473
  } catch (error) {
79575
80474
  if (error.code === "ENOENT")
79576
80475
  return { ok: true, data: null };
@@ -79584,7 +80483,7 @@ function listFanouts(cwd) {
79584
80483
  return dir;
79585
80484
  let names = [];
79586
80485
  try {
79587
- names = fs24.readdirSync(dir.data).filter((entry) => entry.endsWith(".json"));
80486
+ names = fs25.readdirSync(dir.data).filter((entry) => entry.endsWith(".json"));
79588
80487
  } catch {
79589
80488
  return { ok: true, data: [] };
79590
80489
  }
@@ -79592,7 +80491,7 @@ function listFanouts(cwd) {
79592
80491
  for (const entry of names) {
79593
80492
  let parsed = null;
79594
80493
  try {
79595
- parsed = parseStored(JSON.parse(fs24.readFileSync(path47.join(dir.data, entry), "utf8")));
80494
+ parsed = parseStored(JSON.parse(fs25.readFileSync(path48.join(dir.data, entry), "utf8")));
79596
80495
  } catch {
79597
80496
  parsed = null;
79598
80497
  }
@@ -79622,16 +80521,16 @@ function writeFanout(cwd, plan) {
79622
80521
  const dir = fanoutsDir(cwd);
79623
80522
  if (!dir.ok)
79624
80523
  return dir;
79625
- const file = path47.join(dir.data, stackFileName(plan.name));
80524
+ const file = path48.join(dir.data, stackFileName(plan.name));
79626
80525
  const temp = `${file}.${process.pid}.tmp`;
79627
80526
  try {
79628
- fs24.mkdirSync(dir.data, { recursive: true });
79629
- fs24.writeFileSync(temp, `${JSON.stringify(plan, null, 2)}
80527
+ fs25.mkdirSync(dir.data, { recursive: true });
80528
+ fs25.writeFileSync(temp, `${JSON.stringify(plan, null, 2)}
79630
80529
  `);
79631
- fs24.renameSync(temp, file);
80530
+ fs25.renameSync(temp, file);
79632
80531
  return { ok: true, data: file };
79633
80532
  } catch (error) {
79634
- fs24.rmSync(temp, { force: true });
80533
+ fs25.rmSync(temp, { force: true });
79635
80534
  return fanoutFail("unavailable", `cannot write ${file}: ${error.message}`);
79636
80535
  }
79637
80536
  }
@@ -79644,7 +80543,7 @@ function planFanout(cwd, input, now = new Date) {
79644
80543
  return fanoutFail("blocked", "--take-lead needs a session: WORKIT_SESSION_ID is not set", "export WORKIT_SESSION_ID=<your session id>, then plan again with --take-lead");
79645
80544
  if (input.takeLead && isDelegateSession(session))
79646
80545
  return fanoutFail("blocked", `session ${session} is a worker or verifier id; only a lead takes the lead`, "run --take-lead from the lead's own session");
79647
- const parsed = parsePlan(input.trunk && isRecord8(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
80546
+ const parsed = parsePlan(input.trunk && isRecord9(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
79648
80547
  trunk: input.trunk ?? defaultTrunk(cwd),
79649
80548
  root: mainCheckout(cwd),
79650
80549
  worktreeRoot: worktreeRoot(cwd)
@@ -79767,7 +80666,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
79767
80666
  error,
79768
80667
  ...unblock ? { unblock } : {},
79769
80668
  ...data ? { data } : {}
79770
- }), DEFAULT_SHARED, escapeRegExp4 = (text) => text.replace(/[.+^$()|[\]\\{}*?]/gu, "\\$&"), unescape2 = (pattern) => pattern.replace(/\\(.)/gu, "$1"), hasWildcard = (pattern) => /[*?{]/u.test(pattern.replace(/\\./gu, "")), compiled, matchesAny = (patterns, file) => patterns.some((pattern) => matchesScope(pattern, file)), isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), PLACEHOLDER3, filled = (text) => text.trim() !== "" && !PLACEHOLDER3.test(text.trim()), textList = (value) => {
80669
+ }), DEFAULT_SHARED, escapeRegExp4 = (text) => text.replace(/[.+^$()|[\]\\{}*?]/gu, "\\$&"), unescape2 = (pattern) => pattern.replace(/\\(.)/gu, "$1"), hasWildcard = (pattern) => /[*?{]/u.test(pattern.replace(/\\./gu, "")), compiled, matchesAny = (patterns, file) => patterns.some((pattern) => matchesScope(pattern, file)), isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), PLACEHOLDER3, filled = (text) => text.trim() !== "" && !PLACEHOLDER3.test(text.trim()), textList = (value) => {
79771
80670
  if (typeof value === "string")
79772
80671
  return [value];
79773
80672
  if (Array.isArray(value) && value.every((item) => typeof item === "string"))
@@ -79825,8 +80724,8 @@ var init_fanout = __esm(() => {
79825
80724
 
79826
80725
  // packages/workit-core/src/fanout-worktree.ts
79827
80726
  import { randomBytes as randomBytes4 } from "node:crypto";
79828
- import fs25 from "node:fs";
79829
- import path48 from "node:path";
80727
+ import fs26 from "node:fs";
80728
+ import path49 from "node:path";
79830
80729
  function rowSample(lines) {
79831
80730
  const out = [];
79832
80731
  let bytes = 0;
@@ -79886,7 +80785,7 @@ function adminDir(worktree) {
79886
80785
  const file = dir.stdout.trim();
79887
80786
  let token = null;
79888
80787
  try {
79889
- token = fs25.readFileSync(path48.join(file, TOKEN_FILE), "utf8").trim() || null;
80788
+ token = fs26.readFileSync(path49.join(file, TOKEN_FILE), "utf8").trim() || null;
79890
80789
  } catch {
79891
80790
  token = null;
79892
80791
  }
@@ -79894,7 +80793,7 @@ function adminDir(worktree) {
79894
80793
  const stat = worktreeDeps.stat(file);
79895
80794
  return {
79896
80795
  path: file,
79897
- id: path48.basename(file),
80796
+ id: path49.basename(file),
79898
80797
  ino: stat.ino.toString(),
79899
80798
  token,
79900
80799
  birthMs: stat.birthtimeNs > 0n ? Number(stat.birthtimeMs) : null,
@@ -79910,7 +80809,7 @@ function markAdminDir(worktree) {
79910
80809
  return null;
79911
80810
  const token = randomBytes4(16).toString("hex");
79912
80811
  try {
79913
- fs25.writeFileSync(path48.join(admin.path, TOKEN_FILE), `${token}
80812
+ fs26.writeFileSync(path49.join(admin.path, TOKEN_FILE), `${token}
79914
80813
  `);
79915
80814
  } catch {
79916
80815
  return null;
@@ -79925,25 +80824,25 @@ function excludeScratch(cwd) {
79925
80824
  const common = gitCommonDir(cwd);
79926
80825
  if (!common)
79927
80826
  return;
79928
- const file = path48.join(common, "info", "exclude");
80827
+ const file = path49.join(common, "info", "exclude");
79929
80828
  let text = "";
79930
80829
  try {
79931
- text = fs25.readFileSync(file, "utf8");
80830
+ text = fs26.readFileSync(file, "utf8");
79932
80831
  } catch {
79933
80832
  text = "";
79934
80833
  }
79935
80834
  if (text.split(/\r?\n/u).includes(EXCLUDE_LINE))
79936
80835
  return;
79937
- fs25.mkdirSync(path48.dirname(file), { recursive: true });
79938
- fs25.appendFileSync(file, `${text && !text.endsWith(`
80836
+ fs26.mkdirSync(path49.dirname(file), { recursive: true });
80837
+ fs26.appendFileSync(file, `${text && !text.endsWith(`
79939
80838
  `) ? `
79940
80839
  ` : ""}# workit fanout: per-worker scratch dirs
79941
80840
  ${EXCLUDE_LINE}
79942
80841
  `);
79943
80842
  }
79944
80843
  function ensureScratch(worktree) {
79945
- const scratch = path48.join(worktree, SCRATCH_DIR);
79946
- fs25.mkdirSync(scratch, { recursive: true });
80844
+ const scratch = path49.join(worktree, SCRATCH_DIR);
80845
+ fs26.mkdirSync(scratch, { recursive: true });
79947
80846
  return scratch;
79948
80847
  }
79949
80848
  function createSliceWorktree(cwd, plan, input) {
@@ -79987,8 +80886,8 @@ function createSliceWorktree(cwd, plan, input) {
79987
80886
  return fanoutFail("blocked", `${slice.branch} is checked out at ${elsewhere.path}: never two live workers on one branch`, `stop that worker, then workit fanout worktree release ${slice.id} (or git worktree remove ${elsewhere.path})`);
79988
80887
  let dirExisted = false;
79989
80888
  try {
79990
- const stat = fs25.statSync(target);
79991
- if (!stat.isDirectory() || fs25.readdirSync(target).length > 0)
80889
+ const stat = fs26.statSync(target);
80890
+ if (!stat.isDirectory() || fs26.readdirSync(target).length > 0)
79992
80891
  return fanoutFail("blocked", `${target} exists and is ${stat.isDirectory() ? "not empty" : "not a directory"} (not a worktree of this repository)`, `move it away, or set slice ${slice.id}'s worktree to another path`);
79993
80892
  dirExisted = true;
79994
80893
  } catch (error) {
@@ -80070,7 +80969,7 @@ function releaseSliceWorktree(cwd, plan, input) {
80070
80969
  notes: []
80071
80970
  };
80072
80971
  if (index < 0) {
80073
- if (fs25.existsSync(target))
80972
+ if (fs26.existsSync(target))
80074
80973
  return fanoutFail("blocked", `${target} is not a worktree of this repository; nothing was removed`, "remove it yourself once you know what it holds");
80075
80974
  out.notes.push(`no worktree at ${target}; nothing to release`);
80076
80975
  return { ok: true, data: out };
@@ -80132,7 +81031,7 @@ function releaseSliceWorktree(cwd, plan, input) {
80132
81031
  `)[0]}`, entry.locked ? `git worktree unlock ${entry.path}, then release again` : undefined, { ...out });
80133
81032
  out.removed = true;
80134
81033
  if (made.dirExisted === true)
80135
- fs25.mkdirSync(entry.path, { recursive: true });
81034
+ fs26.mkdirSync(entry.path, { recursive: true });
80136
81035
  if (out.unpushed)
80137
81036
  out.notes.push(`${slice.branch} is kept with ${out.unpushed} commit(s) not on the remote`);
80138
81037
  return { ok: true, data: out };
@@ -80160,32 +81059,32 @@ function unpushedCount(cwd, branch) {
80160
81059
  return count.ok ? Number(count.stdout.trim()) : null;
80161
81060
  }
80162
81061
  function removeScratch(worktree) {
80163
- const scratch = path48.join(worktree, SCRATCH_DIR);
81062
+ const scratch = path49.join(worktree, SCRATCH_DIR);
80164
81063
  let stat;
80165
81064
  try {
80166
- stat = fs25.lstatSync(scratch);
81065
+ stat = fs26.lstatSync(scratch);
80167
81066
  } catch {
80168
81067
  return;
80169
81068
  }
80170
81069
  if (stat.isSymbolicLink() || !stat.isDirectory())
80171
- fs25.unlinkSync(scratch);
81070
+ fs26.unlinkSync(scratch);
80172
81071
  else
80173
- fs25.rmSync(scratch, { recursive: true, force: true, maxRetries: 2 });
81072
+ fs26.rmSync(scratch, { recursive: true, force: true, maxRetries: 2 });
80174
81073
  }
80175
81074
  var SLOW, SCRATCH_DIR = ".workit-scratch", EXCLUDE_LINE, DIRTY_ROW_BYTES = 2000, canonical2 = (target) => {
80176
81075
  try {
80177
- return fs25.realpathSync.native(target);
81076
+ return fs26.realpathSync.native(target);
80178
81077
  } catch {
80179
- return path48.resolve(target);
81078
+ return path49.resolve(target);
80180
81079
  }
80181
81080
  }, samePath = (a, b) => {
80182
81081
  const left = canonical2(a);
80183
81082
  const right = canonical2(b);
80184
81083
  return process.platform === "win32" || process.platform === "darwin" ? left.toLowerCase() === right.toLowerCase() : left === right;
80185
81084
  }, inside3 = (child, parent) => {
80186
- const relative = path48.relative(canonical2(parent), canonical2(child));
80187
- return relative === "" || !relative.startsWith("..") && !path48.isAbsolute(relative);
80188
- }, slicePath = (cwd, slice) => path48.isAbsolute(slice.worktree) ? slice.worktree : path48.resolve(mainCheckout(cwd), slice.worktree), CLOCK_SLACK_MS = 2000, TOKEN_FILE = "workit-fanout-token", worktreeDeps, worktreeFailed = (stderr) => fanoutFail("failed", `git worktree add failed: ${stderr.trim().split(`
81085
+ const relative = path49.relative(canonical2(parent), canonical2(child));
81086
+ return relative === "" || !relative.startsWith("..") && !path49.isAbsolute(relative);
81087
+ }, slicePath = (cwd, slice) => path49.isAbsolute(slice.worktree) ? slice.worktree : path49.resolve(mainCheckout(cwd), slice.worktree), CLOCK_SLACK_MS = 2000, TOKEN_FILE = "workit-fanout-token", worktreeDeps, worktreeFailed = (stderr) => fanoutFail("failed", `git worktree add failed: ${stderr.trim().split(`
80189
81088
  `)[0] || "unknown error"}`);
80190
81089
  var init_fanout_worktree = __esm(() => {
80191
81090
  init_ops();
@@ -80195,7 +81094,7 @@ var init_fanout_worktree = __esm(() => {
80195
81094
  SLOW = GIT_TIMEOUTS.worktree;
80196
81095
  EXCLUDE_LINE = `/${SCRATCH_DIR}/`;
80197
81096
  worktreeDeps = {
80198
- stat: (file) => fs25.statSync(file, { bigint: true })
81097
+ stat: (file) => fs26.statSync(file, { bigint: true })
80199
81098
  };
80200
81099
  });
80201
81100
 
@@ -80236,7 +81135,9 @@ function renderBrief(cwd, plan, input) {
80236
81135
  id: row.id,
80237
81136
  what: String(row.what)
80238
81137
  }));
80239
- const session = workerSession(sessionSafe(input.actor.session), slice.id, input.attempt);
81138
+ const identity = identityOf(input.actor.host);
81139
+ const minted = workerSession(sessionSafe(input.actor.session), slice.id, input.attempt);
81140
+ const session = identity === "declared" ? minted : null;
80240
81141
  const fanIn = plan.fanIn === "integration" ? [
80241
81142
  `FAN-IN: integration branch ${plan.trunk}. Before you report, merge its tip into your branch`,
80242
81143
  ` (\`git merge --no-edit ${plan.trunk}\`), resolve conflicts inside SCOPE and re-run VERIFY, so the`,
@@ -80265,8 +81166,8 @@ function renderBrief(cwd, plan, input) {
80265
81166
  ...REPORT,
80266
81167
  "STANDING:",
80267
81168
  ...standing.length ? standing.map((order) => ` - ${order.what}`) : [" - none recorded beyond this brief"],
80268
- ` export WORKIT_SESSION_ID=${session}`,
80269
- ...rules(mode, branch, slice.base)
81169
+ ...identityLines(identity, minted),
81170
+ ...rules(identity, mode, branch, slice.base)
80270
81171
  ];
80271
81172
  return {
80272
81173
  ok: true,
@@ -80287,13 +81188,38 @@ function renderBrief(cwd, plan, input) {
80287
81188
  }
80288
81189
  };
80289
81190
  }
80290
- var DEFAULT_TIMEBOX = "30 minutes", REPORT, rules = (mode, branch, base) => [
80291
- "RULES:",
80292
- mode === "new" ? ` 1. First command: \`workit git branch ${branch} --base ${base}\` (your worktree may start on another name).` : ` 1. First command: \`git switch ${branch}\`, then continue from its head.`,
80293
- ' 2. Decide ambiguities yourself: `workit ledger ruling "<what>" --why "<why>" --cost-if-wrong "<cost>"`; stop only for an irreversible, security-sensitive or out-of-worktree action.',
80294
- " 3. Commit with `workit git commit -m <msg> -- <paths in SCOPE>`; push only if this brief says so.",
80295
- " 4. Never record a verdict on your own work."
80296
- ], sessionSafe = (text) => text.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-"), SESSION_MAX = 128;
81191
+ var DEFAULT_TIMEBOX = "30 minutes", REPORT, identityOf = (host) => {
81192
+ const family = host?.replace(/_(?:cli|desktop)$/, "") ?? "";
81193
+ if (family === "claude_code")
81194
+ return "issued";
81195
+ if (family === "opencode" || family === "pi" || family === "codex")
81196
+ return "derived";
81197
+ return "declared";
81198
+ }, identityLines = (identity, session) => {
81199
+ if (identity === "issued")
81200
+ return [
81201
+ " Session: your env already has WORKIT_SESSION_ID (the lead's id; SubagentStart issues ids only to verifiers and reviewers). Use it as is, in your Workit-Session trailer and workit commands; never export or hand-mint one."
81202
+ ];
81203
+ if (identity === "derived")
81204
+ return [
81205
+ " Session: your host already sets WORKIT_SESSION_ID in your shell; export nothing and never hand-mint one."
81206
+ ];
81207
+ return [` export WORKIT_SESSION_ID=${session}`];
81208
+ }, rules = (identity, mode, branch, base) => {
81209
+ const plain = identity === "issued";
81210
+ let first = ` 1. First command: \`workit git branch ${branch} --base ${base}\` (your worktree may start on another name).`;
81211
+ if (mode === "resume")
81212
+ first = ` 1. First command: \`git switch ${branch}\`, then continue from its head.`;
81213
+ else if (plain)
81214
+ first = ` 1. First command: \`git fetch origin && git switch -c ${branch} origin/${base}\` (plain git: worktree isolation refuses every \`workit git\` form).`;
81215
+ return [
81216
+ "RULES:",
81217
+ first,
81218
+ ' 2. Decide ambiguities yourself: `workit ledger ruling "<what>" --why "<why>" --cost-if-wrong "<cost>"`; stop only for an irreversible, security-sensitive or out-of-worktree action.',
81219
+ plain ? ' 3. Commit with plain `git commit -m <msg> --trailer "Workit-Session: <the WORKIT_SESSION_ID in your env>" --trailer "<your host attribution trailer>" -- <paths in SCOPE>`; push only if this brief says so.' : " 3. Commit with `workit git commit -m <msg> -- <paths in SCOPE>`; push only if this brief says so.",
81220
+ " 4. Never record a verdict on your own work."
81221
+ ];
81222
+ }, sessionSafe = (text) => text.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-"), SESSION_MAX = 128;
80297
81223
  var init_fanout_brief = __esm(() => {
80298
81224
  init_ledger();
80299
81225
  init_fanout();
@@ -80349,9 +81275,9 @@ function hasOwnCommits2(cwd, branch, start, head) {
80349
81275
  const onto = /^rebase(?: -i)? \(finish\): \S+ onto ([0-9a-f]+)/u.exec(subject)?.[1];
80350
81276
  if (!made && !(onto && !sha.startsWith(onto) && !onto.startsWith(sha)))
80351
81277
  continue;
80352
- if (sha !== head && !isAncestor2(cwd, sha, head))
81278
+ if (sha !== head && !isAncestor3(cwd, sha, head))
80353
81279
  continue;
80354
- if (start && (sha === start || isAncestor2(cwd, sha, start)))
81280
+ if (start && (sha === start || isAncestor3(cwd, sha, start)))
80355
81281
  continue;
80356
81282
  return true;
80357
81283
  }
@@ -80361,7 +81287,7 @@ function reverted(cwd, trunk, landedHead, start) {
80361
81287
  const fork = mergeBase(cwd, landedHead, trunk);
80362
81288
  if (fork && fork !== landedHead)
80363
81289
  return unchangedSince(cwd, fork, landedHead, trunk);
80364
- const before = start && start !== landedHead && isAncestor2(cwd, start, landedHead) ? start : gitRun4(cwd, ["rev-parse", "--verify", "-q", `${landedHead}^1`]).stdout.trim();
81290
+ const before = start && start !== landedHead && isAncestor3(cwd, start, landedHead) ? start : gitRun4(cwd, ["rev-parse", "--verify", "-q", `${landedHead}^1`]).stdout.trim();
80365
81291
  return before ? unchangedSince(cwd, before, landedHead, trunk) : false;
80366
81292
  }
80367
81293
  function unchangedSince(cwd, before, after, trunk) {
@@ -80469,9 +81395,9 @@ function detectLanding(ctx, slice, input) {
80469
81395
  }
80470
81396
  return merged ? landed("pr_merged", merged) : { ...out, landed: true, how: "pr_merged" };
80471
81397
  }
80472
- if (!merged || head === merged || isAncestor2(ctx.cwd, head, merged))
81398
+ if (!merged || head === merged || isAncestor3(ctx.cwd, head, merged))
80473
81399
  return landed("pr_merged", merged ?? head);
80474
- out.note = isAncestor2(ctx.cwd, merged, head) ? `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} is ahead of it with newer commits` : `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} has diverged from it (rewritten after the merge)`;
81400
+ out.note = isAncestor3(ctx.cwd, merged, head) ? `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} is ahead of it with newer commits` : `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} has diverged from it (rewritten after the merge)`;
80475
81401
  return out;
80476
81402
  }
80477
81403
  }
@@ -80488,7 +81414,7 @@ function detectLanding(ctx, slice, input) {
80488
81414
  if (trunkIds.size === 0)
80489
81415
  return out;
80490
81416
  const froms = [fork];
80491
- if (input.parentHead && input.parentHead !== head && isAncestor2(ctx.cwd, input.parentHead, head))
81417
+ if (input.parentHead && input.parentHead !== head && isAncestor3(ctx.cwd, input.parentHead, head))
80492
81418
  froms.push(input.parentHead);
80493
81419
  for (const from of froms) {
80494
81420
  const id = patchId(ctx.cwd, from, head);
@@ -80504,7 +81430,7 @@ function sliceStart(cwd, rows, fanout, slice) {
80504
81430
  const row = rows.findLast((candidate) => candidate.type === "fanout.worktree.created" && candidate.fanout === fanout && candidate.slice === slice.id && typeof candidate.baseSha === "string" && inSliceRun(candidate, slice));
80505
81431
  return row?.baseSha ?? null;
80506
81432
  }
80507
- var isAncestor2 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, TRUNK_SCAN = 500;
81433
+ var isAncestor3 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, TRUNK_SCAN = 500;
80508
81434
  var init_fanout_landed = __esm(() => {
80509
81435
  init_rev();
80510
81436
  init_fanout();
@@ -80561,7 +81487,7 @@ function checkFanout(cwd, plan, options = {}) {
80561
81487
  const b = tips.get(second);
80562
81488
  if (landings.get(first)?.landed || landings.get(second)?.landed)
80563
81489
  continue;
80564
- if (!a || !b || isAncestor3(cwd, a, b) || isAncestor3(cwd, b, a))
81490
+ if (!a || !b || isAncestor4(cwd, a, b) || isAncestor4(cwd, b, a))
80565
81491
  continue;
80566
81492
  const check = checks.get(second);
80567
81493
  const theirs = new Map((changedBy.get(first) ?? []).map((file) => [file.toLowerCase(), file]));
@@ -80741,7 +81667,7 @@ function unblockFor(check, plan) {
80741
81667
  return `unblock ${check.waitsFor.join(", ")} first`;
80742
81668
  return check.reasons[0] ?? "workit fanout check";
80743
81669
  }
80744
- var SAFE = (value) => Boolean(value) && !value?.startsWith("-"), isAncestor3 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, resolves = (cwd, ref) => gitRun4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]).ok, blank2 = (slice, head) => ({
81670
+ var SAFE = (value) => Boolean(value) && !value?.startsWith("-"), isAncestor4 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, resolves = (cwd, ref) => gitRun4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]).ok, blank2 = (slice, head) => ({
80745
81671
  id: slice.id,
80746
81672
  branch: slice.branch,
80747
81673
  head,
@@ -80960,8 +81886,8 @@ var exports_fanout = {};
80960
81886
  __export(exports_fanout, {
80961
81887
  run: () => run16
80962
81888
  });
80963
- import fs26 from "node:fs";
80964
- import path49 from "node:path";
81889
+ import fs27 from "node:fs";
81890
+ import path50 from "node:path";
80965
81891
  function forgeFor(io, offline) {
80966
81892
  if (offline)
80967
81893
  return { forge: null, error: "--offline" };
@@ -81022,10 +81948,10 @@ function plan2(argv, io) {
81022
81948
  return usage3(io, "pass --trunk or --track, not both (--track picks the trunk)", PLAN_USAGE2);
81023
81949
  if (flags.positionals.length !== 1)
81024
81950
  return usage3(io, flags.positionals.length ? `unexpected argument ${flags.positionals[1]}` : "missing <plan.json>", PLAN_USAGE2);
81025
- const file = path49.resolve(io.cwd, flags.positionals[0]);
81951
+ const file = path50.resolve(io.cwd, flags.positionals[0]);
81026
81952
  let raw;
81027
81953
  try {
81028
- raw = JSON.parse(fs26.readFileSync(file, "utf8"));
81954
+ raw = JSON.parse(fs27.readFileSync(file, "utf8"));
81029
81955
  } catch (error) {
81030
81956
  return usage3(io, `cannot read ${file} as JSON: ${error.message}`, PLAN_USAGE2);
81031
81957
  }
@@ -96510,7 +97436,7 @@ var init_ast = __esm(() => {
96510
97436
 
96511
97437
  // packages/workit-cli/src/test-audit/rules.ts
96512
97438
  import { builtinModules } from "node:module";
96513
- import path50 from "node:path";
97439
+ import path51 from "node:path";
96514
97440
  function testCallee(call) {
96515
97441
  let callee = call.callee;
96516
97442
  if (callee.type === "CallExpression")
@@ -97003,12 +97929,12 @@ function checkConstantsOnly(ctx, emit) {
97003
97929
  }
97004
97930
  function checkOverMocking(ctx, emit) {
97005
97931
  const { program } = ctx.parsed;
97006
- const unit = path50.basename(ctx.file).replace(/\.(?:[cm]?[jt]sx?)$/, "").replace(/\.(?:test|spec)$/, "");
97932
+ const unit = path51.basename(ctx.file).replace(/\.(?:[cm]?[jt]sx?)$/, "").replace(/\.(?:test|spec)$/, "");
97007
97933
  const mocked = find2(program, (node) => node.type === "CallExpression" && MODULE_MOCK.test(calleeName(node.callee) ?? "") && node.arguments[0]?.type === "StringLiteral");
97008
97934
  const internal = mocked.filter((call) => /^(?:\.|@\/|~\/)/.test(call.arguments[0].value));
97009
97935
  for (const call of mocked) {
97010
97936
  const spec = call.arguments[0].value;
97011
- const base = path50.basename(spec.replace(/\/index(?:\.[cm]?[jt]sx?)?$/, "")).replace(/\.(?:[cm]?[jt]sx?)$/, "");
97937
+ const base = path51.basename(spec.replace(/\/index(?:\.[cm]?[jt]sx?)?$/, "")).replace(/\.(?:[cm]?[jt]sx?)$/, "");
97012
97938
  if (base === unit)
97013
97939
  emit({
97014
97940
  rule: "over-mocking",
@@ -97398,7 +98324,7 @@ var init_rules = __esm(() => {
97398
98324
  // packages/workit-cli/src/test-audit/audit.ts
97399
98325
  import { spawnSync as spawnSync26 } from "node:child_process";
97400
98326
  import { existsSync as existsSync20, readdirSync as readdirSync9, readFileSync as readFileSync23, statSync as statSync8 } from "node:fs";
97401
- import path51 from "node:path";
98327
+ import path52 from "node:path";
97402
98328
  function git7(cwd, args) {
97403
98329
  const run = spawnSync26("git", ["-c", "core.quotepath=off", ...args], {
97404
98330
  cwd,
@@ -97411,7 +98337,7 @@ function walkTests(dir, out) {
97411
98337
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
97412
98338
  if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
97413
98339
  continue;
97414
- const full = path51.join(dir, entry.name);
98340
+ const full = path52.join(dir, entry.name);
97415
98341
  if (entry.isDirectory()) {
97416
98342
  if (!SKIP_DIRS.has(entry.name))
97417
98343
  walkTests(full, out);
@@ -97423,7 +98349,7 @@ function expandPaths(cwd, inputs) {
97423
98349
  const files = [];
97424
98350
  const missing = [];
97425
98351
  for (const input of inputs.length > 0 ? inputs : ["."]) {
97426
- const full = path51.resolve(cwd, input);
98352
+ const full = path52.resolve(cwd, input);
97427
98353
  if (!existsSync20(full))
97428
98354
  missing.push(input);
97429
98355
  else if (statSync8(full).isDirectory())
@@ -97460,11 +98386,11 @@ function changedLines(cwd, base) {
97460
98386
  for (let offset = 0;offset < count; offset += 1)
97461
98387
  lines.add(start + offset);
97462
98388
  }
97463
- changed.set(path51.join(root, file), lines);
98389
+ changed.set(path52.join(root, file), lines);
97464
98390
  }
97465
98391
  const untracked = git7(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
97466
98392
  for (const file of untracked.stdout.split("\x00").filter(Boolean))
97467
- changed.set(path51.join(root, file), "all");
98393
+ changed.set(path52.join(root, file), "all");
97468
98394
  return changed;
97469
98395
  }
97470
98396
  function auditFiles(files, display) {
@@ -97526,7 +98452,7 @@ function auditFiles(files, display) {
97526
98452
  durationMs: Math.round(performance.now() - started)
97527
98453
  };
97528
98454
  }
97529
- var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(path51.sep).join("/"), SEVERITY_ORDER, ruleIds;
98455
+ var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(path52.sep).join("/"), SEVERITY_ORDER, ruleIds;
97530
98456
  var init_audit = __esm(() => {
97531
98457
  init_ast();
97532
98458
  init_rules();
@@ -97555,8 +98481,8 @@ import {
97555
98481
  writeFileSync as writeFileSync11,
97556
98482
  copyFileSync as copyFileSync6
97557
98483
  } from "node:fs";
97558
- import os14 from "node:os";
97559
- import path52 from "node:path";
98484
+ import os16 from "node:os";
98485
+ import path53 from "node:path";
97560
98486
  function mutantsFor(source, file, lines) {
97561
98487
  const parsed = parseSource(source, file);
97562
98488
  const out = [];
@@ -97629,7 +98555,7 @@ function relatedTests(source, testFiles) {
97629
98555
  for (const match of text.matchAll(SPECIFIER)) {
97630
98556
  const spec = match[1];
97631
98557
  if (spec.startsWith(".")) {
97632
- if (stripExt(path52.posix.join(path52.posix.dirname(file), spec)) === target)
98558
+ if (stripExt(path53.posix.join(path53.posix.dirname(file), spec)) === target)
97633
98559
  return true;
97634
98560
  } else if (spec.includes("/") && stripExt(spec).split("/").slice(-2).join("/") === tail)
97635
98561
  return true;
@@ -97637,7 +98563,7 @@ function relatedTests(source, testFiles) {
97637
98563
  return false;
97638
98564
  }).map((entry) => entry.file);
97639
98565
  }
97640
- function removeStaleCopies(tmp = os14.tmpdir()) {
98566
+ function removeStaleCopies(tmp = os16.tmpdir()) {
97641
98567
  let removed = 0;
97642
98568
  let names = [];
97643
98569
  try {
@@ -97649,12 +98575,12 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
97649
98575
  for (const name of names) {
97650
98576
  if (!name.startsWith(TEMP_PREFIX))
97651
98577
  continue;
97652
- const dir = path52.join(tmp, name);
98578
+ const dir = path53.join(tmp, name);
97653
98579
  try {
97654
98580
  const stat = lstatSync8(dir);
97655
98581
  if (!stat.isDirectory() || uid !== undefined && stat.uid !== uid)
97656
98582
  continue;
97657
- const pid = Number(readFileSync24(path52.join(dir, PID_FILE), "utf8"));
98583
+ const pid = Number(readFileSync24(path53.join(dir, PID_FILE), "utf8"));
97658
98584
  if (Number.isInteger(pid) && pid > 0 && alive(pid))
97659
98585
  continue;
97660
98586
  } catch (error) {
@@ -97671,8 +98597,8 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
97671
98597
  function linkNodeModules(origin, target, repo, copy) {
97672
98598
  mkdirSync10(target, { recursive: true });
97673
98599
  for (const entry of readdirSync10(origin, { withFileTypes: true })) {
97674
- const from = path52.join(origin, entry.name);
97675
- const to = path52.join(target, entry.name);
98600
+ const from = path53.join(origin, entry.name);
98601
+ const to = path53.join(target, entry.name);
97676
98602
  if (entry.name.startsWith("@") && entry.isDirectory()) {
97677
98603
  linkNodeModules(from, to, repo, copy);
97678
98604
  continue;
@@ -97688,9 +98614,9 @@ function linkNodeModules(origin, target, repo, copy) {
97688
98614
  } catch {
97689
98615
  continue;
97690
98616
  }
97691
- const relative = path52.relative(repo, real);
97692
- if (!relative.startsWith("..") && !path52.isAbsolute(relative) && !relative.split(path52.sep).includes("node_modules")) {
97693
- symlinkSync(path52.join(copy, relative), to, "junction");
98617
+ const relative = path53.relative(repo, real);
98618
+ if (!relative.startsWith("..") && !path53.isAbsolute(relative) && !relative.split(path53.sep).includes("node_modules")) {
98619
+ symlinkSync(path53.join(copy, relative), to, "junction");
97694
98620
  continue;
97695
98621
  }
97696
98622
  }
@@ -97706,7 +98632,7 @@ function copySymlink(repo, tree, from, to) {
97706
98632
  }
97707
98633
  const realRepo = realpathSync9(repo);
97708
98634
  if (real && inside4(realRepo, real)) {
97709
- symlinkSync(path52.relative(path52.dirname(to), path52.join(tree, path52.relative(realRepo, real))), to);
98635
+ symlinkSync(path53.relative(path53.dirname(to), path53.join(tree, path53.relative(realRepo, real))), to);
97710
98636
  return;
97711
98637
  }
97712
98638
  if (real && statSync9(real).isFile())
@@ -97715,10 +98641,10 @@ function copySymlink(repo, tree, from, to) {
97715
98641
  symlinkSync(readlinkSync3(from), to);
97716
98642
  }
97717
98643
  function writeInside(copy, file, content) {
97718
- const target = path52.join(copy, file);
98644
+ const target = path53.join(copy, file);
97719
98645
  try {
97720
98646
  const stat = lstatSync8(target);
97721
- if (!stat.isFile() || !inside4(realpathSync9(copy), realpathSync9(path52.dirname(target))))
98647
+ if (!stat.isFile() || !inside4(realpathSync9(copy), realpathSync9(path53.dirname(target))))
97722
98648
  return false;
97723
98649
  } catch {
97724
98650
  return false;
@@ -97729,9 +98655,9 @@ function writeInside(copy, file, content) {
97729
98655
  return true;
97730
98656
  }
97731
98657
  function copyWorkingTree(repo) {
97732
- const dir = mkdtempSync4(path52.join(os14.tmpdir(), TEMP_PREFIX));
97733
- writeFileSync11(path52.join(dir, PID_FILE), String(process.pid));
97734
- const tree = path52.join(dir, "tree");
98658
+ const dir = mkdtempSync4(path53.join(os16.tmpdir(), TEMP_PREFIX));
98659
+ writeFileSync11(path53.join(dir, PID_FILE), String(process.pid));
98660
+ const tree = path53.join(dir, "tree");
97735
98661
  const head = git7(repo, ["rev-parse", "--verify", "--quiet", "HEAD"]).stdout.trim();
97736
98662
  if (head && git7(dir, ["clone", "-q", "--shared", "--no-checkout", repo, tree]).ok) {
97737
98663
  const branch = git7(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]).stdout.trim();
@@ -97748,29 +98674,29 @@ function copyWorkingTree(repo) {
97748
98674
  for (const relative of listed) {
97749
98675
  if (!relative)
97750
98676
  continue;
97751
- const from = path52.join(repo, relative);
98677
+ const from = path53.join(repo, relative);
97752
98678
  let stat;
97753
98679
  try {
97754
98680
  stat = lstatSync8(from);
97755
98681
  } catch {
97756
98682
  continue;
97757
98683
  }
97758
- const to = path52.join(tree, relative);
97759
- mkdirSync10(path52.dirname(to), { recursive: true });
98684
+ const to = path53.join(tree, relative);
98685
+ mkdirSync10(path53.dirname(to), { recursive: true });
97760
98686
  if (stat.isSymbolicLink())
97761
98687
  copySymlink(repo, tree, from, to);
97762
98688
  else if (stat.isFile())
97763
98689
  copyFileSync6(from, to);
97764
- let parent = path52.dirname(relative);
98690
+ let parent = path53.dirname(relative);
97765
98691
  while (parent !== "." && !dirs.has(parent)) {
97766
98692
  dirs.add(parent);
97767
- parent = path52.dirname(parent);
98693
+ parent = path53.dirname(parent);
97768
98694
  }
97769
98695
  }
97770
98696
  for (const parent of dirs) {
97771
- const modules = path52.join(repo, parent, "node_modules");
97772
- if (existsSync21(modules) && !existsSync21(path52.join(tree, parent, "node_modules")))
97773
- linkNodeModules(modules, path52.join(tree, parent, "node_modules"), repo, tree);
98697
+ const modules = path53.join(repo, parent, "node_modules");
98698
+ if (existsSync21(modules) && !existsSync21(path53.join(tree, parent, "node_modules")))
98699
+ linkNodeModules(modules, path53.join(tree, parent, "node_modules"), repo, tree);
97774
98700
  }
97775
98701
  return { dir, tree };
97776
98702
  }
@@ -97809,14 +98735,14 @@ async function runMutation(options) {
97809
98735
  const tracked = git7(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
97810
98736
  const testFiles = tracked.filter((file) => TEST_FILE.test(file)).map((file) => {
97811
98737
  try {
97812
- return { file, text: readFileSync24(path52.join(repo, file), "utf8") };
98738
+ return { file, text: readFileSync24(path53.join(repo, file), "utf8") };
97813
98739
  } catch {
97814
98740
  return { file, text: "" };
97815
98741
  }
97816
98742
  });
97817
98743
  const candidates = [];
97818
98744
  for (const [absolute, lines] of [...options.changed].toSorted(([a], [b]) => a.localeCompare(b))) {
97819
- const relative = toPosix(path52.relative(repo, absolute));
98745
+ const relative = toPosix(path53.relative(repo, absolute));
97820
98746
  if (!CODE_FILE.test(relative) || TEST_FILE.test(relative) || relative.endsWith(".d.ts"))
97821
98747
  continue;
97822
98748
  if (NOT_SOURCE.test(relative) || !existsSync21(absolute))
@@ -97830,9 +98756,9 @@ async function runMutation(options) {
97830
98756
  for (const mutant of mutants)
97831
98757
  if (!related.has(mutant.file))
97832
98758
  related.set(mutant.file, relatedTests(mutant.file, testFiles));
97833
- const cwdRelative = path52.relative(repo, options.cwd);
98759
+ const cwdRelative = path53.relative(repo, options.cwd);
97834
98760
  const asArg = (file) => {
97835
- const relative = toPosix(path52.relative(cwdRelative, file));
98761
+ const relative = toPosix(path53.relative(cwdRelative, file));
97836
98762
  return shellQuote(relative.startsWith(".") ? relative : `./${relative}`);
97837
98763
  };
97838
98764
  const commandFor = (tests) => options.command.replaceAll("{files}", () => tests.map(asArg).join(" "));
@@ -97867,7 +98793,7 @@ async function runMutation(options) {
97867
98793
  return finish2(options, 0, results, counts, started);
97868
98794
  removeStaleCopies();
97869
98795
  const { dir: copyDir, tree: copy } = copyWorkingTree(repo);
97870
- const copyCwd = path52.join(copy, cwdRelative);
98796
+ const copyCwd = path53.join(copy, cwdRelative);
97871
98797
  let current = null;
97872
98798
  const restore = () => {
97873
98799
  if (current)
@@ -97902,7 +98828,7 @@ async function runMutation(options) {
97902
98828
  record({ ...mutant, status: "skipped", durationMs: 0, tests, reason: "budget" });
97903
98829
  continue;
97904
98830
  }
97905
- const original = readFileSync24(path52.join(copy, mutant.file), "utf8");
98831
+ const original = readFileSync24(path53.join(copy, mutant.file), "utf8");
97906
98832
  const mutated = original.slice(0, mutant.start) + mutant.replacement + original.slice(mutant.end);
97907
98833
  current = { file: mutant.file, original };
97908
98834
  if (!writeInside(copy, mutant.file, mutated)) {
@@ -97959,8 +98885,8 @@ var NOT_SOURCE, FLIPS, isStringy = (node) => node.type === "StringLiteral" || no
97959
98885
  return error.code === "EPERM";
97960
98886
  }
97961
98887
  }, inside4 = (root, file) => {
97962
- const relative = path52.relative(root, file);
97963
- return relative === "" || !relative.startsWith("..") && !path52.isAbsolute(relative);
98888
+ const relative = path53.relative(root, file);
98889
+ return relative === "" || !relative.startsWith("..") && !path53.isAbsolute(relative);
97964
98890
  }, active, killTree = (child) => {
97965
98891
  try {
97966
98892
  if (child.pid && process.platform !== "win32")
@@ -98009,7 +98935,7 @@ __export(exports_test_audit, {
98009
98935
  run: () => run18
98010
98936
  });
98011
98937
  import { existsSync as existsSync22 } from "node:fs";
98012
- import path53 from "node:path";
98938
+ import path54 from "node:path";
98013
98939
  function parse7(argv) {
98014
98940
  const options = {
98015
98941
  paths: [],
@@ -98113,8 +99039,8 @@ async function run18(argv, io) {
98113
99039
  return emit(io, fail("invalid_input", `${options}
98114
99040
  usage: ${USAGE10}`));
98115
99041
  const top = git7(io.cwd, ["rev-parse", "--show-toplevel"]);
98116
- const repo = top.ok ? path53.resolve(top.stdout.trim()) : null;
98117
- const display = (file) => toPosix(path53.relative(io.cwd, file) || file);
99042
+ const repo = top.ok ? path54.resolve(top.stdout.trim()) : null;
99043
+ const display = (file) => toPosix(path54.relative(io.cwd, file) || file);
98118
99044
  let base = null;
98119
99045
  let changed = null;
98120
99046
  if (options.diff || options.mutate) {
@@ -98357,7 +99283,7 @@ __export(exports_youtrack, {
98357
99283
  run: () => run20
98358
99284
  });
98359
99285
  import { readFileSync as readFileSync25 } from "node:fs";
98360
- import path54 from "node:path";
99286
+ import path55 from "node:path";
98361
99287
  async function run20(argv, io) {
98362
99288
  const [sub, ...rest] = argv;
98363
99289
  if (sub !== "note" && sub !== "time" && sub !== "meeting")
@@ -98403,7 +99329,7 @@ async function run20(argv, io) {
98403
99329
  let markdown = flags.values.markdown ?? "";
98404
99330
  if (flags.values.file)
98405
99331
  try {
98406
- markdown = readFileSync25(path54.resolve(io.cwd, flags.values.file), "utf8");
99332
+ markdown = readFileSync25(path55.resolve(io.cwd, flags.values.file), "utf8");
98407
99333
  } catch (error) {
98408
99334
  return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE12);
98409
99335
  }
@@ -98472,7 +99398,7 @@ __export(exports_changelog, {
98472
99398
  run: () => run21
98473
99399
  });
98474
99400
  import { readFileSync as readFileSync26 } from "node:fs";
98475
- import path55 from "node:path";
99401
+ import path56 from "node:path";
98476
99402
  async function run21(argv, io) {
98477
99403
  const [sub, ...rest] = argv;
98478
99404
  if (sub !== "apply")
@@ -98518,7 +99444,7 @@ async function run21(argv, io) {
98518
99444
  ]);
98519
99445
  }
98520
99446
  var USAGE13 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
98521
- const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(path55.resolve(cwd, raw.slice(1)), "utf8") : raw;
99447
+ const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(path56.resolve(cwd, raw.slice(1)), "utf8") : raw;
98522
99448
  return JSON.parse(text);
98523
99449
  };
98524
99450
  var init_changelog2 = __esm(() => {
@@ -98614,7 +99540,7 @@ var init_handoff = __esm(() => {
98614
99540
 
98615
99541
  // packages/workit-cli/src/main.ts
98616
99542
  init_package();
98617
- import path56 from "node:path";
99543
+ import path57 from "node:path";
98618
99544
 
98619
99545
  // packages/workit-cli/src/help.ts
98620
99546
  var HELP_FLAGS = new Set(["--help", "-h"]);
@@ -98879,7 +99805,7 @@ var VERBS = [
98879
99805
  {
98880
99806
  name: "check",
98881
99807
  group: "delivery",
98882
- usage: "workit check <name> [--task <id>] [--base <ref>] | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--base <ref>] -- <cmd…>",
99808
+ usage: "workit check <name> [--task <id>] [--base <ref>] [--no-lease] | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--base <ref>] [--heavy] [--no-lease] -- <cmd…>",
98883
99809
  summary: "Run a configured or ad-hoc check and record CLI-observed evidence (exit = the command's)",
98884
99810
  examples: ["workit check test", "workit check --name unit -- bun test test/unit"],
98885
99811
  load: () => Promise.resolve().then(() => (init_check(), exports_check))
@@ -98921,8 +99847,8 @@ var VERBS = [
98921
99847
  },
98922
99848
  {
98923
99849
  name: "merge",
98924
- usage: "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch] [--unverified --reason <why>]",
98925
- summary: "Merge when READY, verified by a non-author and granted"
99850
+ usage: "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch] [--unverified --reason <why>]",
99851
+ summary: "Merge when READY, verified by a non-author and granted; --subject/--body set the squash message"
98926
99852
  }
98927
99853
  ],
98928
99854
  examples: [
@@ -98962,18 +99888,24 @@ var VERBS = [
98962
99888
  },
98963
99889
  {
98964
99890
  name: "commit",
98965
- usage: "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>]",
98966
- summary: "Commit (or --amend) with a linted Conventional Commit message and the session trailer"
99891
+ usage: "workit git commit -m|--message <msg>… | -F|--file <file|-> [--trailer <key: value>]… [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>]",
99892
+ summary: "Commit (or --amend) with a linted Conventional Commit message, the session trailer (unless the workspace sets trailers: false) and any --trailer"
98967
99893
  },
98968
99894
  {
98969
99895
  name: "push",
98970
99896
  usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…]",
98971
99897
  summary: "Push the current branch and verify the remote tip (never a blind --force); --release finishes a git-flow release"
99898
+ },
99899
+ {
99900
+ name: "trailers",
99901
+ usage: "workit git trailers",
99902
+ summary: "Print on or off: whether commits in this workspace get the Workit-Session trailer"
98972
99903
  }
98973
99904
  ],
98974
99905
  examples: [
98975
99906
  "workit git branch --kind feature --slug help-everywhere",
98976
99907
  'workit git commit -m "fix(cli): answer help flags first" --all',
99908
+ 'workit git commit -m "feat: x" --trailer "Co-Authored-By: Name <email>" --all',
98977
99909
  "workit git commit --amend --no-edit -- src/forgotten.ts",
98978
99910
  "workit git push --set-upstream"
98979
99911
  ],
@@ -99260,7 +100192,7 @@ var defaultIo = (json) => ({
99260
100192
  });
99261
100193
  async function main(argv, overrides = {}, options = {}) {
99262
100194
  const parsed = parseGlobals(argv);
99263
- const cwd = path56.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
100195
+ const cwd = path57.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
99264
100196
  const io = { ...defaultIo(parsed.json), ...overrides, json: parsed.json, cwd };
99265
100197
  if (parsed.error)
99266
100198
  return emit(io, fail("invalid_input", parsed.error));