@brainervirus/workit-codex 8.22.3 → 8.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@ import {
12
12
  readStandingRev,
13
13
  readStandingSeen,
14
14
  writeStandingSeen
15
- } from "./workit-hook-thet0x8m.js";
15
+ } from "./workit-hook-vhz9yrcw.js";
16
16
  // packages/workit-core/src/hooks/descriptor.ts
17
17
  var support = (descriptor, axis) => {
18
18
  if (axis.startsWith("event:"))
@@ -40,7 +40,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
40
40
  // packages/workit-core/src/hooks/context.ts
41
41
  import { createHash as createHash12 } from "node:crypto";
42
42
  import fs13 from "node:fs";
43
- import path17 from "node:path";
43
+ import path18 from "node:path";
44
44
 
45
45
  // packages/workit-core/src/core/methods.ts
46
46
  var METHODS = {
@@ -115,9 +115,9 @@ Autonomy contract:
115
115
  Prose is a note: only a configured check the CLI ran (\`workit check <name>\`)
116
116
  satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
117
117
  - Author is not verifier: your own verdict is \`--self\`, shown as
118
- self-reviewed, never verified. High risk, \`verification: independent\` and
119
- merge need a verifier with its own host-derived session (a subagent's
120
- \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
118
+ self-reviewed, never verified. High risk, a focused or full lane and
119
+ merge under \`verification: self|independent\` need a verifier with its
120
+ own host-derived session (a subagent's \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
121
121
  - A local-commit endpoint does not imply PR readiness, and a local commit alone
122
122
  is not evidence of a requested remote push. Before reporting delivery,
123
123
  reconcile every requested item and observe it (\`workit verify-delivery\`).
@@ -7174,7 +7174,7 @@ function requirementId(input) {
7174
7174
  import { createHash as createHash11 } from "node:crypto";
7175
7175
  import * as fs12 from "node:fs";
7176
7176
  import { spawnSync as spawnSync9 } from "node:child_process";
7177
- import path15 from "node:path";
7177
+ import path16 from "node:path";
7178
7178
 
7179
7179
  // packages/workit-core/src/git/rev.ts
7180
7180
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -7337,6 +7337,124 @@ function resolveRef(cwd, ref) {
7337
7337
  return null;
7338
7338
  return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
7339
7339
  }
7340
+ var LANE_DIFF = [
7341
+ "-c",
7342
+ "core.quotePath=false",
7343
+ "diff",
7344
+ "--no-color",
7345
+ "--no-ext-diff",
7346
+ "--no-textconv",
7347
+ "--no-renames"
7348
+ ];
7349
+ var laneRange = (fork, head) => {
7350
+ if (!safeArg(fork) || head !== null && !safeArg(head))
7351
+ return null;
7352
+ return head === null ? [fork] : [fork, head];
7353
+ };
7354
+ function diffNumstat(cwd, fork, head) {
7355
+ const range = laneRange(fork, head);
7356
+ if (!range)
7357
+ return null;
7358
+ const run = git(cwd, [...LANE_DIFF, "--numstat", "-z", ...range, "--"]);
7359
+ if (!run.ok)
7360
+ return null;
7361
+ const out = [];
7362
+ for (const record of run.stdout.split("\x00")) {
7363
+ const match = /^(-|\d+)\t(-|\d+)\t(.+)$/su.exec(record.replace(/^\n/u, ""));
7364
+ if (!match)
7365
+ continue;
7366
+ const binary = match[1] === "-";
7367
+ out.push({
7368
+ path: match[3],
7369
+ added: binary ? 0 : Number(match[1]),
7370
+ deleted: binary ? 0 : Number(match[2]),
7371
+ binary
7372
+ });
7373
+ }
7374
+ return out;
7375
+ }
7376
+ function diffRaw(cwd, fork, head) {
7377
+ const range = laneRange(fork, head);
7378
+ if (!range)
7379
+ return null;
7380
+ const run = git(cwd, [...LANE_DIFF, "--raw", "-z", "--no-abbrev", ...range, "--"]);
7381
+ if (!run.ok)
7382
+ return null;
7383
+ const fields = run.stdout.split("\x00");
7384
+ const out = [];
7385
+ for (let index = 0;index + 1 < fields.length; index += 2) {
7386
+ const meta = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) [A-Z]\d*$/u.exec(fields[index].replace(/^\n/u, ""));
7387
+ if (!meta)
7388
+ return null;
7389
+ out.push({
7390
+ path: fields[index + 1],
7391
+ oldMode: meta[1],
7392
+ newMode: meta[2],
7393
+ oldBlob: meta[3],
7394
+ newBlob: meta[4]
7395
+ });
7396
+ }
7397
+ return out;
7398
+ }
7399
+ function readBlobs(cwd, ids) {
7400
+ const out = new Map;
7401
+ const wanted = [...new Set(ids)].filter((id) => /^[0-9a-f]{40,64}$/u.test(id));
7402
+ if (!wanted.length)
7403
+ return out;
7404
+ const run = spawnSync2("git", ["cat-file", "--batch"], {
7405
+ cwd,
7406
+ input: `${wanted.join(`
7407
+ `)}
7408
+ `,
7409
+ maxBuffer: MAX_BUFFER,
7410
+ timeout: GIT_TIMEOUTS.local,
7411
+ killSignal: "SIGKILL",
7412
+ windowsHide: true
7413
+ });
7414
+ if (run.status !== 0 || !run.stdout)
7415
+ return null;
7416
+ const bytes = run.stdout;
7417
+ let at = 0;
7418
+ for (const id of wanted) {
7419
+ const end = bytes.indexOf(10, at);
7420
+ if (end < 0)
7421
+ return null;
7422
+ const header = bytes.subarray(at, end).toString("utf8").split(" ");
7423
+ at = end + 1;
7424
+ if (header[1] === "missing")
7425
+ continue;
7426
+ const size = Number(header[2]);
7427
+ if (header[0] !== id || !Number.isSafeInteger(size))
7428
+ return null;
7429
+ out.set(id, bytes.subarray(at, at + size));
7430
+ at += size + 1;
7431
+ }
7432
+ return out;
7433
+ }
7434
+ function treeFilesNamed(cwd, rev, name) {
7435
+ if (!safeArg(rev))
7436
+ return null;
7437
+ const run = git(cwd, ["ls-tree", "-r", "-z", "--full-tree", rev]);
7438
+ if (!run.ok)
7439
+ return null;
7440
+ const out = new Map;
7441
+ for (const record of run.stdout.split("\x00")) {
7442
+ const match = /^\d{6} blob ([0-9a-f]+)\t(.+)$/su.exec(record);
7443
+ if (match && (match[2] === name || match[2].endsWith(`/${name}`)))
7444
+ out.set(match[2], match[1]);
7445
+ }
7446
+ return out;
7447
+ }
7448
+ function fileAt(cwd, rev, file) {
7449
+ if (!safeArg(rev))
7450
+ return null;
7451
+ const run = git(cwd, ["cat-file", "blob", `${rev}:${file}`]);
7452
+ return run.ok ? run.stdout : null;
7453
+ }
7454
+ function untrackedFiles(cwd) {
7455
+ const run = git(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]);
7456
+ return run.ok ? run.stdout.split("\x00").filter(Boolean) : [];
7457
+ }
7340
7458
  var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
7341
7459
  function currentBranch(cwd) {
7342
7460
  return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
@@ -8329,7 +8447,7 @@ var workspaceConfigSchema = object({
8329
8447
  }).passthrough().optional(),
8330
8448
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
8331
8449
  defaultEndpoint: unknown().optional(),
8332
- verification: _enum(["self", "independent"]).optional(),
8450
+ verification: _enum(["proportional", "self", "independent"]).optional(),
8333
8451
  delegation: unknown().optional(),
8334
8452
  trailers: boolean2().optional(),
8335
8453
  liveSessionGuard: boolean2().optional(),
@@ -12032,8 +12150,8 @@ class TaskStore {
12032
12150
  }
12033
12151
 
12034
12152
  // packages/workit-core/src/core/policy/derive.ts
12035
- import { existsSync as existsSync4 } from "node:fs";
12036
- import path14 from "node:path";
12153
+ import { existsSync as existsSync5 } from "node:fs";
12154
+ import path15 from "node:path";
12037
12155
 
12038
12156
  // packages/workit-core/src/ledger.ts
12039
12157
  import { spawnSync as spawnSync8 } from "node:child_process";
@@ -12880,7 +12998,8 @@ function checkVerdicts(cwd, branch, ledgerRows) {
12880
12998
  verdict: failing?.verdict ?? null,
12881
12999
  reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
12882
13000
  };
12883
- const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
13001
+ const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
13002
+ const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
12884
13003
  return {
12885
13004
  branch,
12886
13005
  head,
@@ -12924,12 +13043,682 @@ var OBSERVED_ONLY = new Set([
12924
13043
  ...PR_ROW_TYPES
12925
13044
  ]);
12926
13045
 
13046
+ // packages/workit-core/src/core/policy/lane.ts
13047
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
13048
+ import path14 from "node:path";
13049
+ // packages/workit-core/src/forge/redact.ts
13050
+ var KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
13051
+ var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
13052
+ var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
13053
+ var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
13054
+
13055
+ // packages/workit-core/src/git/ops.ts
13056
+ var COMMIT_TIMEOUT_MS = 10 * 60000;
13057
+
13058
+ // packages/workit-core/src/core/policy/attributes.ts
13059
+ function patternRegex(dir, pattern) {
13060
+ if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
13061
+ return null;
13062
+ const anchored = pattern.includes("/");
13063
+ const body = pattern.replace(/^\//u, "");
13064
+ let source = "";
13065
+ for (let index = 0;index < body.length; index += 1) {
13066
+ const char = body[index];
13067
+ if (char === "*" && body[index + 1] === "*") {
13068
+ const slash = body[index + 2] === "/";
13069
+ source += slash ? "(?:.*/)?" : ".*";
13070
+ index += slash ? 2 : 1;
13071
+ } else if (char === "*")
13072
+ source += "[^/]*";
13073
+ else if (char === "?")
13074
+ source += "[^/]";
13075
+ else if (char === "[") {
13076
+ const end = body.indexOf("]", index + 2);
13077
+ if (end < 0)
13078
+ return null;
13079
+ const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
13080
+ source += `[${set}]`;
13081
+ index = end;
13082
+ } else if (char === "\\" && index + 1 < body.length) {
13083
+ source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
13084
+ index += 1;
13085
+ } else
13086
+ source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
13087
+ }
13088
+ const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
13089
+ try {
13090
+ return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
13091
+ } catch {
13092
+ return null;
13093
+ }
13094
+ }
13095
+ function rulesOf(dir, text) {
13096
+ const out = [];
13097
+ for (const raw of text.split(`
13098
+ `)) {
13099
+ const line = raw.trim();
13100
+ if (!line || line.startsWith("#") || line.startsWith("[attr]"))
13101
+ continue;
13102
+ const [pattern, ...attributes] = line.split(/\s+/u);
13103
+ let generated = null;
13104
+ for (const attribute of attributes) {
13105
+ if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
13106
+ generated = true;
13107
+ else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
13108
+ generated = false;
13109
+ }
13110
+ if (generated === null)
13111
+ continue;
13112
+ const regex = patternRegex(dir, pattern);
13113
+ if (regex)
13114
+ out.push({ regex, generated });
13115
+ }
13116
+ return out;
13117
+ }
13118
+ function generatedByAttributes(files, paths) {
13119
+ const rules = [...files].map(([file, text]) => {
13120
+ const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
13121
+ return {
13122
+ depth: dir ? dir.split("/").length : 0,
13123
+ rules: rulesOf(dir, text)
13124
+ };
13125
+ }).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
13126
+ const out = new Set;
13127
+ for (const file of paths) {
13128
+ const last = rules.findLast((rule) => rule.regex.test(file));
13129
+ if (last?.generated)
13130
+ out.add(file);
13131
+ }
13132
+ return out;
13133
+ }
13134
+
13135
+ // packages/workit-core/src/core/policy/substance.ts
13136
+ var SLASH_BLOCK = [["/*", "*/"]];
13137
+ var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
13138
+ var SYNTAXES = Object.fromEntries([
13139
+ ...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
13140
+ ...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
13141
+ ext,
13142
+ { ...C_LIKE, triple: true }
13143
+ ]),
13144
+ ["go", { ...C_LIKE, rawBacktick: true }],
13145
+ ...["css", "scss", "less"].map((ext) => [
13146
+ ext,
13147
+ { line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
13148
+ ]),
13149
+ ...["py", "pyi"].map((ext) => [
13150
+ ext,
13151
+ { line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
13152
+ ]),
13153
+ ["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
13154
+ ...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
13155
+ ext,
13156
+ { line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
13157
+ ])
13158
+ ]);
13159
+ var DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
13160
+ var extensionOf = (file) => {
13161
+ const name = file.slice(file.lastIndexOf("/") + 1);
13162
+ const dot = name.lastIndexOf(".");
13163
+ return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
13164
+ };
13165
+ var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
13166
+ var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
13167
+ var SPACE = /[ \t\r\f\v]/u;
13168
+ var WORD = /[\w$]/u;
13169
+ var NO_SPACE_AFTER = "([{,;";
13170
+ var NO_SPACE_BEFORE = ")]},;";
13171
+ var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
13172
+ var REGEX_KEYWORDS = new Set([
13173
+ "return",
13174
+ "typeof",
13175
+ "instanceof",
13176
+ "case",
13177
+ "do",
13178
+ "else",
13179
+ "in",
13180
+ "of",
13181
+ "new",
13182
+ "delete",
13183
+ "void",
13184
+ "throw",
13185
+ "yield",
13186
+ "await"
13187
+ ]);
13188
+ function substanceOf(file, text) {
13189
+ const syntax = syntaxOf(file);
13190
+ if (!syntax)
13191
+ return plainSubstance(text);
13192
+ const markup = syntax.block.some(([open]) => open === "<!--");
13193
+ if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
13194
+ return plainSubstance(text);
13195
+ return lex(syntax, text);
13196
+ }
13197
+ function plainSubstance(text) {
13198
+ return text.split(`
13199
+ `).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
13200
+ `);
13201
+ }
13202
+ function lex(syntax, text) {
13203
+ const collapse = !syntax.indented;
13204
+ let out = "";
13205
+ let lineStart = 0;
13206
+ let kept = 0;
13207
+ let pendingSpace = false;
13208
+ const frames = [{ kind: "code", braces: 0 }];
13209
+ let index = 0;
13210
+ if (text.startsWith("#!")) {
13211
+ const end = text.indexOf(`
13212
+ `);
13213
+ out = end < 0 ? text : text.slice(0, end);
13214
+ kept = out.length;
13215
+ index = end < 0 ? text.length : end;
13216
+ }
13217
+ const emitCode = (chunk) => {
13218
+ if (collapse && pendingSpace && out.length > lineStart) {
13219
+ const before = out[out.length - 1];
13220
+ if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
13221
+ out += " ";
13222
+ }
13223
+ pendingSpace = false;
13224
+ out += chunk;
13225
+ };
13226
+ const emitVerbatim = (chunk) => {
13227
+ emitCode(chunk);
13228
+ kept = out.length;
13229
+ };
13230
+ const newline = () => {
13231
+ if (!collapse) {
13232
+ let end = out.length;
13233
+ while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
13234
+ end -= 1;
13235
+ out = out.slice(0, end);
13236
+ }
13237
+ pendingSpace = false;
13238
+ if (out.length > lineStart) {
13239
+ out += `
13240
+ `;
13241
+ lineStart = out.length;
13242
+ }
13243
+ };
13244
+ const regexAllowed = () => {
13245
+ let end = out.length;
13246
+ while (end > 0 && /\s/u.test(out[end - 1]))
13247
+ end -= 1;
13248
+ if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
13249
+ return true;
13250
+ let start = end;
13251
+ while (start > 0 && WORD.test(out[start - 1]))
13252
+ start -= 1;
13253
+ return REGEX_KEYWORDS.has(out.slice(start, end));
13254
+ };
13255
+ const scanString = (quote, escapes, multiline) => {
13256
+ const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
13257
+ const fstring = syntax.fstring === true && /f/iu.test(prefix);
13258
+ let end = index + quote.length;
13259
+ while (end < text.length) {
13260
+ const char = text[end];
13261
+ if (escapes && char === "\\") {
13262
+ end += 2;
13263
+ continue;
13264
+ }
13265
+ if (text.startsWith(quote, end)) {
13266
+ if (fstring && unbalanced(text.slice(index + quote.length, end)))
13267
+ return false;
13268
+ emitVerbatim(text.slice(index, end + quote.length));
13269
+ index = end + quote.length;
13270
+ return true;
13271
+ }
13272
+ if (char === `
13273
+ ` && !multiline)
13274
+ return false;
13275
+ end += 1;
13276
+ }
13277
+ return false;
13278
+ };
13279
+ while (index < text.length) {
13280
+ const frame = frames[frames.length - 1];
13281
+ const char = text[index];
13282
+ if (frame.kind === "template") {
13283
+ if (char === "\\") {
13284
+ emitVerbatim(text.slice(index, index + 2));
13285
+ index += 2;
13286
+ } else if (char === "`") {
13287
+ emitVerbatim(char);
13288
+ frames.pop();
13289
+ index += 1;
13290
+ } else if (text.startsWith("${", index)) {
13291
+ emitVerbatim("${");
13292
+ frames.push({ kind: "code", braces: 0 });
13293
+ index += 2;
13294
+ } else {
13295
+ out += char;
13296
+ kept = out.length;
13297
+ index += 1;
13298
+ }
13299
+ continue;
13300
+ }
13301
+ if (char === `
13302
+ `) {
13303
+ newline();
13304
+ index += 1;
13305
+ continue;
13306
+ }
13307
+ if (SPACE.test(char)) {
13308
+ if (collapse)
13309
+ pendingSpace = true;
13310
+ else
13311
+ out += char;
13312
+ index += 1;
13313
+ continue;
13314
+ }
13315
+ const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
13316
+ if (lineOpener) {
13317
+ const end = text.indexOf(`
13318
+ `, index);
13319
+ const comment = text.slice(index, end < 0 ? text.length : end);
13320
+ if (DIRECTIVE.test(comment.slice(lineOpener.length)))
13321
+ emitVerbatim(comment);
13322
+ else
13323
+ pendingSpace = true;
13324
+ index = end < 0 ? text.length : end;
13325
+ continue;
13326
+ }
13327
+ const block = syntax.block.find(([open]) => text.startsWith(open, index));
13328
+ if (block) {
13329
+ const end = text.indexOf(block[1], index + block[0].length);
13330
+ if (end < 0)
13331
+ return null;
13332
+ const comment = text.slice(index, end + block[1].length);
13333
+ if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
13334
+ emitVerbatim(comment);
13335
+ else
13336
+ pendingSpace = true;
13337
+ if (comment.includes(`
13338
+ `))
13339
+ newline();
13340
+ index = end + block[1].length;
13341
+ continue;
13342
+ }
13343
+ if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
13344
+ if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
13345
+ return null;
13346
+ continue;
13347
+ }
13348
+ if (syntax.quotes.includes(char)) {
13349
+ if (!scanString(char, true, Boolean(syntax.multiline)))
13350
+ return null;
13351
+ continue;
13352
+ }
13353
+ if (syntax.literal?.includes(char)) {
13354
+ if (!scanString(char, false, false))
13355
+ return null;
13356
+ continue;
13357
+ }
13358
+ if (syntax.rawBacktick && char === "`") {
13359
+ if (!scanString("`", false, true))
13360
+ return null;
13361
+ continue;
13362
+ }
13363
+ if (syntax.js && char === "`") {
13364
+ emitVerbatim(char);
13365
+ frames.push({ kind: "template" });
13366
+ index += 1;
13367
+ continue;
13368
+ }
13369
+ if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
13370
+ return null;
13371
+ if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
13372
+ const lineEnd = text.indexOf(`
13373
+ `, index);
13374
+ if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
13375
+ return null;
13376
+ }
13377
+ if (syntax.js && char === "/" && regexAllowed()) {
13378
+ let end = index + 1;
13379
+ let inClass = false;
13380
+ while (end < text.length && (inClass || text[end] !== "/")) {
13381
+ if (text[end] === `
13382
+ `)
13383
+ return null;
13384
+ if (text[end] === "\\")
13385
+ end += 1;
13386
+ else if (text[end] === "[")
13387
+ inClass = true;
13388
+ else if (text[end] === "]")
13389
+ inClass = false;
13390
+ end += 1;
13391
+ }
13392
+ if (end >= text.length)
13393
+ return null;
13394
+ emitVerbatim(text.slice(index, end + 1));
13395
+ index = end + 1;
13396
+ continue;
13397
+ }
13398
+ if (frames.length > 1 && frame.kind === "code") {
13399
+ if (char === "{")
13400
+ frame.braces += 1;
13401
+ else if (char === "}") {
13402
+ if (frame.braces === 0) {
13403
+ emitVerbatim(char);
13404
+ frames.pop();
13405
+ index += 1;
13406
+ continue;
13407
+ }
13408
+ frame.braces -= 1;
13409
+ }
13410
+ }
13411
+ emitCode(char);
13412
+ index += 1;
13413
+ }
13414
+ if (frames.length !== 1)
13415
+ return null;
13416
+ newline();
13417
+ return out;
13418
+ }
13419
+ function unbalanced(body) {
13420
+ let depth = 0;
13421
+ for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
13422
+ if (part === "{")
13423
+ depth += 1;
13424
+ else if (part === "}")
13425
+ depth = Math.max(0, depth - 1);
13426
+ }
13427
+ return depth > 0;
13428
+ }
13429
+ function hasOpaqueLineBreak(text) {
13430
+ return /\r(?!\n)|[\u2028\u2029]/u.test(text);
13431
+ }
13432
+ var decoder = new TextDecoder("utf-8", { fatal: true });
13433
+ function textOf(bytes) {
13434
+ if (bytes.includes(0))
13435
+ return null;
13436
+ try {
13437
+ return decoder.decode(bytes);
13438
+ } catch {
13439
+ return null;
13440
+ }
13441
+ }
13442
+ function cosmeticChange(file, before, after) {
13443
+ const left = textOf(before);
13444
+ const right = textOf(after);
13445
+ if (left === null || right === null)
13446
+ return false;
13447
+ if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
13448
+ return false;
13449
+ const a = substanceOf(file, left);
13450
+ return a !== null && a === substanceOf(file, right);
13451
+ }
13452
+
13453
+ // packages/workit-core/src/core/policy/lane.ts
13454
+ var LANE_THRESHOLDS = {
13455
+ selfMaxLines: 100,
13456
+ selfMaxFiles: 5,
13457
+ fullMinLines: 500
13458
+ };
13459
+ var RISK_TOKENS = new Set([
13460
+ "auth",
13461
+ "authn",
13462
+ "authz",
13463
+ "oauth",
13464
+ "security",
13465
+ "permission",
13466
+ "permissions",
13467
+ "secret",
13468
+ "secrets",
13469
+ "crypto",
13470
+ "gate",
13471
+ "gates",
13472
+ "policy",
13473
+ "policies",
13474
+ "hook",
13475
+ "hooks",
13476
+ "husky",
13477
+ "lefthook",
13478
+ "release",
13479
+ "releases",
13480
+ "migration",
13481
+ "migrations",
13482
+ "migrate",
13483
+ "workflows"
13484
+ ]);
13485
+ var RISK_FILES = new Set([
13486
+ ".gitlab-ci.yml",
13487
+ "Jenkinsfile",
13488
+ "azure-pipelines.yml",
13489
+ ".pre-commit-config.yaml",
13490
+ ".workit/risk-paths"
13491
+ ]);
13492
+ var RISK_NAMES = new Set([".gitattributes"]);
13493
+ var RISK_PATHS_FILE = ".workit/risk-paths";
13494
+ var LOCKFILES = new Set([
13495
+ "bun.lock",
13496
+ "bun.lockb",
13497
+ "package-lock.json",
13498
+ "npm-shrinkwrap.json",
13499
+ "yarn.lock",
13500
+ "pnpm-lock.yaml",
13501
+ "Cargo.lock",
13502
+ "go.sum",
13503
+ "poetry.lock",
13504
+ "uv.lock",
13505
+ "Pipfile.lock",
13506
+ "Gemfile.lock",
13507
+ "composer.lock"
13508
+ ]);
13509
+ var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
13510
+ var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
13511
+ var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
13512
+ var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
13513
+ var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
13514
+ var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
13515
+ function kindOf(file) {
13516
+ if (file.binary)
13517
+ return "behavior";
13518
+ if (file.generated)
13519
+ return "generated";
13520
+ if (LOCKFILES.has(basename(file.path)))
13521
+ return "lockfile";
13522
+ if (isDoc(file.path))
13523
+ return "doc";
13524
+ if (file.cosmetic)
13525
+ return "cosmetic";
13526
+ return TEST_PATH.test(file.path) ? "test" : "behavior";
13527
+ }
13528
+ var globCache = new Map;
13529
+ function globMatch(pattern, file) {
13530
+ let regex = globCache.get(pattern);
13531
+ if (!regex) {
13532
+ let source = "";
13533
+ for (let index = 0;index < pattern.length; index += 1) {
13534
+ const char = pattern[index];
13535
+ if (char === "*" && pattern[index + 1] === "*") {
13536
+ source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
13537
+ index += pattern[index + 2] === "/" ? 2 : 1;
13538
+ } else if (char === "*")
13539
+ source += "[^/]*";
13540
+ else if (char === "?")
13541
+ source += "[^/]";
13542
+ else
13543
+ source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
13544
+ }
13545
+ regex = new RegExp(`^${source}$`, "u");
13546
+ globCache.set(pattern, regex);
13547
+ }
13548
+ return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
13549
+ }
13550
+ function isRiskPath(file, extra = []) {
13551
+ if (extra.some((pattern) => globMatch(pattern, file)))
13552
+ return true;
13553
+ if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
13554
+ return true;
13555
+ if (TEST_PATH.test(file) || isDoc(file))
13556
+ return false;
13557
+ return file.split("/").map((segment, index, all) => index === all.length - 1 ? segment.replace(/\.[^.]+$/u, "") : segment).some((segment) => segment.toLowerCase().split(/[-_.]/u).some((token) => RISK_TOKENS.has(token)));
13558
+ }
13559
+ var parseRiskPaths = (text) => text.split(`
13560
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
13561
+ function computeLane(input) {
13562
+ const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
13563
+ const risky = new Set(riskFiles);
13564
+ const kinds = input.files.map((file) => ({
13565
+ file,
13566
+ kind: risky.has(file.path) ? "behavior" : kindOf(file)
13567
+ }));
13568
+ const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
13569
+ const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
13570
+ const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
13571
+ const result = (lane, reasons) => ({
13572
+ lane,
13573
+ reasons,
13574
+ lines,
13575
+ files: behavior.length,
13576
+ riskFiles
13577
+ });
13578
+ const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
13579
+ if (input.riskTier === "high")
13580
+ return result("full", ["the task judgment records high risk"]);
13581
+ if (riskFiles.length)
13582
+ return result("full", [
13583
+ `touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
13584
+ ]);
13585
+ if (!behavior.length)
13586
+ return result("none", [
13587
+ input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
13588
+ ]);
13589
+ if (lines >= fullMinLines)
13590
+ return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
13591
+ if (lines > selfMaxLines)
13592
+ return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
13593
+ if (behavior.length > selfMaxFiles)
13594
+ return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
13595
+ if (opaque.length)
13596
+ return result("focused", [
13597
+ `a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
13598
+ ]);
13599
+ return result("self", [
13600
+ `small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
13601
+ ]);
13602
+ }
13603
+ function untrackedStat(cwd, file) {
13604
+ try {
13605
+ const bytes = readFileSync4(path14.join(cwd, file));
13606
+ if (bytes.includes(0))
13607
+ return { path: file, added: 0, deleted: 0, binary: true };
13608
+ const text = bytes.toString("utf8");
13609
+ if (hasOpaqueLineBreak(text))
13610
+ return { path: file, added: 0, deleted: 0, binary: true };
13611
+ const lines = text.split(`
13612
+ `).length - (text.endsWith(`
13613
+ `) ? 1 : 0);
13614
+ return { path: file, added: lines, deleted: 0 };
13615
+ } catch {
13616
+ return { path: file, added: 0, deleted: 0, binary: true };
13617
+ }
13618
+ }
13619
+ var REGULAR = new Set(["100644", "100755"]);
13620
+ var ABSENT = /^0+$/u;
13621
+ function cosmeticFiles(cwd, fork, head) {
13622
+ const raw = diffRaw(cwd, fork, head);
13623
+ if (!raw)
13624
+ return null;
13625
+ const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
13626
+ const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
13627
+ if (!blobs)
13628
+ return null;
13629
+ const out = new Set;
13630
+ const opaque = new Set;
13631
+ for (const entry of candidates) {
13632
+ const before = blobs.get(entry.oldBlob);
13633
+ let after = blobs.get(entry.newBlob);
13634
+ if (!after && head === null && ABSENT.test(entry.newBlob))
13635
+ try {
13636
+ after = readFileSync4(path14.join(cwd, entry.path));
13637
+ } catch {
13638
+ after = undefined;
13639
+ }
13640
+ if (before && after && cosmeticChange(entry.path, before, after))
13641
+ out.add(entry.path);
13642
+ else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
13643
+ opaque.add(entry.path);
13644
+ }
13645
+ return { cosmetic: out, opaque };
13646
+ }
13647
+ function generatedAt(cwd, fork, paths) {
13648
+ const files = treeFilesNamed(cwd, fork, ".gitattributes");
13649
+ const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
13650
+ if (!files || !blobs)
13651
+ return new Set;
13652
+ const texts = new Map;
13653
+ for (const [file, id] of files) {
13654
+ const bytes = blobs.get(id);
13655
+ if (bytes)
13656
+ texts.set(file, bytes.toString("utf8"));
13657
+ }
13658
+ return generatedByAttributes(texts, paths);
13659
+ }
13660
+ var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
13661
+ function laneFiles(cwd, fork, head) {
13662
+ const stats = diffNumstat(cwd, fork, head);
13663
+ const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
13664
+ if (!stats || !cosmetic)
13665
+ return null;
13666
+ const files = [
13667
+ ...stats.map((entry) => ({
13668
+ ...entry,
13669
+ ...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
13670
+ cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
13671
+ })),
13672
+ ...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
13673
+ ];
13674
+ const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
13675
+ return files.map((entry) => ({
13676
+ ...entry,
13677
+ generated: generated.has(entry.path)
13678
+ }));
13679
+ }
13680
+ function branchLane(cwd, options) {
13681
+ const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
13682
+ const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
13683
+ const head = options.workingTree ? null : options.head;
13684
+ const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
13685
+ const files = fork ? laneFiles(cwd, fork, head) : null;
13686
+ const riskTier = options.judgment?.riskTier ?? null;
13687
+ if (!fork || !files) {
13688
+ const high = riskTier === "high";
13689
+ return {
13690
+ lane: high ? "full" : "focused",
13691
+ reasons: [
13692
+ `the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
13693
+ ...high ? ["the task judgment records high risk"] : []
13694
+ ],
13695
+ lines: 0,
13696
+ files: 0,
13697
+ riskFiles: [],
13698
+ base,
13699
+ head: options.head
13700
+ };
13701
+ }
13702
+ const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
13703
+ return {
13704
+ ...computeLane({
13705
+ files,
13706
+ riskTier,
13707
+ riskPaths: [...repoRisk, ...options.riskPaths ?? []]
13708
+ }),
13709
+ base,
13710
+ head: options.head
13711
+ };
13712
+ }
13713
+ var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
13714
+
12927
13715
  // packages/workit-core/src/core/policy/derive.ts
12928
13716
  var RULES = {
12929
13717
  test: "check:test",
12930
13718
  self: "verdict:self",
12931
13719
  nonAuthor: "verdict:non-author",
12932
13720
  verified: "verdict:verified",
13721
+ lane: "verdict:lane",
12933
13722
  product: "decision:product",
12934
13723
  plan: "plan"
12935
13724
  };
@@ -12952,6 +13741,7 @@ var normalizedScope = (scope) => ({
12952
13741
  function deriveRequirements(judgment, scope, constraints, verification = "self") {
12953
13742
  const out = [];
12954
13743
  const base = { scope, dependentAction: null };
13744
+ const proportional = verification === "proportional";
12955
13745
  if (judgment.behaviorChange)
12956
13746
  out.push(derived2({
12957
13747
  ...base,
@@ -12962,7 +13752,17 @@ function deriveRequirements(judgment, scope, constraints, verification = "self")
12962
13752
  before: "close",
12963
13753
  acceptanceAllowed: false
12964
13754
  }));
12965
- if (judgment.behaviorChange && judgment.riskTier === "normal")
13755
+ if (proportional && judgment.riskTier === "normal")
13756
+ out.push(derived2({
13757
+ ...base,
13758
+ ruleId: RULES.lane,
13759
+ dimension: "review",
13760
+ reason: "The review lane computed from the diff decides the verdict: none needs none, self the author's own (shown as self-reviewed), focused and full a non-author one.",
13761
+ satisfaction: "Read the lane in `workit policy assess` or `workit pr status`; record what it asks: `workit ledger verdict tests-verified --self --how <what you ran>` for self, or hand focused and full to a verifier.",
13762
+ before: "close",
13763
+ acceptanceAllowed: false
13764
+ }));
13765
+ if (!proportional && judgment.behaviorChange && judgment.riskTier === "normal")
12966
13766
  out.push(verification === "independent" ? derived2({
12967
13767
  ...base,
12968
13768
  ruleId: RULES.nonAuthor,
@@ -13065,7 +13865,7 @@ var productDecision = (task, ledger) => {
13065
13865
  };
13066
13866
  };
13067
13867
  var planRecorded = (task, root) => {
13068
- const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path14.resolve(root, ref.path)));
13868
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path15.resolve(root, ref.path)));
13069
13869
  if (cited)
13070
13870
  return {
13071
13871
  met: true,
@@ -13079,14 +13879,30 @@ var planRecorded = (task, root) => {
13079
13879
  decisionIds: []
13080
13880
  };
13081
13881
  };
13082
- var verdict = (requirement, root, ledger) => {
13882
+ var workingLane = (task, root) => branchLane(root, { head: null, workingTree: true, judgment: latestJudgment(task) });
13883
+ var laneVerdict = (task, root, ledger) => {
13884
+ if (!ledger.branch() && latestJudgment(task)?.behaviorChange === false)
13885
+ return {
13886
+ met: true,
13887
+ reason: "no branch to read a lane from, and the judgment records no behavior change",
13888
+ decisionIds: []
13889
+ };
13890
+ const lane = workingLane(task, root);
13891
+ const label = `lane ${lane.lane} (${lane.reasons.join("; ")})`;
13892
+ const need = laneNeed(lane.lane);
13893
+ if (need === "none")
13894
+ return { met: true, reason: `${label}: no verdict needed`, decisionIds: [] };
13895
+ const status = verdict(need === "self" ? RULES.self : RULES.nonAuthor, root, ledger);
13896
+ return { ...status, reason: `${label}: ${status.reason}` };
13897
+ };
13898
+ var verdict = (ruleId, root, ledger) => {
13083
13899
  const branch = ledger.branch();
13084
13900
  if (!branch)
13085
13901
  return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
13086
13902
  const check = checkVerdicts(root, branch, ledger.rows());
13087
13903
  const failing = check.accepted.reasons.includes("failing_verdict");
13088
13904
  const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
13089
- const met = !failing && (requirement.ruleId === RULES.verified ? check.verdicts.find((entry) => entry.accepted && entry.kind === "live" && String(entry.verdict.result) === "verified") : requirement.ruleId === RULES.nonAuthor ? check.verdicts.find((entry) => entry.accepted && strong(entry)) : check.verdicts.find((entry) => entry.current && strong(entry)));
13905
+ const met = !failing && (ruleId === RULES.verified ? check.verdicts.find((entry) => entry.accepted && entry.kind === "live" && String(entry.verdict.result) === "verified") : ruleId === RULES.nonAuthor ? check.verdicts.find((entry) => entry.accepted && strong(entry)) : check.verdicts.find((entry) => entry.current && strong(entry)));
13090
13906
  if (met)
13091
13907
  return {
13092
13908
  met: true,
@@ -13094,7 +13910,7 @@ var verdict = (requirement, root, ledger) => {
13094
13910
  decisionIds: []
13095
13911
  };
13096
13912
  const why = failing ? "a current independent verdict fails" : check.verdicts.some((entry) => entry.current && entry.verdict.result === "type-check-only") ? "type-check-only does not prove a behavior change" : (check.accepted.reasons.join(", ") || "no_verdict").replace(/_/g, " ");
13097
- const unblock = requirement.ruleId === RULES.self ? "record your own verdict after an observed `workit check test`: `workit ledger verdict tests-verified --self --how <what you ran>` (shown as self-reviewed), or have a verifier record one" : `an independent session records \`workit ledger verdict verified${requirement.ruleId === RULES.verified ? " --kind live" : ""} --how <what it exercised>\``;
13913
+ const unblock = ruleId === RULES.self ? "record your own verdict after an observed `workit check test`: `workit ledger verdict tests-verified --self --how <what you ran>` (shown as self-reviewed), or have a verifier record one" : `an independent session records \`workit ledger verdict verified${ruleId === RULES.verified ? " --kind live" : ""} --how <what it exercised>\``;
13098
13914
  return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
13099
13915
  };
13100
13916
  function derivedRuleStatus(task, requirement, root, ledger) {
@@ -13106,7 +13922,9 @@ function derivedRuleStatus(task, requirement, root, ledger) {
13106
13922
  case RULES.self:
13107
13923
  case RULES.nonAuthor:
13108
13924
  case RULES.verified:
13109
- return verdict(requirement, root, ledger);
13925
+ return verdict(requirement.ruleId, root, ledger);
13926
+ case RULES.lane:
13927
+ return laneVerdict(task, root, ledger);
13110
13928
  default:
13111
13929
  return null;
13112
13930
  }
@@ -13125,14 +13943,14 @@ function writeBlockers(task, root) {
13125
13943
 
13126
13944
  // packages/workit-core/src/core/task-evaluation.ts
13127
13945
  var digestBytes = (value) => createHash11("sha256").update(value).digest("hex");
13128
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path15.sep}`);
13129
- var relative = (root, candidate) => path15.relative(root, candidate).split(path15.sep).join("/") || ".";
13946
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path16.sep}`);
13947
+ var relative = (root, candidate) => path16.relative(root, candidate).split(path16.sep).join("/") || ".";
13130
13948
  var canonicalPath = (value) => {
13131
13949
  if (typeof value !== "string" || !value)
13132
13950
  return null;
13133
13951
  if (value.includes("\\") || value.split("/").includes(".."))
13134
13952
  return null;
13135
- const normalized = path15.posix.normalize(value);
13953
+ const normalized = path16.posix.normalize(value);
13136
13954
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
13137
13955
  return null;
13138
13956
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -13180,14 +13998,14 @@ var gitPaths = (root) => {
13180
13998
  git: !/not a git repository/i.test(stderr)
13181
13999
  };
13182
14000
  }
13183
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")) : [];
14001
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")) : [];
13184
14002
  const stagedDeleted = spawnSync9("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
13185
14003
  if (stagedDeleted.status !== 0) {
13186
14004
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
13187
14005
  if (!/not a git repository/i.test(stderr))
13188
14006
  return { paths, uncertain: true, git: true };
13189
14007
  } else if (stagedDeleted.stdout) {
13190
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")));
14008
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")));
13191
14009
  }
13192
14010
  return { paths: [...new Set(paths)], uncertain: false, git: true };
13193
14011
  };
@@ -13202,7 +14020,7 @@ var walk = (root, directory, output) => {
13202
14020
  for (const name of names) {
13203
14021
  if (name === ".git" || name === ".workit")
13204
14022
  continue;
13205
- const target = path15.join(directory, name);
14023
+ const target = path16.join(directory, name);
13206
14024
  let stat;
13207
14025
  try {
13208
14026
  stat = fs12.lstatSync(target);
@@ -13220,12 +14038,12 @@ var walk = (root, directory, output) => {
13220
14038
  var scopeRoots = (root, scope) => {
13221
14039
  const roots = [];
13222
14040
  for (const item of scope.paths.length ? scope.paths : ["."]) {
13223
- const target = path15.resolve(root, item);
14041
+ const target = path16.resolve(root, item);
13224
14042
  if (!inside(root, target))
13225
14043
  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` }] });
13226
14044
  let ancestor = target;
13227
14045
  while (!fs12.existsSync(ancestor) && ancestor !== root)
13228
- ancestor = path15.dirname(ancestor);
14046
+ ancestor = path16.dirname(ancestor);
13229
14047
  try {
13230
14048
  if (!inside(root, fs12.realpathSync(ancestor)))
13231
14049
  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` }] });
@@ -13277,7 +14095,7 @@ function captureCandidate(root, scope, environment = []) {
13277
14095
  for (const item of [...names].toSorted()) {
13278
14096
  if (!scopeMatches(item, normalizedScope))
13279
14097
  continue;
13280
- const target = path15.join(checkout, item);
14098
+ const target = path16.join(checkout, item);
13281
14099
  try {
13282
14100
  const stat = fs12.lstatSync(target);
13283
14101
  if (stat.isSymbolicLink()) {
@@ -13461,23 +14279,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
13461
14279
  continue;
13462
14280
  if (!reference.digest)
13463
14281
  return failure2("invalid_input", "document references require a byte digest");
13464
- const target = path15.resolve(root, reference.path);
13465
- if (target !== root && !target.startsWith(`${root}${path15.sep}`))
14282
+ const target = path16.resolve(root, reference.path);
14283
+ if (target !== root && !target.startsWith(`${root}${path16.sep}`))
13466
14284
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
13467
14285
  fields: [
13468
14286
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
13469
14287
  ]
13470
14288
  });
13471
14289
  try {
13472
- const below = path15.relative(root, target);
14290
+ const below = path16.relative(root, target);
13473
14291
  let current = root;
13474
- for (const segment of below.split(path15.sep)) {
14292
+ for (const segment of below.split(path16.sep)) {
13475
14293
  if (!segment)
13476
14294
  continue;
13477
- current = path15.join(current, segment);
14295
+ current = path16.join(current, segment);
13478
14296
  const stat = fs12.lstatSync(current);
13479
14297
  const real = fs12.realpathSync(current);
13480
- if (real !== root && !real.startsWith(`${root}${path15.sep}`))
14298
+ if (real !== root && !real.startsWith(`${root}${path16.sep}`))
13481
14299
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
13482
14300
  fields: [
13483
14301
  {
@@ -14398,7 +15216,7 @@ var judgeInputSummary = (raw) => {
14398
15216
 
14399
15217
  // packages/workit-core/src/autonomy.ts
14400
15218
  import os4 from "node:os";
14401
- import path16 from "node:path";
15219
+ import path17 from "node:path";
14402
15220
  var accountHome = () => {
14403
15221
  try {
14404
15222
  return os4.userInfo().homedir;
@@ -14407,10 +15225,10 @@ var accountHome = () => {
14407
15225
  }
14408
15226
  };
14409
15227
  var grantsHome = { resolve: accountHome };
14410
- var grantsDir = () => path16.join(grantsHome.resolve(), ".config", "workit");
15228
+ var grantsDir = () => path17.join(grantsHome.resolve(), ".config", "workit");
14411
15229
  var grantsOverride = () => {
14412
- const configured = path16.resolve(resolveConfigDir());
14413
- return configured === path16.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path16.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
15230
+ const configured = path17.resolve(resolveConfigDir());
15231
+ return configured === path17.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 ${path17.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
14414
15232
  };
14415
15233
  var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
14416
15234
  var DEFAULT_GRANTS = Object.freeze({
@@ -14463,8 +15281,16 @@ function endpointView(entry, context) {
14463
15281
  ...issue ? { endpointIssue: issue } : {}
14464
15282
  };
14465
15283
  }
14466
- var DEFAULT_VERIFICATION = "self";
14467
- var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
15284
+ var VERIFICATION_MODES = [
15285
+ "proportional",
15286
+ "self",
15287
+ "independent"
15288
+ ];
15289
+ var DEFAULT_VERIFICATION = "proportional";
15290
+ var verificationOf = (entry) => {
15291
+ const value = entry?.verification;
15292
+ return VERIFICATION_MODES.includes(value) ? value : DEFAULT_VERIFICATION;
15293
+ };
14468
15294
  var isGrantValue = (value) => value === true || value === false || value === "verified";
14469
15295
  var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
14470
15296
  var fromAutoApprove = (value) => {
@@ -16423,7 +17249,7 @@ function entryCompactContext(store, entry, session, context) {
16423
17249
  return null;
16424
17250
  const freshness = { mode: "signal", current: () => recentSignal(store.root) };
16425
17251
  const observed = task?.data.evidence.some(isObservedCheck) ?? false;
16426
- const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path17.resolve(store.root, ref.path)] : []));
17252
+ const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path18.resolve(store.root, ref.path)] : []));
16427
17253
  const observedFiles = filesKey(files);
16428
17254
  const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
16429
17255
  if (!compact.ok || !compact.data)
@@ -16501,14 +17327,14 @@ var turnMarker = (input) => {
16501
17327
  if (location instanceof Error)
16502
17328
  return null;
16503
17329
  const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
16504
- return path17.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
17330
+ return path18.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
16505
17331
  } catch {
16506
17332
  return null;
16507
17333
  }
16508
17334
  };
16509
17335
  var writeTurnDigest = (file, digest) => {
16510
17336
  try {
16511
- fs13.mkdirSync(path17.dirname(file), { recursive: true });
17337
+ fs13.mkdirSync(path18.dirname(file), { recursive: true });
16512
17338
  fs13.writeFileSync(file, `${JSON.stringify({ digest })}
16513
17339
  `);
16514
17340
  } catch {}
@@ -16523,7 +17349,7 @@ var pruneTurnDigests = (dir, now) => {
16523
17349
  for (const name of fs13.readdirSync(dir)) {
16524
17350
  if (!name.startsWith("turn-"))
16525
17351
  continue;
16526
- const file = path17.join(dir, name);
17352
+ const file = path18.join(dir, name);
16527
17353
  try {
16528
17354
  if (now - fs13.statSync(file).mtimeMs > TURN_TTL_MS)
16529
17355
  fs13.rmSync(file, { force: true });
@@ -16553,7 +17379,7 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
16553
17379
  const file = turnMarker(input);
16554
17380
  if (!file)
16555
17381
  return;
16556
- pruneTurnDigests(path17.dirname(file), Date.now());
17382
+ pruneTurnDigests(path18.dirname(file), Date.now());
16557
17383
  if (text)
16558
17384
  writeTurnDigest(file, digestOf(text));
16559
17385
  else
@@ -16561,20 +17387,20 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
16561
17387
  };
16562
17388
  // packages/workit-core/src/core/route-intent.ts
16563
17389
  import { spawnSync as spawnSync11 } from "node:child_process";
16564
- import { existsSync as existsSync6 } from "node:fs";
16565
- import path20 from "node:path";
17390
+ import { existsSync as existsSync7 } from "node:fs";
17391
+ import path21 from "node:path";
16566
17392
 
16567
17393
  // packages/workit-core/src/hooks/raw-git.ts
16568
17394
  import { spawnSync as spawnSync10 } from "node:child_process";
16569
17395
  import { createHash as createHash14 } from "node:crypto";
16570
17396
  import fs15 from "node:fs";
16571
17397
  import os5 from "node:os";
16572
- import path19 from "node:path";
17398
+ import path20 from "node:path";
16573
17399
 
16574
17400
  // packages/workit-core/src/hooks/session-marker.ts
16575
17401
  import { createHash as createHash13 } from "node:crypto";
16576
17402
  import fs14 from "node:fs";
16577
- import path18 from "node:path";
17403
+ import path19 from "node:path";
16578
17404
  var sessionMarkerFile = (input, session, store) => {
16579
17405
  let dir = store;
16580
17406
  if (dir === undefined) {
@@ -16584,7 +17410,7 @@ var sessionMarkerFile = (input, session, store) => {
16584
17410
  dir = location.dir;
16585
17411
  }
16586
17412
  const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
16587
- return path18.join(dir, "hooks", `skills-${name}.json`);
17413
+ return path19.join(dir, "hooks", `skills-${name}.json`);
16588
17414
  };
16589
17415
  var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
16590
17416
  var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
@@ -16611,7 +17437,7 @@ var parseMarker = (text) => {
16611
17437
  };
16612
17438
  var readSessionMarker = (file) => parseMarker(readRaw(file));
16613
17439
  var writeAtomic = (file, marker) => {
16614
- fs14.mkdirSync(path18.dirname(file), { recursive: true });
17440
+ fs14.mkdirSync(path19.dirname(file), { recursive: true });
16615
17441
  const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
16616
17442
  try {
16617
17443
  fs14.writeFileSync(temp, JSON.stringify(marker));
@@ -17139,17 +17965,17 @@ var GIT_VALUE_OPTIONS = new Set([
17139
17965
  ]);
17140
17966
  var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
17141
17967
  var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
17142
- var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
17968
+ var ASSIGNMENT2 = /^([A-Za-z_]\w*)=([\s\S]*)$/;
17143
17969
  var MAX_DEPTH = 4;
17144
17970
  var MAX_EXPANSIONS = 16;
17145
- var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
17971
+ var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
17146
17972
  var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
17147
17973
  var joinDir = (base, raw) => {
17148
17974
  const next = raw.replace(HERE, ".");
17149
17975
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
17150
17976
  return;
17151
- const expanded = next === "~" || next.startsWith("~/") ? path19.join(os5.homedir(), next.slice(1)) : next;
17152
- return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
17977
+ const expanded = next === "~" || next.startsWith("~/") ? path20.join(os5.homedir(), next.slice(1)) : next;
17978
+ return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
17153
17979
  };
17154
17980
  var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
17155
17981
  var cdTarget = (words) => {
@@ -17166,7 +17992,7 @@ var stripPrefixes = (input) => {
17166
17992
  const first = words[0];
17167
17993
  if (first === undefined)
17168
17994
  return { words, env };
17169
- const assignment = ASSIGNMENT.exec(first);
17995
+ const assignment = ASSIGNMENT2.exec(first);
17170
17996
  if (assignment) {
17171
17997
  if (GIT_CONFIG_ENV.test(assignment[1]))
17172
17998
  env[assignment[1]] = assignment[2];
@@ -17225,10 +18051,10 @@ var assignments = (words, vars) => {
17225
18051
  while (rest[0]?.startsWith("-"))
17226
18052
  rest = rest.slice(1);
17227
18053
  }
17228
- if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
18054
+ if (rest.length === 0 || !rest.every((word) => ASSIGNMENT2.test(word)))
17229
18055
  return null;
17230
18056
  return rest.map((word) => {
17231
- const [, name, value] = ASSIGNMENT.exec(word);
18057
+ const [, name, value] = ASSIGNMENT2.exec(word);
17232
18058
  return [name, expandWord(value, vars)];
17233
18059
  });
17234
18060
  };
@@ -17416,7 +18242,7 @@ var splitString = (line) => {
17416
18242
  return line.slice(index).join(" ").slice(15);
17417
18243
  if (/^-S./.test(word))
17418
18244
  return [word.slice(2), ...line.slice(index + 1)].join(" ");
17419
- if (!word.startsWith("-") && !ASSIGNMENT.test(word))
18245
+ if (!word.startsWith("-") && !ASSIGNMENT2.test(word))
17420
18246
  return null;
17421
18247
  }
17422
18248
  return null;
@@ -17683,6 +18509,24 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
17683
18509
  }
17684
18510
  return found;
17685
18511
  }
18512
+ var WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
18513
+ function skipWorkitGlobals(args) {
18514
+ let cwd = null;
18515
+ let at = 0;
18516
+ for (;at < args.length; at++) {
18517
+ const match = WORKIT_GLOBAL.exec(args[at]);
18518
+ if (match?.[1] === "=") {
18519
+ if (args[at].startsWith("--cwd="))
18520
+ cwd = args[at].slice(6);
18521
+ } else if (match && args[at + 1] !== undefined) {
18522
+ if (args[at] === "--cwd")
18523
+ cwd = args[at + 1];
18524
+ at++;
18525
+ } else if (args[at] !== "--json")
18526
+ break;
18527
+ }
18528
+ return { rest: args.slice(at), cwd };
18529
+ }
17686
18530
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
17687
18531
  var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
17688
18532
  var pushDestination = (destination) => {
@@ -17982,17 +18826,17 @@ var headSha2 = (repo) => {
17982
18826
  if (!git || git.reftable)
17983
18827
  return null;
17984
18828
  try {
17985
- const head = fs15.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
18829
+ const head = fs15.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
17986
18830
  if (/^[0-9a-f]{40,64}$/.test(head))
17987
18831
  return head;
17988
18832
  const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
17989
18833
  if (!ref)
17990
18834
  return null;
17991
- const common = path19.dirname(repo.location.dir);
18835
+ const common = path20.dirname(repo.location.dir);
17992
18836
  try {
17993
- return fs15.readFileSync(path19.join(common, ref), "utf8").trim() || null;
18837
+ return fs15.readFileSync(path20.join(common, ref), "utf8").trim() || null;
17994
18838
  } catch {}
17995
- const packed = fs15.readFileSync(path19.join(common, "packed-refs"), "utf8");
18839
+ const packed = fs15.readFileSync(path20.join(common, "packed-refs"), "utf8");
17996
18840
  return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
17997
18841
  } catch {
17998
18842
  return null;
@@ -18080,7 +18924,7 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
18080
18924
  const sub = invocation.args[0];
18081
18925
  if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
18082
18926
  return [invocation];
18083
- const dir = path19.resolve(root, invocation.dir ?? ".");
18927
+ const dir = path20.resolve(root, invocation.dir ?? ".");
18084
18928
  if (!fs15.existsSync(dir))
18085
18929
  return [invocation];
18086
18930
  const config = gitConfig(dir, invocation, cache);
@@ -18103,11 +18947,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
18103
18947
  var normalizedUrl = (url, dir) => {
18104
18948
  const trimmed = url.trim();
18105
18949
  if (/^file:\/\//i.test(trimmed))
18106
- return path19.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
18950
+ return path20.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
18107
18951
  const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
18108
18952
  const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
18109
18953
  if (!scheme && !scp)
18110
- return path19.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
18954
+ return path20.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
18111
18955
  const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
18112
18956
  return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
18113
18957
  };
@@ -18127,7 +18971,7 @@ var rewrittenUrl = (url, config) => {
18127
18971
  }
18128
18972
  return url;
18129
18973
  };
18130
- var pushUrl = (config, remote, dir) => {
18974
+ var pushUrl2 = (config, remote, dir) => {
18131
18975
  const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
18132
18976
  return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
18133
18977
  };
@@ -18155,7 +18999,7 @@ var toPushRemote = (repo, remote, config) => {
18155
18999
  const pushRemote = pushRemoteOf(config, repo.branch);
18156
19000
  if (pushRemote === null || pushRemote === remote)
18157
19001
  return true;
18158
- return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
19002
+ return pushUrl2(config, remote, repo.dir) === pushUrl2(config, pushRemote, repo.dir);
18159
19003
  };
18160
19004
  var quote = (text) => `\`${text}\``;
18161
19005
  var refusal = (action, branch, dir) => {
@@ -18331,7 +19175,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
18331
19175
  const out = [];
18332
19176
  const repos = new Map;
18333
19177
  const repoFor = (relative) => {
18334
- const dir = path19.resolve(cwd, relative ?? ".");
19178
+ const dir = path20.resolve(cwd, relative ?? ".");
18335
19179
  if (!repos.has(dir)) {
18336
19180
  const repo = repoAt(dir);
18337
19181
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
@@ -18417,7 +19261,7 @@ var analysisDenial = (error) => ({
18417
19261
  });
18418
19262
  var NEXT_COMMAND = "next";
18419
19263
  var MARKER_TTL_SECONDS = 3600;
18420
- var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
19264
+ var markerFile = (repo, session, key) => path20.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
18421
19265
  var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
18422
19266
  function noteRawCommit(input, command, key) {
18423
19267
  try {
@@ -18428,7 +19272,7 @@ function noteRawCommit(input, command, key) {
18428
19272
  if (action.kind !== "commit")
18429
19273
  continue;
18430
19274
  const file = markerFile(repo, input.session.id, key);
18431
- fs15.mkdirSync(path19.dirname(file), { recursive: true });
19275
+ fs15.mkdirSync(path20.dirname(file), { recursive: true });
18432
19276
  fs15.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
18433
19277
  `);
18434
19278
  }
@@ -18659,8 +19503,8 @@ var judgedDir = (root, dir) => {
18659
19503
  return null;
18660
19504
  if (dir === null)
18661
19505
  return root;
18662
- const resolved = path20.resolve(root, dir);
18663
- return existsSync6(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
19506
+ const resolved = path21.resolve(root, dir);
19507
+ return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
18664
19508
  };
18665
19509
  var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
18666
19510
  let judged = false;
@@ -18696,7 +19540,7 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
18696
19540
  // packages/workit-core/src/core/heavy-rules.ts
18697
19541
  import fs16 from "node:fs";
18698
19542
  import os6 from "node:os";
18699
- import path21 from "node:path";
19543
+ import path22 from "node:path";
18700
19544
  var EMPTY2 = () => ({ deny: [], allow: [] });
18701
19545
  var readText2 = (file) => {
18702
19546
  try {
@@ -18737,19 +19581,19 @@ function parseJsonc(text) {
18737
19581
  return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
18738
19582
  }
18739
19583
  function gitTop(cwd) {
18740
- for (let dir = path21.resolve(cwd);; dir = path21.dirname(dir)) {
18741
- if (fs16.existsSync(path21.join(dir, ".git")))
19584
+ for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
19585
+ if (fs16.existsSync(path22.join(dir, ".git")))
18742
19586
  return dir;
18743
- if (path21.dirname(dir) === dir)
19587
+ if (path22.dirname(dir) === dir)
18744
19588
  return null;
18745
19589
  }
18746
19590
  }
18747
19591
  var projectDirs = (cwd) => {
18748
19592
  const top = gitTop(cwd);
18749
19593
  const dirs = [];
18750
- for (let dir = path21.resolve(cwd);; dir = path21.dirname(dir)) {
19594
+ for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
18751
19595
  dirs.push(dir);
18752
- if (!top || dir === top || path21.dirname(dir) === dir)
19596
+ if (!top || dir === top || path22.dirname(dir) === dir)
18753
19597
  return dirs;
18754
19598
  }
18755
19599
  };
@@ -18761,12 +19605,12 @@ var CLAUDE_MANAGED = {
18761
19605
  };
18762
19606
  function claudeSettingsFiles(cwd, env, platform = process.platform) {
18763
19607
  const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
18764
- const user = env.CLAUDE_CONFIG_DIR || path21.join(homeOf(env), ".claude");
19608
+ const user = env.CLAUDE_CONFIG_DIR || path22.join(homeOf(env), ".claude");
18765
19609
  return [
18766
19610
  ...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
18767
- path21.join(user, "settings.json"),
18768
- path21.join(project, ".claude", "settings.json"),
18769
- path21.join(project, ".claude", "settings.local.json")
19611
+ path22.join(user, "settings.json"),
19612
+ path22.join(project, ".claude", "settings.json"),
19613
+ path22.join(project, ".claude", "settings.local.json")
18770
19614
  ];
18771
19615
  }
18772
19616
  var bashPattern = (entry) => {
@@ -18856,10 +19700,10 @@ var opencodeConfig = (value, rules) => {
18856
19700
  function opencodeRules(cwd, env = process.env) {
18857
19701
  const rules = EMPTY2();
18858
19702
  try {
18859
- const config = env.XDG_CONFIG_HOME || path21.join(homeOf(env), ".config");
19703
+ const config = env.XDG_CONFIG_HOME || path22.join(homeOf(env), ".config");
18860
19704
  const files = [
18861
- ...OPENCODE_NAMES.map((name) => path21.join(config, "opencode", name)),
18862
- ...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path21.join(dir, name), path21.join(dir, ".opencode", name)])),
19705
+ ...OPENCODE_NAMES.map((name) => path22.join(config, "opencode", name)),
19706
+ ...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path22.join(dir, name), path22.join(dir, ".opencode", name)])),
18863
19707
  ...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
18864
19708
  ];
18865
19709
  for (const file of files) {
@@ -18903,9 +19747,9 @@ var codexRule = (body) => {
18903
19747
  function codexRules(cwd, env = process.env) {
18904
19748
  const rules = EMPTY2();
18905
19749
  try {
18906
- const home = env.CODEX_HOME || path21.join(homeOf(env), ".codex");
19750
+ const home = env.CODEX_HOME || path22.join(homeOf(env), ".codex");
18907
19751
  const top = gitTop(cwd) ?? cwd;
18908
- for (const dir of [path21.join(home, "rules"), path21.join(top, ".codex", "rules")]) {
19752
+ for (const dir of [path22.join(home, "rules"), path22.join(top, ".codex", "rules")]) {
18909
19753
  let files;
18910
19754
  try {
18911
19755
  files = fs16.readdirSync(dir).filter((name) => name.endsWith(".rules"));
@@ -18915,7 +19759,7 @@ function codexRules(cwd, env = process.env) {
18915
19759
  return "unreadable";
18916
19760
  }
18917
19761
  for (const name of files) {
18918
- const text = readText2(path21.join(dir, name)) ?? "";
19762
+ const text = readText2(path22.join(dir, name)) ?? "";
18919
19763
  for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
18920
19764
  const rule = codexRule(part);
18921
19765
  if (!rule)
@@ -18934,8 +19778,8 @@ function cursorRules(cwd, env = process.env) {
18934
19778
  try {
18935
19779
  const top = gitTop(cwd) ?? cwd;
18936
19780
  for (const file of [
18937
- path21.join(homeOf(env), ".cursor", "cli-config.json"),
18938
- path21.join(top, ".cursor", "cli.json")
19781
+ path22.join(homeOf(env), ".cursor", "cli-config.json"),
19782
+ path22.join(top, ".cursor", "cli.json")
18939
19783
  ]) {
18940
19784
  const text = readText2(file);
18941
19785
  if (text === null)
@@ -19011,21 +19855,21 @@ function wrapAllowed(read, words, command, options = {}) {
19011
19855
  // packages/workit-core/src/core/heavy.ts
19012
19856
  import fs17 from "node:fs";
19013
19857
  import os7 from "node:os";
19014
- import path22 from "node:path";
19858
+ import path23 from "node:path";
19015
19859
  function stateDir(env, platform = process.platform) {
19016
19860
  if (env.WORKFLOW_TOOLKIT_STATE)
19017
19861
  return env.WORKFLOW_TOOLKIT_STATE;
19018
19862
  if (env.XDG_STATE_HOME)
19019
- return path22.join(env.XDG_STATE_HOME, "workit");
19863
+ return path23.join(env.XDG_STATE_HOME, "workit");
19020
19864
  const home = env.HOME || os7.homedir();
19021
19865
  if (platform === "darwin")
19022
- return path22.join(home, "Library", "Application Support", "workit");
19866
+ return path23.join(home, "Library", "Application Support", "workit");
19023
19867
  if (platform === "win32")
19024
- return path22.join(env.LOCALAPPDATA ?? path22.join(home, "AppData", "Local"), "workit");
19025
- return path22.join(home, ".local", "state", "workit");
19868
+ return path23.join(env.LOCALAPPDATA ?? path23.join(home, "AppData", "Local"), "workit");
19869
+ return path23.join(home, ".local", "state", "workit");
19026
19870
  }
19027
19871
  var HEAVY_COMMANDS_FILE = "heavy-commands.json";
19028
- var heavyCommandsFile = (env) => path22.join(stateDir(env), HEAVY_COMMANDS_FILE);
19872
+ var heavyCommandsFile = (env) => path23.join(stateDir(env), HEAVY_COMMANDS_FILE);
19029
19873
  var HEAVY_SCRIPT = /^(?:test|tests|t|tst|unit|integration|e2e|coverage|cov|lint|eslint|typecheck|type-check|types|tsc|check|verify|validate|build|compile|bundle|ci|sonar|cypress|playwright|jest|vitest|mocha|bench|benchmark|all)(?:$|[:_.-])/iu;
19030
19874
  var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
19031
19875
  var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
@@ -19300,7 +20144,7 @@ function machinePatterns(env) {
19300
20144
  return isRecord4(value) ? stringList(value.patterns) : [];
19301
20145
  }
19302
20146
  function repoPatterns(cwd) {
19303
- return repoHeavyCommands(gitTop(cwd) ?? path22.resolve(cwd));
20147
+ return repoHeavyCommands(gitTop(cwd) ?? path23.resolve(cwd));
19304
20148
  }
19305
20149
  function heavyLines(lines, options) {
19306
20150
  const found = [];
@@ -19378,12 +20222,12 @@ function heavyPlan(command, options) {
19378
20222
  }
19379
20223
  function workitOnPath(env = process.env) {
19380
20224
  const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
19381
- for (const dir of (env.PATH ?? "").split(path22.delimiter))
20225
+ for (const dir of (env.PATH ?? "").split(path23.delimiter))
19382
20226
  for (const name of names) {
19383
20227
  if (!dir)
19384
20228
  continue;
19385
20229
  try {
19386
- fs17.accessSync(path22.join(dir, name), fs17.constants.X_OK);
20230
+ fs17.accessSync(path23.join(dir, name), fs17.constants.X_OK);
19387
20231
  return true;
19388
20232
  } catch {}
19389
20233
  }
@@ -19443,16 +20287,16 @@ var splitDenial = (plan) => ({
19443
20287
  unblock: plan.unblock
19444
20288
  });
19445
20289
  // packages/workit-core/src/hooks/write-gate.ts
19446
- import { realpathSync as realpathSync5 } from "node:fs";
20290
+ import { existsSync as existsSync8, realpathSync as realpathSync5 } from "node:fs";
19447
20291
  import os9 from "node:os";
19448
- import path24 from "node:path";
20292
+ import path25 from "node:path";
19449
20293
 
19450
20294
  // packages/workit-core/src/core/live-session.ts
19451
20295
  import { spawnSync as spawnSync12 } from "node:child_process";
19452
20296
  import { createHash as createHash15 } from "node:crypto";
19453
20297
  import fs18 from "node:fs";
19454
20298
  import os8 from "node:os";
19455
- import path23 from "node:path";
20299
+ import path24 from "node:path";
19456
20300
  var LIVE_SESSION_TTL_MS = 15 * 60000;
19457
20301
  var HEARTBEAT_EVERY_MS = 30000;
19458
20302
  var liveProbe = {
@@ -19526,15 +20370,15 @@ var liveOwner = (input, transport, env = process.env) => {
19526
20370
  };
19527
20371
  };
19528
20372
  var checkoutOf = (target) => {
19529
- let dir = path23.resolve(target);
19530
- while (!fs18.existsSync(dir) && path23.dirname(dir) !== dir)
19531
- dir = path23.dirname(dir);
20373
+ let dir = path24.resolve(target);
20374
+ while (!fs18.existsSync(dir) && path24.dirname(dir) !== dir)
20375
+ dir = path24.dirname(dir);
19532
20376
  if (fs18.existsSync(dir) && !fs18.statSync(dir).isDirectory())
19533
- dir = path23.dirname(dir);
19534
- const parts = dir.split(path23.sep);
20377
+ dir = path24.dirname(dir);
20378
+ const parts = dir.split(path24.sep);
19535
20379
  const dotGit = parts.lastIndexOf(".git");
19536
20380
  if (dotGit > 0)
19537
- dir = parts.slice(0, dotGit).join(path23.sep) || path23.sep;
20381
+ dir = parts.slice(0, dotGit).join(path24.sep) || path24.sep;
19538
20382
  const location = resolveStore(dir);
19539
20383
  if (location instanceof Error || !location.shared || !location.top)
19540
20384
  return null;
@@ -19544,9 +20388,9 @@ var checkoutOf = (target) => {
19544
20388
  } catch {}
19545
20389
  return top;
19546
20390
  };
19547
- var markerFile2 = (top) => path23.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
20391
+ var markerFile2 = (top) => path24.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
19548
20392
  var checkoutKey = (top, platform = process.platform) => {
19549
- let key = path23.resolve(top);
20393
+ let key = path24.resolve(top);
19550
20394
  try {
19551
20395
  key = fs18.realpathSync.native(key);
19552
20396
  } catch {}
@@ -19641,7 +20485,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
19641
20485
  checkout,
19642
20486
  heartbeatAt: now.toISOString()
19643
20487
  };
19644
- fs18.mkdirSync(path23.dirname(file), { recursive: true });
20488
+ fs18.mkdirSync(path24.dirname(file), { recursive: true });
19645
20489
  const temp = `${file}.${process.pid}.tmp`;
19646
20490
  fs18.writeFileSync(temp, JSON.stringify(marker));
19647
20491
  fs18.renameSync(temp, file);
@@ -19653,26 +20497,26 @@ var NONE3 = { kind: "none" };
19653
20497
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
19654
20498
  var canonical2 = (file) => {
19655
20499
  let rest = "";
19656
- for (let dir = path24.resolve(file);; dir = path24.dirname(dir)) {
20500
+ for (let dir = path25.resolve(file);; dir = path25.dirname(dir)) {
19657
20501
  try {
19658
- return path24.join(realpathSync5.native(dir), rest);
20502
+ return path25.join(realpathSync5.native(dir), rest);
19659
20503
  } catch {
19660
- if (path24.dirname(dir) === dir)
19661
- return path24.resolve(file);
19662
- rest = rest ? path24.join(path24.basename(dir), rest) : path24.basename(dir);
20504
+ if (path25.dirname(dir) === dir)
20505
+ return path25.resolve(file);
20506
+ rest = rest ? path25.join(path25.basename(dir), rest) : path25.basename(dir);
19663
20507
  }
19664
20508
  }
19665
20509
  };
19666
20510
  var exempt = (cwd, root, file, plans) => {
19667
20511
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
19668
- const relative = path24.relative(fold(canonical2(root)), fold(canonical2(path24.resolve(cwd, file))));
19669
- if (relative.startsWith("..") || path24.isAbsolute(relative))
20512
+ const relative = path25.relative(fold(canonical2(root)), fold(canonical2(path25.resolve(cwd, file))));
20513
+ if (relative.startsWith("..") || path25.isAbsolute(relative))
19670
20514
  return true;
19671
- const posix = relative.split(path24.sep).join("/");
20515
+ const posix = relative.split(path25.sep).join("/");
19672
20516
  const segments = posix.split("/");
19673
20517
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
19674
20518
  };
19675
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
20519
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
19676
20520
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
19677
20521
  var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
19678
20522
  var wrappedScript = (input) => {
@@ -19684,9 +20528,9 @@ var wrappedScript = (input) => {
19684
20528
  return null;
19685
20529
  if ("script" in tail)
19686
20530
  return tail.script;
19687
- const [shell, flag, script] = tail.argv;
19688
- if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
19689
- return script ?? null;
20531
+ const script = shellScript2(tail.argv);
20532
+ if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
20533
+ return script;
19690
20534
  return tail.argv.map(quoteWord2).join(" ");
19691
20535
  };
19692
20536
  var editsOf = (input) => {
@@ -19765,6 +20609,94 @@ var editsOf = (input) => {
19765
20609
  return [];
19766
20610
  }
19767
20611
  };
20612
+ var shellScript2 = (words) => {
20613
+ if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
20614
+ return null;
20615
+ for (let index = 1;index < words.length; index++) {
20616
+ const word = words[index];
20617
+ if (/^-[a-z]*c$/.test(word))
20618
+ return words[index + 1] ?? null;
20619
+ if (/^[-+][oO]$/.test(word))
20620
+ index++;
20621
+ else if (!/^[-+]/.test(word))
20622
+ return null;
20623
+ }
20624
+ return null;
20625
+ };
20626
+ var XARGS_VALUE = /^-[IinLPdsEa]$/;
20627
+ var runsWriter = (words) => {
20628
+ const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
20629
+ const edits = editsOf(filled);
20630
+ return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
20631
+ };
20632
+ var opaqueWrites = (input) => {
20633
+ let words = input;
20634
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
20635
+ if (words[0] === "xargs") {
20636
+ let at = 1;
20637
+ while (words[at]?.startsWith("-"))
20638
+ at += XARGS_VALUE.test(words[at]) ? 2 : 1;
20639
+ return runsWriter(words.slice(at)) ? ["."] : null;
20640
+ }
20641
+ words = words.slice(1);
20642
+ }
20643
+ const [verb, ...args] = words;
20644
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20645
+ const files = operands(args);
20646
+ const has = (pattern) => args.some((arg) => pattern.test(arg));
20647
+ const listed = (named) => named.length ? named : ["."];
20648
+ if (name === "unlink" || name === "shred")
20649
+ return listed(files);
20650
+ if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
20651
+ return listed(has(/^--reference/) ? files : files.slice(1));
20652
+ if (name === "rsync") {
20653
+ const dest = files.at(-1);
20654
+ const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
20655
+ return dest === undefined ? ["."] : remote ? [] : [dest];
20656
+ }
20657
+ if (name === "find") {
20658
+ const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
20659
+ const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
20660
+ const end = args.findIndex((arg) => /^[-(!]/.test(arg));
20661
+ return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
20662
+ }
20663
+ if (/^g?awk$/.test(name)) {
20664
+ if (!has(/^-i|^--include/))
20665
+ return null;
20666
+ const named = [];
20667
+ let program = !has(/^-f$|^--file/);
20668
+ for (let index = 0;index < args.length; index++)
20669
+ if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
20670
+ index++;
20671
+ else if (args[index].startsWith("-"))
20672
+ continue;
20673
+ else if (program)
20674
+ program = false;
20675
+ else
20676
+ named.push(args[index]);
20677
+ return listed(named);
20678
+ }
20679
+ if (name === "perl")
20680
+ return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
20681
+ if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
20682
+ return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
20683
+ return shellScript2(words) === null ? null : ["."];
20684
+ };
20685
+ var linkSources = (args, at) => {
20686
+ const names = operands([...args]);
20687
+ const into = args.indexOf("-t");
20688
+ const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
20689
+ const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
20690
+ const target = dest === undefined ? null : at(dest);
20691
+ const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
20692
+ const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync8(target) || /[\\/]$/.test(dest ?? "") ? target : path25.dirname(target);
20693
+ return sources.flatMap((source) => {
20694
+ if (symbolic && base !== null && !path25.isAbsolute(homeExpanded(source)))
20695
+ return [path25.resolve(base, source)];
20696
+ const resolved = at(source);
20697
+ return resolved ? [resolved] : [];
20698
+ });
20699
+ };
19768
20700
  var shellWrites = (command, depth = 0) => {
19769
20701
  const targets = [];
19770
20702
  let unknown = false;
@@ -19789,7 +20721,7 @@ var shellWrites = (command, depth = 0) => {
19789
20721
  return { writes: true, targets: unknown ? null : targets };
19790
20722
  };
19791
20723
  var cache2 = new Map;
19792
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path24.resolve(root, file)) ?? "-").join("\x00");
20724
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path25.resolve(root, file)) ?? "-").join("\x00");
19793
20725
  var gateFor = (cwd) => {
19794
20726
  const store = new TaskStore(cwd);
19795
20727
  const listed = store.listTaskIndex();
@@ -19868,32 +20800,36 @@ var gitMutationDirs = (cwd, command, dialect) => {
19868
20800
  return [];
19869
20801
  if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
19870
20802
  return [];
19871
- return [path24.resolve(cwd, invocation.dir ?? ".")];
20803
+ return [path25.resolve(cwd, invocation.dir ?? ".")];
19872
20804
  });
19873
20805
  };
19874
20806
  var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
19875
20807
  var PUSHD2 = new Set(["pushd", "push-location"]);
19876
20808
  var POPD2 = new Set(["popd", "pop-location"]);
19877
- var cdInto = (dir, words) => {
20809
+ var UNKNOWN_DIR = path25.resolve(path25.parse(process.cwd()).root, ".workit-unknown-dir");
20810
+ var cdInto = (dir, words, strict = false) => {
20811
+ const lost = strict ? UNKNOWN_DIR : undefined;
19878
20812
  const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
19879
20813
  const target = named === undefined ? undefined : homeExpanded(named);
19880
- if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
19881
- return;
19882
- return target === undefined ? os9.homedir() : path24.resolve(dir, target);
20814
+ if (target === "-" || target && /^~|[$`*?]/.test(target))
20815
+ return lost;
20816
+ if (dir === undefined)
20817
+ return lost;
20818
+ return target === undefined ? os9.homedir() : path25.resolve(dir, target);
19883
20819
  };
19884
20820
  var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
19885
20821
  var homeExpanded = (file) => {
19886
20822
  const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
19887
20823
  return match ? (process.env.HOME || os9.homedir()) + file.slice(match[0].length) : file;
19888
20824
  };
19889
- var segmentTargets = (cwd, command, dialect, depth = 0) => {
20825
+ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
19890
20826
  const targets = [];
19891
20827
  let dir = cwd;
19892
20828
  let stack = [];
19893
20829
  const groups = [];
19894
20830
  const at = (file) => {
19895
20831
  const home = homeExpanded(file);
19896
- return path24.isAbsolute(home) ? home : dir === undefined ? null : path24.resolve(dir, home);
20832
+ return path25.isAbsolute(home) ? home : dir === undefined ? null : path25.resolve(dir, home);
19897
20833
  };
19898
20834
  for (const segment of segmentsOf(command, dialect)) {
19899
20835
  if (segment.group === "open") {
@@ -19910,21 +20846,35 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
19910
20846
  const words = lead < 0 ? [] : segment.words.slice(lead);
19911
20847
  const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
19912
20848
  if (CD2.has(name)) {
19913
- dir = cdInto(dir, words);
20849
+ dir = cdInto(dir, words, strict);
19914
20850
  continue;
19915
20851
  }
19916
20852
  if (PUSHD2.has(name)) {
19917
20853
  stack.push(dir);
19918
- dir = cdInto(dir, words);
20854
+ dir = cdInto(dir, words, strict);
19919
20855
  continue;
19920
20856
  }
19921
20857
  if (POPD2.has(name)) {
19922
- dir = stack.length ? stack.pop() : undefined;
20858
+ dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
19923
20859
  continue;
19924
20860
  }
19925
- const inner = depth < 4 ? wrappedScript(words) : null;
20861
+ const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
19926
20862
  if (inner !== null && dir !== undefined)
19927
- targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
20863
+ targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
20864
+ if (strict && dir !== undefined) {
20865
+ const runIn = dir;
20866
+ for (const sub of depth < 4 ? segment.subs ?? [] : [])
20867
+ targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
20868
+ const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
20869
+ for (const file of opaque ?? []) {
20870
+ const resolved = at(file);
20871
+ if (resolved)
20872
+ targets.push(file === "." ? path25.join(resolved, DIR_TARGET) : resolved);
20873
+ }
20874
+ const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
20875
+ if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
20876
+ targets.push(...linkSources(words.slice(ln + 1), at));
20877
+ }
19928
20878
  for (const file of segment.redirects)
19929
20879
  if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
19930
20880
  const resolved = at(file);
@@ -19934,18 +20884,36 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
19934
20884
  const edits = editsOf(segment.words);
19935
20885
  if (edits === null) {
19936
20886
  if (dir !== undefined)
19937
- targets.push(path24.join(dir, DIR_TARGET));
20887
+ targets.push(path25.join(dir, DIR_TARGET));
19938
20888
  } else
19939
20889
  for (const file of edits) {
19940
20890
  const resolved = at(file);
19941
20891
  if (resolved)
19942
20892
  targets.push(resolved);
19943
20893
  }
19944
- if (dir !== undefined && workitInvocations(segment.words.map(quoteWord2).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
19945
- targets.push(path24.join(dir, DIR_TARGET));
20894
+ if (dir !== undefined)
20895
+ for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
20896
+ const { at: runsIn, args } = workitCwd(dir, invocation);
20897
+ if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
20898
+ targets.push(path25.join(runsIn, DIR_TARGET));
20899
+ }
19946
20900
  }
19947
20901
  return targets;
19948
20902
  };
20903
+ var workitCwd = (dir, args) => {
20904
+ const { rest, cwd } = skipWorkitGlobals(args);
20905
+ return { at: cwd === null ? dir : path25.resolve(dir, homeExpanded(cwd)), args: rest };
20906
+ };
20907
+ var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
20908
+ try {
20909
+ return [
20910
+ ...segmentTargets(cwd, command, dialect, 0, strict),
20911
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path25.join(dir, DIR_TARGET))
20912
+ ];
20913
+ } catch {
20914
+ return [];
20915
+ }
20916
+ };
19949
20917
  var overridden = (command, dialect) => {
19950
20918
  const segments = segmentsOf(command, dialect);
19951
20919
  if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
@@ -19977,7 +20945,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
19977
20945
  var guarded = (targets) => {
19978
20946
  const seen = new Set;
19979
20947
  return targets.filter((target) => {
19980
- const dir = path24.dirname(target);
20948
+ const dir = path25.dirname(target);
19981
20949
  if (seen.has(dir))
19982
20950
  return false;
19983
20951
  seen.add(dir);
@@ -19998,7 +20966,7 @@ var claimAll = (owner, targets, deps) => {
19998
20966
  for (const target of guarded(targets))
19999
20967
  claimLiveSession(target, owner, deps);
20000
20968
  };
20001
- var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path24.resolve(cwd, file)) : [path24.join(cwd, DIR_TARGET)];
20969
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path25.resolve(cwd, file)) : [path25.join(cwd, DIR_TARGET)];
20002
20970
  var issuedTarget = (cwd, targets) => {
20003
20971
  const state = resolveStateDir();
20004
20972
  const found = targets.find((target) => underIssued(state, cwd, homeExpanded(target), process.platform, canonical2));
@@ -20006,7 +20974,7 @@ var issuedTarget = (cwd, targets) => {
20006
20974
  };
20007
20975
  var underIssued = (state, cwd, target, platform = process.platform, real = (file) => file) => {
20008
20976
  const win = platform === "win32";
20009
- const p = win ? path24.win32 : path24.posix;
20977
+ const p = win ? path25.win32 : path25.posix;
20010
20978
  const fold = (value) => win || platform === "darwin" ? value.toLowerCase() : value;
20011
20979
  const named = win ? target.replace(/^\/([a-zA-Z])(?=\/|$)/, "$1:") : target;
20012
20980
  const relative = p.relative(fold(real(p.resolve(state))), fold(real(p.resolve(cwd, named))));
@@ -20051,16 +21019,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20051
21019
  } catch {}
20052
21020
  const writes = shellWrites(command);
20053
21021
  let targets = [];
20054
- if (owner) {
20055
- try {
20056
- targets = [
20057
- ...segmentTargets(cwd, command, dialect),
20058
- ...gitMutationDirs(cwd, command, dialect).map((dir) => path24.join(dir, DIR_TARGET))
20059
- ];
20060
- } catch {
20061
- targets = [];
20062
- }
20063
- }
21022
+ if (owner)
21023
+ targets = shellMutationTargets(cwd, command, dialect);
20064
21024
  if (owner && targets.length) {
20065
21025
  const live = liveGate(owner, targets, deps);
20066
21026
  if (live.kind !== "none") {
@@ -20105,6 +21065,24 @@ function beforeWriteGate(cwd, paths) {
20105
21065
  }
20106
21066
  // packages/workit-core/src/hooks/stop.ts
20107
21067
  import { spawnSync as spawnSync13 } from "node:child_process";
21068
+
21069
+ // packages/workit-core/src/core/policy/current-judgment.ts
21070
+ function currentJudgment(cwd) {
21071
+ try {
21072
+ const store = new TaskStore(cwd);
21073
+ const listed = store.listTaskIndex();
21074
+ const key = store.currentKey();
21075
+ if (!listed.ok || !key.ok)
21076
+ return null;
21077
+ const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
21078
+ const task = entry ? store.readTask(entry.id) : null;
21079
+ return task?.ok === true ? latestJudgment(task.data) : null;
21080
+ } catch {
21081
+ return null;
21082
+ }
21083
+ }
21084
+
21085
+ // packages/workit-core/src/hooks/stop.ts
20108
21086
  var NONE4 = { kind: "none" };
20109
21087
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
20110
21088
  var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
@@ -20197,29 +21175,32 @@ var redChecks = (rows, pr, head, endpoint) => {
20197
21175
  reason: `PR #${pr}'s checks were last seen ${state} at ${short(head)}, and this workspace's endpoint is \`${endpoint}\`: run \`workit ci wait --pr ${pr}\`${state === "failing" ? " after fixing the failures `workit pr status` lists (then push)" : ""}.`
20198
21176
  };
20199
21177
  };
20200
- var taskJudgment = (cwd) => {
20201
- try {
20202
- const store = new TaskStore(cwd);
20203
- const listed = store.listTaskIndex();
20204
- const key = store.currentKey();
20205
- if (!listed.ok || !key.ok)
20206
- return null;
20207
- const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
20208
- const task = entry ? store.readTask(entry.id) : null;
20209
- return task?.ok === true ? latestJudgment(task.data) : null;
20210
- } catch {
20211
- return null;
20212
- }
21178
+ var prBase = (rows) => {
21179
+ const seen = rows.findLast((row) => (row.type === "pr.status" || row.type === "pr.created") && row.observer === "workit_cli" && typeof row.base === "string");
21180
+ return typeof seen?.base === "string" ? seen.base : null;
20213
21181
  };
20214
- var unverified = (cwd, branch, all, descriptor, verification) => {
21182
+ var unverified = (cwd, branch, all, descriptor, verification, head) => {
20215
21183
  const check = checkVerdicts(cwd, branch, all);
20216
21184
  if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
20217
21185
  return null;
20218
- const judgment = taskJudgment(cwd);
21186
+ const judgment = currentJudgment(cwd);
20219
21187
  const high = judgment?.riskTier === "high";
20220
- if (judgment && !judgment.behaviorChange && !high)
20221
- return null;
20222
- const independent = verification === "independent" || high;
21188
+ let independent;
21189
+ if (verification === "proportional") {
21190
+ const lane = branchLane(cwd, {
21191
+ base: prBase(all.filter((row) => row.branch === branch)),
21192
+ head,
21193
+ judgment
21194
+ });
21195
+ const need = laneNeed(lane.lane);
21196
+ if (need === "none")
21197
+ return null;
21198
+ independent = need === "non-author";
21199
+ } else {
21200
+ if (judgment && !judgment.behaviorChange && !high)
21201
+ return null;
21202
+ independent = verification === "independent" || high;
21203
+ }
20223
21204
  if (!independent && check.review === "self-reviewed")
20224
21205
  return null;
20225
21206
  const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
@@ -20266,7 +21247,7 @@ function stopObligation(input, descriptor) {
20266
21247
  }
20267
21248
  if (!delivered || endpoint !== "green" && endpoint !== "merged")
20268
21249
  return null;
20269
- const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification);
21250
+ const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification, head);
20270
21251
  return left ? { ...left, head } : null;
20271
21252
  } catch {
20272
21253
  return null;
@@ -20527,13 +21508,13 @@ ${line}` };
20527
21508
  return decision;
20528
21509
  };
20529
21510
  // packages/workit-core/src/hooks/hosts/fields.ts
20530
- import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20531
- import path25 from "node:path";
21511
+ import { existsSync as existsSync9, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
21512
+ import path26 from "node:path";
20532
21513
  var isRecord5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
20533
21514
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
20534
21515
  var optionalText = (value) => typeof value === "string" ? value : null;
20535
21516
  var existingDirectory = (value) => {
20536
- if (!nonEmpty2(value) || !path25.isAbsolute(value) || !existsSync7(value))
21517
+ if (!nonEmpty2(value) || !path26.isAbsolute(value) || !existsSync9(value))
20537
21518
  return null;
20538
21519
  try {
20539
21520
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -20601,9 +21582,9 @@ var writeTargets = (input) => {
20601
21582
  };
20602
21583
  // packages/workit-core/src/hooks/standing.ts
20603
21584
  import fs19 from "node:fs";
20604
- import path26 from "node:path";
21585
+ import path27 from "node:path";
20605
21586
  var readPlans = (store) => {
20606
- const dir = path26.join(store, "fanouts");
21587
+ const dir = path27.join(store, "fanouts");
20607
21588
  let names;
20608
21589
  try {
20609
21590
  names = fs19.readdirSync(dir).filter((name) => name.endsWith(".json"));
@@ -20613,7 +21594,7 @@ var readPlans = (store) => {
20613
21594
  const plans = [];
20614
21595
  for (const name of names) {
20615
21596
  try {
20616
- const raw = JSON.parse(fs19.readFileSync(path26.join(dir, name), "utf8"));
21597
+ const raw = JSON.parse(fs19.readFileSync(path27.join(dir, name), "utf8"));
20617
21598
  if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
20618
21599
  continue;
20619
21600
  const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
@@ -20736,9 +21717,266 @@ var cwdRewrite = (command) => {
20736
21717
  return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
20737
21718
  };
20738
21719
 
21720
+ // packages/workit-core/src/hooks/worktree-confine.ts
21721
+ import { createHash as createHash16 } from "node:crypto";
21722
+ import fs20 from "node:fs";
21723
+ import path28 from "node:path";
21724
+ var FANOUT_TOKEN_FILE = "workit-fanout-token";
21725
+ var NONE5 = { kind: "none" };
21726
+ var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
21727
+ var canonical3 = (file) => {
21728
+ let rest = "";
21729
+ for (let dir = path28.resolve(file);; dir = path28.dirname(dir)) {
21730
+ try {
21731
+ return path28.join(fs20.realpathSync.native(dir), rest);
21732
+ } catch {
21733
+ if (path28.dirname(dir) === dir)
21734
+ return path28.resolve(file);
21735
+ rest = rest ? path28.join(path28.basename(dir), rest) : path28.basename(dir);
21736
+ }
21737
+ }
21738
+ };
21739
+ var within = (child, parent) => {
21740
+ const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
21741
+ const relative = path28.relative(fold(parent), fold(child));
21742
+ return relative === "" || !relative.startsWith("..") && !path28.isAbsolute(relative);
21743
+ };
21744
+ var checkoutOf2 = (file) => {
21745
+ for (let dir = canonical3(file);; dir = path28.dirname(dir)) {
21746
+ const dotGit = path28.join(dir, ".git");
21747
+ if (fs20.existsSync(dotGit)) {
21748
+ if (!fs20.statSync(dotGit).isFile())
21749
+ return { root: dir, workit: false };
21750
+ const admin = /^gitdir:\s*(.+)$/mu.exec(fs20.readFileSync(dotGit, "utf8"))?.[1]?.trim();
21751
+ return {
21752
+ root: dir,
21753
+ workit: Boolean(admin) && fs20.existsSync(path28.join(path28.resolve(dir, admin), FANOUT_TOKEN_FILE))
21754
+ };
21755
+ }
21756
+ if (path28.dirname(dir) === dir)
21757
+ return null;
21758
+ }
21759
+ };
21760
+ var workitWorktreeOf = (file) => {
21761
+ try {
21762
+ const checkout = checkoutOf2(file);
21763
+ return checkout?.workit ? checkout.root : null;
21764
+ } catch {
21765
+ return null;
21766
+ }
21767
+ };
21768
+ var bindingDir = () => path28.join(resolveStateDir(), "worktree-bind");
21769
+ var bindingFile = (session, agentId) => path28.join(bindingDir(), `${createHash16("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
21770
+ var readBinding = (file) => {
21771
+ try {
21772
+ const value = JSON.parse(fs20.readFileSync(file, "utf8"));
21773
+ return typeof value.worktree === "string" ? value : null;
21774
+ } catch {
21775
+ return null;
21776
+ }
21777
+ };
21778
+ var boundWorktree = (input) => {
21779
+ const agentId = input.session.agentId;
21780
+ if (!agentId)
21781
+ return null;
21782
+ const binding = readBinding(bindingFile(input.session.id, agentId));
21783
+ return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
21784
+ };
21785
+ var bind = (input, worktree) => {
21786
+ const agentId = input.session.agentId;
21787
+ const file = bindingFile(input.session.id, agentId);
21788
+ const binding = {
21789
+ worktree,
21790
+ session: input.session.id,
21791
+ agentId,
21792
+ at: new Date().toISOString()
21793
+ };
21794
+ fs20.mkdirSync(path28.dirname(file), { recursive: true });
21795
+ fs20.writeFileSync(file, JSON.stringify(binding));
21796
+ try {
21797
+ if (inWorkitWorkspace(input.cwd))
21798
+ appendHookObserved(input.cwd, {
21799
+ type: "worktree.bound",
21800
+ actor: { host: input.host, session: input.session.id, agentId },
21801
+ agentType: input.session.agentType,
21802
+ worktree
21803
+ });
21804
+ } catch {}
21805
+ };
21806
+ var hostIsolated = (cwd, agentId) => {
21807
+ try {
21808
+ if (agentId === null)
21809
+ return false;
21810
+ const checkout = checkoutOf2(cwd);
21811
+ if (!checkout)
21812
+ return false;
21813
+ const dotGit = path28.join(checkout.root, ".git");
21814
+ if (!fs20.statSync(dotGit).isFile())
21815
+ return false;
21816
+ const admin = /^gitdir:\s*(.+)$/mu.exec(fs20.readFileSync(dotGit, "utf8"))?.[1]?.trim();
21817
+ if (!admin)
21818
+ return false;
21819
+ const adminDir = path28.resolve(checkout.root, admin);
21820
+ const back = fs20.readFileSync(path28.join(adminDir, "gitdir"), "utf8").trim();
21821
+ if (path28.relative(canonical3(path28.resolve(adminDir, back)), canonical3(dotGit)) !== "")
21822
+ return false;
21823
+ const common = path28.join(adminDir, "commondir");
21824
+ const gitDir = fs20.existsSync(common) ? path28.resolve(adminDir, fs20.readFileSync(common, "utf8").trim()) : adminDir;
21825
+ const expected = path28.join(path28.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
21826
+ return path28.relative(canonical3(expected), checkout.root) === "";
21827
+ } catch {
21828
+ return false;
21829
+ }
21830
+ };
21831
+ var shown = (target) => path28.basename(target) === ".workit-live-target" ? path28.dirname(target) : target;
21832
+ var unboundDeny = (input, target) => {
21833
+ const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree> git commit -m … -- <paths>`, `workit --cwd <worktree> check <name>`). If your brief names no worktree, stop and report that to the lead.";
21834
+ return {
21835
+ kind: "deny",
21836
+ reason: `workit worktree confinement: implementer ${input.session.agentId} is not working in a Workit worktree yet, and ${shown(target)} is outside every worktree \`workit fanout worktree create\` made, so it would change the lead's checkout. ${unblock}`,
21837
+ unblock
21838
+ };
21839
+ };
21840
+ var outsideDeny = (input, worktree, target) => {
21841
+ const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree} git commit -m … -- <paths>\`, \`workit --cwd ${worktree} check <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
21842
+ return {
21843
+ kind: "deny",
21844
+ reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
21845
+ unblock
21846
+ };
21847
+ };
21848
+ var unknownDirDeny = (input, worktree) => {
21849
+ const wt = worktree ?? "<worktree>";
21850
+ const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
21851
+ return {
21852
+ kind: "deny",
21853
+ reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
21854
+ unblock
21855
+ };
21856
+ };
21857
+ function confineImplementer(input, targets) {
21858
+ try {
21859
+ if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
21860
+ return NONE5;
21861
+ if (targets.some((target) => within(path28.resolve(target), UNKNOWN_DIR)))
21862
+ return unknownDirDeny(input, boundWorktree(input));
21863
+ const bindings = canonical3(bindingDir());
21864
+ const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
21865
+ if (judged.length === 0)
21866
+ return NONE5;
21867
+ let worktree = boundWorktree(input);
21868
+ if (!worktree) {
21869
+ for (const { real } of judged) {
21870
+ worktree = within(real, bindings) ? null : workitWorktreeOf(real);
21871
+ if (worktree)
21872
+ break;
21873
+ }
21874
+ if (!worktree)
21875
+ return unboundDeny(input, judged[0].named);
21876
+ bind(input, worktree);
21877
+ }
21878
+ const root = canonical3(worktree);
21879
+ const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
21880
+ return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
21881
+ } catch {
21882
+ return NONE5;
21883
+ }
21884
+ }
21885
+ var MUTATING_REMOTE = new Set([
21886
+ "add",
21887
+ "remove",
21888
+ "rm",
21889
+ "rename",
21890
+ "set-url",
21891
+ "set-head",
21892
+ "set-branches",
21893
+ "prune"
21894
+ ]);
21895
+ var CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
21896
+ var CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
21897
+ var CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
21898
+ var leadOwnedGit = (args) => {
21899
+ const [sub = "", ...rest] = args;
21900
+ if (rest.some((arg) => arg === "--help" || arg === "-h"))
21901
+ return false;
21902
+ const names = rest.filter((arg) => !arg.startsWith("-"));
21903
+ const flag = (pattern) => rest.some((arg) => pattern.test(arg));
21904
+ switch (sub) {
21905
+ case "worktree":
21906
+ return names[0] !== undefined && names[0] !== "list";
21907
+ case "update-ref":
21908
+ return true;
21909
+ case "symbolic-ref":
21910
+ return names.length > 1 || flag(/^(?:-d|--delete)$/);
21911
+ case "branch":
21912
+ return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
21913
+ case "tag":
21914
+ return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
21915
+ case "push": {
21916
+ if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
21917
+ return true;
21918
+ const refspecs = names.slice(1);
21919
+ return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
21920
+ }
21921
+ case "replace":
21922
+ case "filter-branch":
21923
+ case "gc":
21924
+ case "prune":
21925
+ case "repack":
21926
+ case "sparse-checkout":
21927
+ return true;
21928
+ case "notes":
21929
+ case "reflog":
21930
+ case "submodule":
21931
+ return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
21932
+ case "stash":
21933
+ return names[0] === "drop" || names[0] === "clear";
21934
+ case "remote":
21935
+ return MUTATING_REMOTE.has(names[0] ?? "");
21936
+ case "config": {
21937
+ if (rest.some((arg) => CONFIG_READ.test(arg)))
21938
+ return false;
21939
+ if (rest.some((arg) => CONFIG_WRITE.test(arg)))
21940
+ return true;
21941
+ let operands = 0;
21942
+ for (let index = 0;index < rest.length; index++)
21943
+ if (CONFIG_VALUE.test(rest[index]))
21944
+ index++;
21945
+ else if (!rest[index].startsWith("-"))
21946
+ operands++;
21947
+ return operands > 1;
21948
+ }
21949
+ default:
21950
+ return false;
21951
+ }
21952
+ };
21953
+ var leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
21954
+ if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
21955
+ return false;
21956
+ const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
21957
+ return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
21958
+ });
21959
+ var leadOwnedDeny = (input, command) => {
21960
+ const unblock = "Worktrees, branches other than your own, refs, tags, remotes, git config, the shared stash and the fan-out plan belong to the lead: report what you need changed as a follow-up instead.";
21961
+ return {
21962
+ kind: "deny",
21963
+ reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
21964
+ unblock
21965
+ };
21966
+ };
21967
+ function confineImplementerShell(input, command, dialect = "posix") {
21968
+ try {
21969
+ if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
21970
+ return NONE5;
21971
+ if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
21972
+ return leadOwnedDeny(input, command);
21973
+ } catch {}
21974
+ return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
21975
+ }
21976
+
20739
21977
  // packages/workit-core/src/hooks/handle.ts
20740
21978
  var usable2 = (value) => value === "native" || value === "partial";
20741
- var NONE5 = { kind: "none" };
21979
+ var NONE6 = { kind: "none" };
20742
21980
  var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
20743
21981
  var offered = new Set;
20744
21982
  var ROLES = {
@@ -20751,6 +21989,7 @@ var workitRole = (descriptor, agentType) => {
20751
21989
  const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
20752
21990
  return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
20753
21991
  };
21992
+ var implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer";
20754
21993
  var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
20755
21994
  var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
20756
21995
  var isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`;
@@ -20758,7 +21997,7 @@ var subagentStartText = (input, descriptor, event) => {
20758
21997
  const session = input.session.id ?? null;
20759
21998
  const role = workitRole(descriptor, event.agentType);
20760
21999
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
20761
- const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
22000
+ const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
20762
22001
  const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
20763
22002
  if (role === "implementer" && isolated)
20764
22003
  return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${isolatedGit(input.cwd)} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
@@ -20766,7 +22005,7 @@ var subagentStartText = (input, descriptor, event) => {
20766
22005
  return `${isolatedGit(input.cwd)}
20767
22006
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
20768
22007
  if (role === "implementer")
20769
- return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
22008
+ return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree> git commit -m "<message>" -- <paths>\`, \`workit --cwd <worktree> check <name>\`. The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit --cwd <worktree> git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
20770
22009
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
20771
22010
  if (role !== "judge" || !event.agentId)
20772
22011
  return readOnly;
@@ -20797,7 +22036,7 @@ function decide(input, deps) {
20797
22036
  switch (event.kind) {
20798
22037
  case "session.start": {
20799
22038
  if (!usable2(descriptor.context.sessionStart))
20800
- return NONE5;
22039
+ return NONE6;
20801
22040
  const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
20802
22041
  const offer = event.source === "startup" && (key === null || !offered.has(key));
20803
22042
  if (offer && key !== null)
@@ -20812,26 +22051,31 @@ function decide(input, deps) {
20812
22051
  }
20813
22052
  case "context.turn": {
20814
22053
  if (!usable2(descriptor.context.perTurn))
20815
- return NONE5;
22054
+ return NONE6;
20816
22055
  const current = turnContextText(input, descriptor);
20817
22056
  const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
20818
- return withContextLine(text ? { kind: "context", text } : NONE5, promptNudge(input, event.prompt));
22057
+ return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
20819
22058
  }
20820
22059
  case "shell.pre": {
20821
22060
  const skillRead = skillReadIn(event.command, event.dialect);
20822
22061
  if (skillRead)
20823
22062
  recordSkillLoad(input, skillRead, "read");
20824
22063
  const canDeny = usable2(descriptor.shellPolicy.deny);
20825
- const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
22064
+ const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
20826
22065
  if (policy.kind !== "none")
20827
22066
  return policy;
20828
22067
  const postTool = usable2(descriptor.events["shell.post"].support);
20829
22068
  if (!postTool)
20830
22069
  settlePendingCommit(input);
20831
- const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
22070
+ const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
20832
22071
  if (raw.kind === "deny")
20833
22072
  return raw;
20834
- const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
22073
+ if (canDeny && implementerAgent(input, descriptor)) {
22074
+ const confined = confineImplementerShell(input, event.command, event.dialect);
22075
+ if (confined.kind === "deny")
22076
+ return confined;
22077
+ }
22078
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
20835
22079
  if (gate.kind === "deny")
20836
22080
  return gate;
20837
22081
  const plan = heavyGate(input.cwd, event.command, {
@@ -20854,10 +22098,10 @@ function decide(input, deps) {
20854
22098
  command: rewrittenTo,
20855
22099
  input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
20856
22100
  text
20857
- } : NONE5;
22101
+ } : NONE6;
20858
22102
  if (descriptor.transport !== "hook-process")
20859
22103
  return rewritten(null);
20860
- const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
22104
+ const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
20861
22105
  if (!lease)
20862
22106
  return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
20863
22107
  return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
@@ -20866,9 +22110,17 @@ function decide(input, deps) {
20866
22110
  case "shell.post":
20867
22111
  if (usable2(descriptor.events["shell.post"].support))
20868
22112
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
20869
- return NONE5;
20870
- case "write.pre":
20871
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
22113
+ return NONE6;
22114
+ case "write.pre": {
22115
+ if (!usable2(descriptor.events["write.pre"].support))
22116
+ return NONE6;
22117
+ if (implementerAgent(input, descriptor)) {
22118
+ const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
22119
+ if (confined.kind === "deny")
22120
+ return confined;
22121
+ }
22122
+ return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
22123
+ }
20872
22124
  case "subagent.start":
20873
22125
  if (workitRole(descriptor, event.agentType) === "judge")
20874
22126
  trackJudge(input, event.agentId, true);
@@ -20885,22 +22137,22 @@ function decide(input, deps) {
20885
22137
  case "tool.pre":
20886
22138
  if (event.skill)
20887
22139
  recordSkillLoad(input, event.skill, "tool");
20888
- return NONE5;
22140
+ return NONE6;
20889
22141
  case "stop":
20890
- return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE5;
22142
+ return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
20891
22143
  case "subagent.stop":
20892
22144
  trackJudge(input, event.agentId, false);
20893
- return NONE5;
22145
+ return NONE6;
20894
22146
  case "prompt.submit":
20895
- return NONE5;
22147
+ return NONE6;
20896
22148
  }
20897
22149
  }
20898
22150
  var failureDecision = (descriptor, event, error) => {
20899
22151
  if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
20900
- return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE5;
22152
+ return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE6;
20901
22153
  if (event === "session.start" || event === "subagent.start")
20902
22154
  return { kind: "context", text: `[workit diagnostic: ${error}]` };
20903
- return NONE5;
22155
+ return NONE6;
20904
22156
  };
20905
22157
  var dispatchHook = (adapter, raw, env = process.env) => {
20906
22158
  const parsed = adapter.parse(raw, env);
@@ -20926,7 +22178,7 @@ var dispatchHook = (adapter, raw, env = process.env) => {
20926
22178
  };
20927
22179
  // packages/workit-core/src/hooks/deadline.ts
20928
22180
  import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
20929
- import path27 from "node:path";
22181
+ import path29 from "node:path";
20930
22182
  var HOOK_SKIP_TEXT = "workit context skipped: host under load";
20931
22183
  var HOOK_DEADLINE_MS = 8000;
20932
22184
  var HOST_DEADLINES = {
@@ -20944,7 +22196,7 @@ var hookDeadlineMs = (env, host = "", native) => {
20944
22196
  const row = HOST_DEADLINES[host];
20945
22197
  return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
20946
22198
  };
20947
- var skipsFile = (stateDir) => path27.join(stateDir, HOOK_SKIPS_FILE);
22199
+ var skipsFile = (stateDir) => path29.join(stateDir, HOOK_SKIPS_FILE);
20948
22200
  var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
20949
22201
  try {
20950
22202
  mkdirSync3(stateDir, { recursive: true });
@@ -20962,7 +22214,7 @@ var QUIET = new Set([
20962
22214
  ]);
20963
22215
  var skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT };
20964
22216
  var TIMED_OUT = Symbol("timed out");
20965
- var within = async (work, ms) => {
22217
+ var within2 = async (work, ms) => {
20966
22218
  let timer;
20967
22219
  const expired = new Promise((resolve) => {
20968
22220
  timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
@@ -20993,7 +22245,7 @@ var readWithin = async (stdin, budget) => {
20993
22245
  text += String(chunk);
20994
22246
  return text;
20995
22247
  })();
20996
- const text = await within(read, budget.startedAt + budget.deadlineMs - budget.now());
22248
+ const text = await within2(read, budget.startedAt + budget.deadlineMs - budget.now());
20997
22249
  return isTimedOut(text) ? null : text;
20998
22250
  };
20999
22251
  var note = (adapter, raw, env, budget, outcome, stage) => {
@@ -21022,7 +22274,7 @@ var dispatchWithin = async (adapter, raw, env, budget, dispatch = dispatchHook)
21022
22274
  if (budget.host !== null && typeof native === "string")
21023
22275
  budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
21024
22276
  const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
21025
- const result = remaining() > 0 ? await within(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
22277
+ const result = remaining() > 0 ? await within2(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
21026
22278
  if (isTimedOut(result))
21027
22279
  return skippedHook(adapter, raw, env, budget, "dispatch");
21028
22280
  if (remaining() < 0)
@@ -21588,8 +22840,8 @@ var codexAdapter = {
21588
22840
  };
21589
22841
 
21590
22842
  // packages/workit-core/src/hooks/hosts/cursor.ts
21591
- import fs20 from "node:fs";
21592
- import path28 from "node:path";
22843
+ import fs21 from "node:fs";
22844
+ import path30 from "node:path";
21593
22845
  var undocumented = { support: "undocumented", native: null };
21594
22846
  var none = { support: "none", native: null };
21595
22847
  var CURSOR_DESCRIPTOR = {
@@ -21786,17 +23038,17 @@ var protocolEvent2 = (input) => {
21786
23038
  };
21787
23039
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
21788
23040
  var lastAssistantText = (file) => {
21789
- if (!file || !path28.isAbsolute(file))
23041
+ if (!file || !path30.isAbsolute(file))
21790
23042
  return null;
21791
23043
  try {
21792
- const size = fs20.statSync(file).size;
23044
+ const size = fs21.statSync(file).size;
21793
23045
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21794
23046
  const buffer = Buffer.alloc(size - start);
21795
- const fd = fs20.openSync(file, "r");
23047
+ const fd = fs21.openSync(file, "r");
21796
23048
  try {
21797
- fs20.readSync(fd, buffer, 0, buffer.length, start);
23049
+ fs21.readSync(fd, buffer, 0, buffer.length, start);
21798
23050
  } finally {
21799
- fs20.closeSync(fd);
23051
+ fs21.closeSync(fd);
21800
23052
  }
21801
23053
  const lines = buffer.toString("utf8").split(`
21802
23054
  `);
@@ -21823,7 +23075,7 @@ var lastAssistantText = (file) => {
21823
23075
  return null;
21824
23076
  }
21825
23077
  };
21826
- var within2 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path28.sep) ? root : `${root}${path28.sep}`);
23078
+ var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path30.sep) ? root : `${root}${path30.sep}`);
21827
23079
  var hookCwd = (input) => {
21828
23080
  const cwd = existingDirectory(input.cwd);
21829
23081
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -21832,7 +23084,7 @@ var hookCwd = (input) => {
21832
23084
  };
21833
23085
  var cursorWorkspaceRoot = (input) => {
21834
23086
  const cwd = existingDirectory(input.cwd);
21835
- return cwd && input.workspace_roots.find((root) => within2(root, cwd)) || input.workspace_roots[0];
23087
+ return cwd && input.workspace_roots.find((root) => within3(root, cwd)) || input.workspace_roots[0];
21836
23088
  };
21837
23089
  var cursorDeny = (reason) => ({
21838
23090
  permission: "deny",