@brainervirus/workit-pi 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.
package/dist/workit.js CHANGED
@@ -4,8 +4,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
4
4
 
5
5
  // packages/workit-core/src/core.ts
6
6
  import { spawnSync as spawnSync16 } from "node:child_process";
7
- import { existsSync as existsSync8, realpathSync as realpathSync5 } from "node:fs";
8
- import path25 from "node:path";
7
+ import { existsSync as existsSync9, realpathSync as realpathSync5 } from "node:fs";
8
+ import path26 from "node:path";
9
9
 
10
10
  // packages/workit-core/src/core/task-contract.ts
11
11
  import { createHash, randomUUID } from "node:crypto";
@@ -8402,7 +8402,7 @@ var workspaceConfigSchema = object({
8402
8402
  }).passthrough().optional(),
8403
8403
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
8404
8404
  defaultEndpoint: unknown().optional(),
8405
- verification: _enum(["self", "independent"]).optional(),
8405
+ verification: _enum(["proportional", "self", "independent"]).optional(),
8406
8406
  delegation: unknown().optional(),
8407
8407
  trailers: boolean2().optional(),
8408
8408
  liveSessionGuard: boolean2().optional(),
@@ -8965,6 +8965,124 @@ function resolveRef(cwd, ref) {
8965
8965
  return null;
8966
8966
  return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
8967
8967
  }
8968
+ var LANE_DIFF = [
8969
+ "-c",
8970
+ "core.quotePath=false",
8971
+ "diff",
8972
+ "--no-color",
8973
+ "--no-ext-diff",
8974
+ "--no-textconv",
8975
+ "--no-renames"
8976
+ ];
8977
+ var laneRange = (fork, head) => {
8978
+ if (!safeArg(fork) || head !== null && !safeArg(head))
8979
+ return null;
8980
+ return head === null ? [fork] : [fork, head];
8981
+ };
8982
+ function diffNumstat(cwd, fork, head) {
8983
+ const range = laneRange(fork, head);
8984
+ if (!range)
8985
+ return null;
8986
+ const run = git(cwd, [...LANE_DIFF, "--numstat", "-z", ...range, "--"]);
8987
+ if (!run.ok)
8988
+ return null;
8989
+ const out = [];
8990
+ for (const record of run.stdout.split("\x00")) {
8991
+ const match = /^(-|\d+)\t(-|\d+)\t(.+)$/su.exec(record.replace(/^\n/u, ""));
8992
+ if (!match)
8993
+ continue;
8994
+ const binary = match[1] === "-";
8995
+ out.push({
8996
+ path: match[3],
8997
+ added: binary ? 0 : Number(match[1]),
8998
+ deleted: binary ? 0 : Number(match[2]),
8999
+ binary
9000
+ });
9001
+ }
9002
+ return out;
9003
+ }
9004
+ function diffRaw(cwd, fork, head) {
9005
+ const range = laneRange(fork, head);
9006
+ if (!range)
9007
+ return null;
9008
+ const run = git(cwd, [...LANE_DIFF, "--raw", "-z", "--no-abbrev", ...range, "--"]);
9009
+ if (!run.ok)
9010
+ return null;
9011
+ const fields = run.stdout.split("\x00");
9012
+ const out = [];
9013
+ for (let index = 0;index + 1 < fields.length; index += 2) {
9014
+ const meta = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) [A-Z]\d*$/u.exec(fields[index].replace(/^\n/u, ""));
9015
+ if (!meta)
9016
+ return null;
9017
+ out.push({
9018
+ path: fields[index + 1],
9019
+ oldMode: meta[1],
9020
+ newMode: meta[2],
9021
+ oldBlob: meta[3],
9022
+ newBlob: meta[4]
9023
+ });
9024
+ }
9025
+ return out;
9026
+ }
9027
+ function readBlobs(cwd, ids) {
9028
+ const out = new Map;
9029
+ const wanted = [...new Set(ids)].filter((id) => /^[0-9a-f]{40,64}$/u.test(id));
9030
+ if (!wanted.length)
9031
+ return out;
9032
+ const run = spawnSync3("git", ["cat-file", "--batch"], {
9033
+ cwd,
9034
+ input: `${wanted.join(`
9035
+ `)}
9036
+ `,
9037
+ maxBuffer: MAX_BUFFER,
9038
+ timeout: GIT_TIMEOUTS.local,
9039
+ killSignal: "SIGKILL",
9040
+ windowsHide: true
9041
+ });
9042
+ if (run.status !== 0 || !run.stdout)
9043
+ return null;
9044
+ const bytes = run.stdout;
9045
+ let at = 0;
9046
+ for (const id of wanted) {
9047
+ const end = bytes.indexOf(10, at);
9048
+ if (end < 0)
9049
+ return null;
9050
+ const header = bytes.subarray(at, end).toString("utf8").split(" ");
9051
+ at = end + 1;
9052
+ if (header[1] === "missing")
9053
+ continue;
9054
+ const size = Number(header[2]);
9055
+ if (header[0] !== id || !Number.isSafeInteger(size))
9056
+ return null;
9057
+ out.set(id, bytes.subarray(at, at + size));
9058
+ at += size + 1;
9059
+ }
9060
+ return out;
9061
+ }
9062
+ function treeFilesNamed(cwd, rev, name) {
9063
+ if (!safeArg(rev))
9064
+ return null;
9065
+ const run = git(cwd, ["ls-tree", "-r", "-z", "--full-tree", rev]);
9066
+ if (!run.ok)
9067
+ return null;
9068
+ const out = new Map;
9069
+ for (const record of run.stdout.split("\x00")) {
9070
+ const match = /^\d{6} blob ([0-9a-f]+)\t(.+)$/su.exec(record);
9071
+ if (match && (match[2] === name || match[2].endsWith(`/${name}`)))
9072
+ out.set(match[2], match[1]);
9073
+ }
9074
+ return out;
9075
+ }
9076
+ function fileAt(cwd, rev, file) {
9077
+ if (!safeArg(rev))
9078
+ return null;
9079
+ const run = git(cwd, ["cat-file", "blob", `${rev}:${file}`]);
9080
+ return run.ok ? run.stdout : null;
9081
+ }
9082
+ function untrackedFiles(cwd) {
9083
+ const run = git(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]);
9084
+ return run.ok ? run.stdout.split("\x00").filter(Boolean) : [];
9085
+ }
8968
9086
  var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
8969
9087
  function currentBranch(cwd) {
8970
9088
  return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
@@ -12742,9 +12860,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
12742
12860
 
12743
12861
  // packages/workit-core/src/core/task-evaluation.ts
12744
12862
  import { createHash as createHash12 } from "node:crypto";
12745
- import * as fs14 from "node:fs";
12746
- import { spawnSync as spawnSync10 } from "node:child_process";
12747
- import path17 from "node:path";
12863
+ import * as fs15 from "node:fs";
12864
+ import { spawnSync as spawnSync11 } from "node:child_process";
12865
+ import path19 from "node:path";
12748
12866
 
12749
12867
  // packages/workit-core/src/check-config.ts
12750
12868
  import { spawnSync as spawnSync8 } from "node:child_process";
@@ -12974,8 +13092,8 @@ function checkCommand(args, version, onPath = workitAvailable()) {
12974
13092
  }
12975
13093
 
12976
13094
  // packages/workit-core/src/core/policy/derive.ts
12977
- import { existsSync as existsSync4 } from "node:fs";
12978
- import path16 from "node:path";
13095
+ import { existsSync as existsSync5 } from "node:fs";
13096
+ import path18 from "node:path";
12979
13097
 
12980
13098
  // packages/workit-core/src/ledger.ts
12981
13099
  import { spawnSync as spawnSync9 } from "node:child_process";
@@ -13879,7 +13997,8 @@ function checkVerdicts(cwd, branch, ledgerRows) {
13879
13997
  verdict: failing?.verdict ?? null,
13880
13998
  reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
13881
13999
  };
13882
- const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
14000
+ 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))));
14001
+ const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
13883
14002
  return {
13884
14003
  branch,
13885
14004
  head,
@@ -13923,169 +14042,905 @@ var OBSERVED_ONLY = new Set([
13923
14042
  ...PR_ROW_TYPES
13924
14043
  ]);
13925
14044
 
13926
- // packages/workit-core/src/core/policy/derive.ts
13927
- var RULES = {
13928
- test: "check:test",
13929
- self: "verdict:self",
13930
- nonAuthor: "verdict:non-author",
13931
- verified: "verdict:verified",
13932
- product: "decision:product",
13933
- plan: "plan"
13934
- };
13935
- var derived2 = (spec) => ({
13936
- ...spec,
13937
- id: requirementId({
13938
- ruleId: spec.ruleId,
13939
- scope: spec.scope,
13940
- satisfaction: spec.satisfaction,
13941
- before: spec.before,
13942
- dependentAction: spec.dependentAction
13943
- })
13944
- });
13945
- var sorted = (values) => [...new Set(values)].toSorted();
13946
- var normalizedScope = (scope) => ({
13947
- description: scope.description,
13948
- paths: sorted(scope.paths),
13949
- exclusions: sorted(scope.exclusions)
13950
- });
13951
- function deriveRequirements(judgment, scope, constraints, verification = "self") {
13952
- const out = [];
13953
- const base = { scope, dependentAction: null };
13954
- if (judgment.behaviorChange)
13955
- out.push(derived2({
13956
- ...base,
13957
- ruleId: RULES.test,
13958
- dimension: "testing",
13959
- reason: "Behavior changes: the configured test check must pass on the final tree.",
13960
- satisfaction: "Run `workit check test` on the final tree.",
13961
- before: "close",
13962
- acceptanceAllowed: false
13963
- }));
13964
- if (judgment.behaviorChange && judgment.riskTier === "normal")
13965
- out.push(verification === "independent" ? derived2({
13966
- ...base,
13967
- ruleId: RULES.nonAuthor,
13968
- dimension: "review",
13969
- reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
13970
- satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
13971
- before: "close",
13972
- acceptanceAllowed: false
13973
- }) : derived2({
13974
- ...base,
13975
- ruleId: RULES.self,
13976
- dimension: "review",
13977
- reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
13978
- satisfaction: "After an observed passing `workit check test`, record `workit ledger verdict tests-verified --self --how <what you ran>` (or have a verifier record one).",
13979
- before: "close",
13980
- acceptanceAllowed: false
13981
- }));
13982
- if (judgment.riskTier === "high")
13983
- out.push(derived2({
13984
- ...base,
13985
- ruleId: RULES.verified,
13986
- dimension: "review",
13987
- reason: "High risk: an independent verifier must exercise the live surface.",
13988
- satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
13989
- before: "close",
13990
- acceptanceAllowed: false
13991
- }));
13992
- if (judgment.productChoiceOpen)
13993
- out.push(derived2({
13994
- ...base,
13995
- ruleId: RULES.product,
13996
- dimension: "decisions",
13997
- reason: "A product or preference choice is open; the user decides it before code is written.",
13998
- satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
13999
- before: "write",
14000
- acceptanceAllowed: false
14001
- }));
14002
- if (judgment.needsPlan || judgment.riskTier === "high")
14003
- out.push(derived2({
14004
- ...base,
14005
- ruleId: RULES.plan,
14006
- dimension: "artifacts",
14007
- reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
14008
- satisfaction: "Write the plan or spec (e.g. docs/plans/<slug>.md; docs and Markdown writes stay allowed), then record it: `workit policy assess --ref <path>`.",
14009
- before: "write",
14010
- acceptanceAllowed: true
14011
- }));
14012
- for (const constraint of constraints)
14013
- for (const obligation of constraint.requires)
14014
- out.push(derived2({
14015
- ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
14016
- kind: obligation.kind,
14017
- scope: normalizedScope(obligation.scope),
14018
- refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
14019
- method: obligation.method,
14020
- before: obligation.before,
14021
- dependentAction: obligation.dependentAction
14022
- })}`,
14023
- dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
14024
- scope: obligation.scope,
14025
- reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
14026
- satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
14027
- before: obligation.before,
14028
- dependentAction: obligation.dependentAction,
14029
- acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
14030
- }));
14031
- const seen = new Set;
14032
- return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
14045
+ // packages/workit-core/src/core/policy/lane.ts
14046
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
14047
+ import path17 from "node:path";
14048
+
14049
+ // packages/workit-core/src/core/pr-create.ts
14050
+ import fs14 from "node:fs";
14051
+ import path16 from "node:path";
14052
+ import { spawnSync as spawnSync10 } from "node:child_process";
14053
+ // packages/workit-core/src/forge/pr-body.ts
14054
+ function parseGhIssue(value) {
14055
+ const m = /issues\/(\d+)/.exec(value);
14056
+ return m ? m[1] : String(value).trim().replace(/^#/, "");
14033
14057
  }
14034
- var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
14035
- var ledgerView = (root) => {
14036
- let branch;
14037
- let rows;
14038
- return {
14039
- branch: () => branch === undefined ? branch = currentBranch(root) : branch,
14040
- rows: () => {
14041
- if (rows === undefined) {
14042
- const read = readLedger(root);
14043
- rows = read.ok ? read.value.rows : [];
14044
- }
14045
- return rows;
14058
+
14059
+ // packages/workit-core/src/core/pr-create.ts
14060
+ function whichOnPath(tool) {
14061
+ const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
14062
+ for (const dir of (process.env.PATH ?? "").split(path16.delimiter)) {
14063
+ if (!dir)
14064
+ continue;
14065
+ for (const name of names) {
14066
+ const candidate = path16.join(dir, name);
14067
+ try {
14068
+ fs14.accessSync(candidate, fs14.constants.X_OK);
14069
+ return candidate;
14070
+ } catch {}
14046
14071
  }
14047
- };
14048
- };
14049
- var productDecision = (task, ledger) => {
14050
- const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
14051
- if (decisions.length)
14052
- return {
14053
- met: true,
14054
- reason: "the user's decision is recorded on the task",
14055
- decisionIds: decisions.map((entry) => entry.id)
14056
- };
14057
- const since = Date.parse(task.createdAt);
14058
- const branch = ledger.branch();
14059
- const row = ledger.rows().findLast((item) => item.type === "decision" && item.by !== "decider" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
14060
- return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
14061
- met: false,
14062
- reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or, if it is not open, `workit policy assess --judge product-choice=no --why "<reason>"`)',
14063
- decisionIds: []
14064
- };
14072
+ }
14073
+ return null;
14074
+ }
14075
+ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
14076
+ var pushRemote = (cwd) => {
14077
+ const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
14078
+ cwd,
14079
+ encoding: "utf8"
14080
+ });
14081
+ if (result.status !== 0)
14082
+ return null;
14083
+ const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
14084
+ return urls.length === 1 ? urls[0] : null;
14065
14085
  };
14066
- var planRecorded = (task, root) => {
14067
- const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path16.resolve(root, ref.path)));
14068
- if (cited)
14086
+
14087
+ // packages/workit-core/src/forge/redact.ts
14088
+ 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_.-]*`;
14089
+ var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
14090
+ var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
14091
+ var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
14092
+
14093
+ // packages/workit-core/src/git/ops.ts
14094
+ var COMMIT_TIMEOUT_MS = 10 * 60000;
14095
+
14096
+ // packages/workit-core/src/core/policy/attributes.ts
14097
+ function patternRegex(dir, pattern) {
14098
+ if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
14099
+ return null;
14100
+ const anchored = pattern.includes("/");
14101
+ const body = pattern.replace(/^\//u, "");
14102
+ let source = "";
14103
+ for (let index = 0;index < body.length; index += 1) {
14104
+ const char = body[index];
14105
+ if (char === "*" && body[index + 1] === "*") {
14106
+ const slash = body[index + 2] === "/";
14107
+ source += slash ? "(?:.*/)?" : ".*";
14108
+ index += slash ? 2 : 1;
14109
+ } else if (char === "*")
14110
+ source += "[^/]*";
14111
+ else if (char === "?")
14112
+ source += "[^/]";
14113
+ else if (char === "[") {
14114
+ const end = body.indexOf("]", index + 2);
14115
+ if (end < 0)
14116
+ return null;
14117
+ const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
14118
+ source += `[${set}]`;
14119
+ index = end;
14120
+ } else if (char === "\\" && index + 1 < body.length) {
14121
+ source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
14122
+ index += 1;
14123
+ } else
14124
+ source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
14125
+ }
14126
+ const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
14127
+ try {
14128
+ return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
14129
+ } catch {
14130
+ return null;
14131
+ }
14132
+ }
14133
+ function rulesOf(dir, text) {
14134
+ const out = [];
14135
+ for (const raw of text.split(`
14136
+ `)) {
14137
+ const line = raw.trim();
14138
+ if (!line || line.startsWith("#") || line.startsWith("[attr]"))
14139
+ continue;
14140
+ const [pattern, ...attributes] = line.split(/\s+/u);
14141
+ let generated = null;
14142
+ for (const attribute of attributes) {
14143
+ if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
14144
+ generated = true;
14145
+ else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
14146
+ generated = false;
14147
+ }
14148
+ if (generated === null)
14149
+ continue;
14150
+ const regex = patternRegex(dir, pattern);
14151
+ if (regex)
14152
+ out.push({ regex, generated });
14153
+ }
14154
+ return out;
14155
+ }
14156
+ function generatedByAttributes(files, paths) {
14157
+ const rules = [...files].map(([file, text]) => {
14158
+ const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
14069
14159
  return {
14070
- met: true,
14071
- reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
14072
- decisionIds: []
14160
+ depth: dir ? dir.split("/").length : 0,
14161
+ rules: rulesOf(dir, text)
14073
14162
  };
14074
- const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
14075
- return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
14076
- met: false,
14163
+ }).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
14164
+ const out = new Set;
14165
+ for (const file of paths) {
14166
+ const last = rules.findLast((rule) => rule.regex.test(file));
14167
+ if (last?.generated)
14168
+ out.add(file);
14169
+ }
14170
+ return out;
14171
+ }
14172
+
14173
+ // packages/workit-core/src/core/policy/substance.ts
14174
+ var SLASH_BLOCK = [["/*", "*/"]];
14175
+ var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
14176
+ var SYNTAXES = Object.fromEntries([
14177
+ ...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
14178
+ ...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
14179
+ ext,
14180
+ { ...C_LIKE, triple: true }
14181
+ ]),
14182
+ ["go", { ...C_LIKE, rawBacktick: true }],
14183
+ ...["css", "scss", "less"].map((ext) => [
14184
+ ext,
14185
+ { line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
14186
+ ]),
14187
+ ...["py", "pyi"].map((ext) => [
14188
+ ext,
14189
+ { line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
14190
+ ]),
14191
+ ["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
14192
+ ...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
14193
+ ext,
14194
+ { line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
14195
+ ])
14196
+ ]);
14197
+ 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;
14198
+ var extensionOf = (file) => {
14199
+ const name = file.slice(file.lastIndexOf("/") + 1);
14200
+ const dot = name.lastIndexOf(".");
14201
+ return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
14202
+ };
14203
+ var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
14204
+ var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
14205
+ var SPACE = /[ \t\r\f\v]/u;
14206
+ var WORD = /[\w$]/u;
14207
+ var NO_SPACE_AFTER = "([{,;";
14208
+ var NO_SPACE_BEFORE = ")]},;";
14209
+ var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
14210
+ var REGEX_KEYWORDS = new Set([
14211
+ "return",
14212
+ "typeof",
14213
+ "instanceof",
14214
+ "case",
14215
+ "do",
14216
+ "else",
14217
+ "in",
14218
+ "of",
14219
+ "new",
14220
+ "delete",
14221
+ "void",
14222
+ "throw",
14223
+ "yield",
14224
+ "await"
14225
+ ]);
14226
+ function substanceOf(file, text) {
14227
+ const syntax = syntaxOf(file);
14228
+ if (!syntax)
14229
+ return plainSubstance(text);
14230
+ const markup = syntax.block.some(([open]) => open === "<!--");
14231
+ if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
14232
+ return plainSubstance(text);
14233
+ return lex(syntax, text);
14234
+ }
14235
+ function plainSubstance(text) {
14236
+ return text.split(`
14237
+ `).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
14238
+ `);
14239
+ }
14240
+ function lex(syntax, text) {
14241
+ const collapse = !syntax.indented;
14242
+ let out = "";
14243
+ let lineStart = 0;
14244
+ let kept = 0;
14245
+ let pendingSpace = false;
14246
+ const frames = [{ kind: "code", braces: 0 }];
14247
+ let index = 0;
14248
+ if (text.startsWith("#!")) {
14249
+ const end = text.indexOf(`
14250
+ `);
14251
+ out = end < 0 ? text : text.slice(0, end);
14252
+ kept = out.length;
14253
+ index = end < 0 ? text.length : end;
14254
+ }
14255
+ const emitCode = (chunk) => {
14256
+ if (collapse && pendingSpace && out.length > lineStart) {
14257
+ const before = out[out.length - 1];
14258
+ if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
14259
+ out += " ";
14260
+ }
14261
+ pendingSpace = false;
14262
+ out += chunk;
14263
+ };
14264
+ const emitVerbatim = (chunk) => {
14265
+ emitCode(chunk);
14266
+ kept = out.length;
14267
+ };
14268
+ const newline = () => {
14269
+ if (!collapse) {
14270
+ let end = out.length;
14271
+ while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
14272
+ end -= 1;
14273
+ out = out.slice(0, end);
14274
+ }
14275
+ pendingSpace = false;
14276
+ if (out.length > lineStart) {
14277
+ out += `
14278
+ `;
14279
+ lineStart = out.length;
14280
+ }
14281
+ };
14282
+ const regexAllowed = () => {
14283
+ let end = out.length;
14284
+ while (end > 0 && /\s/u.test(out[end - 1]))
14285
+ end -= 1;
14286
+ if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
14287
+ return true;
14288
+ let start = end;
14289
+ while (start > 0 && WORD.test(out[start - 1]))
14290
+ start -= 1;
14291
+ return REGEX_KEYWORDS.has(out.slice(start, end));
14292
+ };
14293
+ const scanString = (quote, escapes, multiline) => {
14294
+ const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
14295
+ const fstring = syntax.fstring === true && /f/iu.test(prefix);
14296
+ let end = index + quote.length;
14297
+ while (end < text.length) {
14298
+ const char = text[end];
14299
+ if (escapes && char === "\\") {
14300
+ end += 2;
14301
+ continue;
14302
+ }
14303
+ if (text.startsWith(quote, end)) {
14304
+ if (fstring && unbalanced(text.slice(index + quote.length, end)))
14305
+ return false;
14306
+ emitVerbatim(text.slice(index, end + quote.length));
14307
+ index = end + quote.length;
14308
+ return true;
14309
+ }
14310
+ if (char === `
14311
+ ` && !multiline)
14312
+ return false;
14313
+ end += 1;
14314
+ }
14315
+ return false;
14316
+ };
14317
+ while (index < text.length) {
14318
+ const frame = frames[frames.length - 1];
14319
+ const char = text[index];
14320
+ if (frame.kind === "template") {
14321
+ if (char === "\\") {
14322
+ emitVerbatim(text.slice(index, index + 2));
14323
+ index += 2;
14324
+ } else if (char === "`") {
14325
+ emitVerbatim(char);
14326
+ frames.pop();
14327
+ index += 1;
14328
+ } else if (text.startsWith("${", index)) {
14329
+ emitVerbatim("${");
14330
+ frames.push({ kind: "code", braces: 0 });
14331
+ index += 2;
14332
+ } else {
14333
+ out += char;
14334
+ kept = out.length;
14335
+ index += 1;
14336
+ }
14337
+ continue;
14338
+ }
14339
+ if (char === `
14340
+ `) {
14341
+ newline();
14342
+ index += 1;
14343
+ continue;
14344
+ }
14345
+ if (SPACE.test(char)) {
14346
+ if (collapse)
14347
+ pendingSpace = true;
14348
+ else
14349
+ out += char;
14350
+ index += 1;
14351
+ continue;
14352
+ }
14353
+ const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
14354
+ if (lineOpener) {
14355
+ const end = text.indexOf(`
14356
+ `, index);
14357
+ const comment = text.slice(index, end < 0 ? text.length : end);
14358
+ if (DIRECTIVE.test(comment.slice(lineOpener.length)))
14359
+ emitVerbatim(comment);
14360
+ else
14361
+ pendingSpace = true;
14362
+ index = end < 0 ? text.length : end;
14363
+ continue;
14364
+ }
14365
+ const block = syntax.block.find(([open]) => text.startsWith(open, index));
14366
+ if (block) {
14367
+ const end = text.indexOf(block[1], index + block[0].length);
14368
+ if (end < 0)
14369
+ return null;
14370
+ const comment = text.slice(index, end + block[1].length);
14371
+ if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
14372
+ emitVerbatim(comment);
14373
+ else
14374
+ pendingSpace = true;
14375
+ if (comment.includes(`
14376
+ `))
14377
+ newline();
14378
+ index = end + block[1].length;
14379
+ continue;
14380
+ }
14381
+ if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
14382
+ if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
14383
+ return null;
14384
+ continue;
14385
+ }
14386
+ if (syntax.quotes.includes(char)) {
14387
+ if (!scanString(char, true, Boolean(syntax.multiline)))
14388
+ return null;
14389
+ continue;
14390
+ }
14391
+ if (syntax.literal?.includes(char)) {
14392
+ if (!scanString(char, false, false))
14393
+ return null;
14394
+ continue;
14395
+ }
14396
+ if (syntax.rawBacktick && char === "`") {
14397
+ if (!scanString("`", false, true))
14398
+ return null;
14399
+ continue;
14400
+ }
14401
+ if (syntax.js && char === "`") {
14402
+ emitVerbatim(char);
14403
+ frames.push({ kind: "template" });
14404
+ index += 1;
14405
+ continue;
14406
+ }
14407
+ if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
14408
+ return null;
14409
+ if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
14410
+ const lineEnd = text.indexOf(`
14411
+ `, index);
14412
+ if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
14413
+ return null;
14414
+ }
14415
+ if (syntax.js && char === "/" && regexAllowed()) {
14416
+ let end = index + 1;
14417
+ let inClass = false;
14418
+ while (end < text.length && (inClass || text[end] !== "/")) {
14419
+ if (text[end] === `
14420
+ `)
14421
+ return null;
14422
+ if (text[end] === "\\")
14423
+ end += 1;
14424
+ else if (text[end] === "[")
14425
+ inClass = true;
14426
+ else if (text[end] === "]")
14427
+ inClass = false;
14428
+ end += 1;
14429
+ }
14430
+ if (end >= text.length)
14431
+ return null;
14432
+ emitVerbatim(text.slice(index, end + 1));
14433
+ index = end + 1;
14434
+ continue;
14435
+ }
14436
+ if (frames.length > 1 && frame.kind === "code") {
14437
+ if (char === "{")
14438
+ frame.braces += 1;
14439
+ else if (char === "}") {
14440
+ if (frame.braces === 0) {
14441
+ emitVerbatim(char);
14442
+ frames.pop();
14443
+ index += 1;
14444
+ continue;
14445
+ }
14446
+ frame.braces -= 1;
14447
+ }
14448
+ }
14449
+ emitCode(char);
14450
+ index += 1;
14451
+ }
14452
+ if (frames.length !== 1)
14453
+ return null;
14454
+ newline();
14455
+ return out;
14456
+ }
14457
+ function unbalanced(body) {
14458
+ let depth = 0;
14459
+ for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
14460
+ if (part === "{")
14461
+ depth += 1;
14462
+ else if (part === "}")
14463
+ depth = Math.max(0, depth - 1);
14464
+ }
14465
+ return depth > 0;
14466
+ }
14467
+ function hasOpaqueLineBreak(text) {
14468
+ return /\r(?!\n)|[\u2028\u2029]/u.test(text);
14469
+ }
14470
+ var decoder = new TextDecoder("utf-8", { fatal: true });
14471
+ function textOf(bytes) {
14472
+ if (bytes.includes(0))
14473
+ return null;
14474
+ try {
14475
+ return decoder.decode(bytes);
14476
+ } catch {
14477
+ return null;
14478
+ }
14479
+ }
14480
+ function cosmeticChange(file, before, after) {
14481
+ const left = textOf(before);
14482
+ const right = textOf(after);
14483
+ if (left === null || right === null)
14484
+ return false;
14485
+ if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
14486
+ return false;
14487
+ const a = substanceOf(file, left);
14488
+ return a !== null && a === substanceOf(file, right);
14489
+ }
14490
+
14491
+ // packages/workit-core/src/core/policy/lane.ts
14492
+ var LANE_THRESHOLDS = {
14493
+ selfMaxLines: 100,
14494
+ selfMaxFiles: 5,
14495
+ fullMinLines: 500
14496
+ };
14497
+ var RISK_TOKENS = new Set([
14498
+ "auth",
14499
+ "authn",
14500
+ "authz",
14501
+ "oauth",
14502
+ "security",
14503
+ "permission",
14504
+ "permissions",
14505
+ "secret",
14506
+ "secrets",
14507
+ "crypto",
14508
+ "gate",
14509
+ "gates",
14510
+ "policy",
14511
+ "policies",
14512
+ "hook",
14513
+ "hooks",
14514
+ "husky",
14515
+ "lefthook",
14516
+ "release",
14517
+ "releases",
14518
+ "migration",
14519
+ "migrations",
14520
+ "migrate",
14521
+ "workflows"
14522
+ ]);
14523
+ var RISK_FILES = new Set([
14524
+ ".gitlab-ci.yml",
14525
+ "Jenkinsfile",
14526
+ "azure-pipelines.yml",
14527
+ ".pre-commit-config.yaml",
14528
+ ".workit/risk-paths"
14529
+ ]);
14530
+ var RISK_NAMES = new Set([".gitattributes"]);
14531
+ var RISK_PATHS_FILE = ".workit/risk-paths";
14532
+ var LOCKFILES = new Set([
14533
+ "bun.lock",
14534
+ "bun.lockb",
14535
+ "package-lock.json",
14536
+ "npm-shrinkwrap.json",
14537
+ "yarn.lock",
14538
+ "pnpm-lock.yaml",
14539
+ "Cargo.lock",
14540
+ "go.sum",
14541
+ "poetry.lock",
14542
+ "uv.lock",
14543
+ "Pipfile.lock",
14544
+ "Gemfile.lock",
14545
+ "composer.lock"
14546
+ ]);
14547
+ var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
14548
+ var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
14549
+ var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
14550
+ var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
14551
+ var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
14552
+ var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
14553
+ function kindOf(file) {
14554
+ if (file.binary)
14555
+ return "behavior";
14556
+ if (file.generated)
14557
+ return "generated";
14558
+ if (LOCKFILES.has(basename(file.path)))
14559
+ return "lockfile";
14560
+ if (isDoc(file.path))
14561
+ return "doc";
14562
+ if (file.cosmetic)
14563
+ return "cosmetic";
14564
+ return TEST_PATH.test(file.path) ? "test" : "behavior";
14565
+ }
14566
+ var globCache = new Map;
14567
+ function globMatch(pattern, file) {
14568
+ let regex = globCache.get(pattern);
14569
+ if (!regex) {
14570
+ let source = "";
14571
+ for (let index = 0;index < pattern.length; index += 1) {
14572
+ const char = pattern[index];
14573
+ if (char === "*" && pattern[index + 1] === "*") {
14574
+ source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
14575
+ index += pattern[index + 2] === "/" ? 2 : 1;
14576
+ } else if (char === "*")
14577
+ source += "[^/]*";
14578
+ else if (char === "?")
14579
+ source += "[^/]";
14580
+ else
14581
+ source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
14582
+ }
14583
+ regex = new RegExp(`^${source}$`, "u");
14584
+ globCache.set(pattern, regex);
14585
+ }
14586
+ return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
14587
+ }
14588
+ function isRiskPath(file, extra = []) {
14589
+ if (extra.some((pattern) => globMatch(pattern, file)))
14590
+ return true;
14591
+ if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
14592
+ return true;
14593
+ if (TEST_PATH.test(file) || isDoc(file))
14594
+ return false;
14595
+ 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)));
14596
+ }
14597
+ var parseRiskPaths = (text) => text.split(`
14598
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
14599
+ function computeLane(input) {
14600
+ const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
14601
+ const risky = new Set(riskFiles);
14602
+ const kinds = input.files.map((file) => ({
14603
+ file,
14604
+ kind: risky.has(file.path) ? "behavior" : kindOf(file)
14605
+ }));
14606
+ const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
14607
+ const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
14608
+ const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
14609
+ const result = (lane, reasons) => ({
14610
+ lane,
14611
+ reasons,
14612
+ lines,
14613
+ files: behavior.length,
14614
+ riskFiles
14615
+ });
14616
+ const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
14617
+ if (input.riskTier === "high")
14618
+ return result("full", ["the task judgment records high risk"]);
14619
+ if (riskFiles.length)
14620
+ return result("full", [
14621
+ `touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
14622
+ ]);
14623
+ if (!behavior.length)
14624
+ return result("none", [
14625
+ input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
14626
+ ]);
14627
+ if (lines >= fullMinLines)
14628
+ return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
14629
+ if (lines > selfMaxLines)
14630
+ return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
14631
+ if (behavior.length > selfMaxFiles)
14632
+ return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
14633
+ if (opaque.length)
14634
+ return result("focused", [
14635
+ `a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
14636
+ ]);
14637
+ return result("self", [
14638
+ `small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
14639
+ ]);
14640
+ }
14641
+ function untrackedStat(cwd, file) {
14642
+ try {
14643
+ const bytes = readFileSync4(path17.join(cwd, file));
14644
+ if (bytes.includes(0))
14645
+ return { path: file, added: 0, deleted: 0, binary: true };
14646
+ const text = bytes.toString("utf8");
14647
+ if (hasOpaqueLineBreak(text))
14648
+ return { path: file, added: 0, deleted: 0, binary: true };
14649
+ const lines = text.split(`
14650
+ `).length - (text.endsWith(`
14651
+ `) ? 1 : 0);
14652
+ return { path: file, added: lines, deleted: 0 };
14653
+ } catch {
14654
+ return { path: file, added: 0, deleted: 0, binary: true };
14655
+ }
14656
+ }
14657
+ var REGULAR = new Set(["100644", "100755"]);
14658
+ var ABSENT = /^0+$/u;
14659
+ function cosmeticFiles(cwd, fork, head) {
14660
+ const raw = diffRaw(cwd, fork, head);
14661
+ if (!raw)
14662
+ return null;
14663
+ const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
14664
+ const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
14665
+ if (!blobs)
14666
+ return null;
14667
+ const out = new Set;
14668
+ const opaque = new Set;
14669
+ for (const entry of candidates) {
14670
+ const before = blobs.get(entry.oldBlob);
14671
+ let after = blobs.get(entry.newBlob);
14672
+ if (!after && head === null && ABSENT.test(entry.newBlob))
14673
+ try {
14674
+ after = readFileSync4(path17.join(cwd, entry.path));
14675
+ } catch {
14676
+ after = undefined;
14677
+ }
14678
+ if (before && after && cosmeticChange(entry.path, before, after))
14679
+ out.add(entry.path);
14680
+ else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
14681
+ opaque.add(entry.path);
14682
+ }
14683
+ return { cosmetic: out, opaque };
14684
+ }
14685
+ function generatedAt(cwd, fork, paths) {
14686
+ const files = treeFilesNamed(cwd, fork, ".gitattributes");
14687
+ const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
14688
+ if (!files || !blobs)
14689
+ return new Set;
14690
+ const texts = new Map;
14691
+ for (const [file, id] of files) {
14692
+ const bytes = blobs.get(id);
14693
+ if (bytes)
14694
+ texts.set(file, bytes.toString("utf8"));
14695
+ }
14696
+ return generatedByAttributes(texts, paths);
14697
+ }
14698
+ var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
14699
+ function laneFiles(cwd, fork, head) {
14700
+ const stats = diffNumstat(cwd, fork, head);
14701
+ const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
14702
+ if (!stats || !cosmetic)
14703
+ return null;
14704
+ const files = [
14705
+ ...stats.map((entry) => ({
14706
+ ...entry,
14707
+ ...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
14708
+ cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
14709
+ })),
14710
+ ...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
14711
+ ];
14712
+ const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
14713
+ return files.map((entry) => ({
14714
+ ...entry,
14715
+ generated: generated.has(entry.path)
14716
+ }));
14717
+ }
14718
+ function branchLane(cwd, options) {
14719
+ const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
14720
+ const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
14721
+ const head = options.workingTree ? null : options.head;
14722
+ const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
14723
+ const files = fork ? laneFiles(cwd, fork, head) : null;
14724
+ const riskTier = options.judgment?.riskTier ?? null;
14725
+ if (!fork || !files) {
14726
+ const high = riskTier === "high";
14727
+ return {
14728
+ lane: high ? "full" : "focused",
14729
+ reasons: [
14730
+ `the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
14731
+ ...high ? ["the task judgment records high risk"] : []
14732
+ ],
14733
+ lines: 0,
14734
+ files: 0,
14735
+ riskFiles: [],
14736
+ base,
14737
+ head: options.head
14738
+ };
14739
+ }
14740
+ const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
14741
+ return {
14742
+ ...computeLane({
14743
+ files,
14744
+ riskTier,
14745
+ riskPaths: [...repoRisk, ...options.riskPaths ?? []]
14746
+ }),
14747
+ base,
14748
+ head: options.head
14749
+ };
14750
+ }
14751
+ var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
14752
+
14753
+ // packages/workit-core/src/core/policy/derive.ts
14754
+ var RULES = {
14755
+ test: "check:test",
14756
+ self: "verdict:self",
14757
+ nonAuthor: "verdict:non-author",
14758
+ verified: "verdict:verified",
14759
+ lane: "verdict:lane",
14760
+ product: "decision:product",
14761
+ plan: "plan"
14762
+ };
14763
+ var derived2 = (spec) => ({
14764
+ ...spec,
14765
+ id: requirementId({
14766
+ ruleId: spec.ruleId,
14767
+ scope: spec.scope,
14768
+ satisfaction: spec.satisfaction,
14769
+ before: spec.before,
14770
+ dependentAction: spec.dependentAction
14771
+ })
14772
+ });
14773
+ var sorted = (values) => [...new Set(values)].toSorted();
14774
+ var normalizedScope = (scope) => ({
14775
+ description: scope.description,
14776
+ paths: sorted(scope.paths),
14777
+ exclusions: sorted(scope.exclusions)
14778
+ });
14779
+ function deriveRequirements(judgment, scope, constraints, verification = "self") {
14780
+ const out = [];
14781
+ const base = { scope, dependentAction: null };
14782
+ const proportional = verification === "proportional";
14783
+ if (judgment.behaviorChange)
14784
+ out.push(derived2({
14785
+ ...base,
14786
+ ruleId: RULES.test,
14787
+ dimension: "testing",
14788
+ reason: "Behavior changes: the configured test check must pass on the final tree.",
14789
+ satisfaction: "Run `workit check test` on the final tree.",
14790
+ before: "close",
14791
+ acceptanceAllowed: false
14792
+ }));
14793
+ if (proportional && judgment.riskTier === "normal")
14794
+ out.push(derived2({
14795
+ ...base,
14796
+ ruleId: RULES.lane,
14797
+ dimension: "review",
14798
+ 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.",
14799
+ 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.",
14800
+ before: "close",
14801
+ acceptanceAllowed: false
14802
+ }));
14803
+ if (!proportional && judgment.behaviorChange && judgment.riskTier === "normal")
14804
+ out.push(verification === "independent" ? derived2({
14805
+ ...base,
14806
+ ruleId: RULES.nonAuthor,
14807
+ dimension: "review",
14808
+ reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
14809
+ satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
14810
+ before: "close",
14811
+ acceptanceAllowed: false
14812
+ }) : derived2({
14813
+ ...base,
14814
+ ruleId: RULES.self,
14815
+ dimension: "review",
14816
+ reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
14817
+ satisfaction: "After an observed passing `workit check test`, record `workit ledger verdict tests-verified --self --how <what you ran>` (or have a verifier record one).",
14818
+ before: "close",
14819
+ acceptanceAllowed: false
14820
+ }));
14821
+ if (judgment.riskTier === "high")
14822
+ out.push(derived2({
14823
+ ...base,
14824
+ ruleId: RULES.verified,
14825
+ dimension: "review",
14826
+ reason: "High risk: an independent verifier must exercise the live surface.",
14827
+ satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
14828
+ before: "close",
14829
+ acceptanceAllowed: false
14830
+ }));
14831
+ if (judgment.productChoiceOpen)
14832
+ out.push(derived2({
14833
+ ...base,
14834
+ ruleId: RULES.product,
14835
+ dimension: "decisions",
14836
+ reason: "A product or preference choice is open; the user decides it before code is written.",
14837
+ satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
14838
+ before: "write",
14839
+ acceptanceAllowed: false
14840
+ }));
14841
+ if (judgment.needsPlan || judgment.riskTier === "high")
14842
+ out.push(derived2({
14843
+ ...base,
14844
+ ruleId: RULES.plan,
14845
+ dimension: "artifacts",
14846
+ reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
14847
+ satisfaction: "Write the plan or spec (e.g. docs/plans/<slug>.md; docs and Markdown writes stay allowed), then record it: `workit policy assess --ref <path>`.",
14848
+ before: "write",
14849
+ acceptanceAllowed: true
14850
+ }));
14851
+ for (const constraint of constraints)
14852
+ for (const obligation of constraint.requires)
14853
+ out.push(derived2({
14854
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
14855
+ kind: obligation.kind,
14856
+ scope: normalizedScope(obligation.scope),
14857
+ refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
14858
+ method: obligation.method,
14859
+ before: obligation.before,
14860
+ dependentAction: obligation.dependentAction
14861
+ })}`,
14862
+ dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
14863
+ scope: obligation.scope,
14864
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
14865
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
14866
+ before: obligation.before,
14867
+ dependentAction: obligation.dependentAction,
14868
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
14869
+ }));
14870
+ const seen = new Set;
14871
+ return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
14872
+ }
14873
+ var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
14874
+ var ledgerView = (root) => {
14875
+ let branch;
14876
+ let rows;
14877
+ return {
14878
+ branch: () => branch === undefined ? branch = currentBranch(root) : branch,
14879
+ rows: () => {
14880
+ if (rows === undefined) {
14881
+ const read = readLedger(root);
14882
+ rows = read.ok ? read.value.rows : [];
14883
+ }
14884
+ return rows;
14885
+ }
14886
+ };
14887
+ };
14888
+ var productDecision = (task, ledger) => {
14889
+ const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
14890
+ if (decisions.length)
14891
+ return {
14892
+ met: true,
14893
+ reason: "the user's decision is recorded on the task",
14894
+ decisionIds: decisions.map((entry) => entry.id)
14895
+ };
14896
+ const since = Date.parse(task.createdAt);
14897
+ const branch = ledger.branch();
14898
+ const row = ledger.rows().findLast((item) => item.type === "decision" && item.by !== "decider" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
14899
+ return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
14900
+ met: false,
14901
+ reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or, if it is not open, `workit policy assess --judge product-choice=no --why "<reason>"`)',
14902
+ decisionIds: []
14903
+ };
14904
+ };
14905
+ var planRecorded = (task, root) => {
14906
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path18.resolve(root, ref.path)));
14907
+ if (cited)
14908
+ return {
14909
+ met: true,
14910
+ reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
14911
+ decisionIds: []
14912
+ };
14913
+ const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
14914
+ return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
14915
+ met: false,
14077
14916
  reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
14078
14917
  decisionIds: []
14079
14918
  };
14080
14919
  };
14081
- var verdict = (requirement, root, ledger) => {
14920
+ var workingLane = (task, root) => branchLane(root, { head: null, workingTree: true, judgment: latestJudgment(task) });
14921
+ var laneVerdict = (task, root, ledger) => {
14922
+ if (!ledger.branch() && latestJudgment(task)?.behaviorChange === false)
14923
+ return {
14924
+ met: true,
14925
+ reason: "no branch to read a lane from, and the judgment records no behavior change",
14926
+ decisionIds: []
14927
+ };
14928
+ const lane = workingLane(task, root);
14929
+ const label = `lane ${lane.lane} (${lane.reasons.join("; ")})`;
14930
+ const need = laneNeed(lane.lane);
14931
+ if (need === "none")
14932
+ return { met: true, reason: `${label}: no verdict needed`, decisionIds: [] };
14933
+ const status = verdict(need === "self" ? RULES.self : RULES.nonAuthor, root, ledger);
14934
+ return { ...status, reason: `${label}: ${status.reason}` };
14935
+ };
14936
+ var verdict = (ruleId, root, ledger) => {
14082
14937
  const branch = ledger.branch();
14083
14938
  if (!branch)
14084
14939
  return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
14085
14940
  const check = checkVerdicts(root, branch, ledger.rows());
14086
14941
  const failing = check.accepted.reasons.includes("failing_verdict");
14087
14942
  const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
14088
- 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)));
14943
+ 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)));
14089
14944
  if (met)
14090
14945
  return {
14091
14946
  met: true,
@@ -14093,7 +14948,7 @@ var verdict = (requirement, root, ledger) => {
14093
14948
  decisionIds: []
14094
14949
  };
14095
14950
  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, " ");
14096
- 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>\``;
14951
+ 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>\``;
14097
14952
  return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
14098
14953
  };
14099
14954
  function derivedRuleStatus(task, requirement, root, ledger) {
@@ -14105,7 +14960,9 @@ function derivedRuleStatus(task, requirement, root, ledger) {
14105
14960
  case RULES.self:
14106
14961
  case RULES.nonAuthor:
14107
14962
  case RULES.verified:
14108
- return verdict(requirement, root, ledger);
14963
+ return verdict(requirement.ruleId, root, ledger);
14964
+ case RULES.lane:
14965
+ return laneVerdict(task, root, ledger);
14109
14966
  default:
14110
14967
  return null;
14111
14968
  }
@@ -14124,14 +14981,14 @@ function writeBlockers(task, root) {
14124
14981
 
14125
14982
  // packages/workit-core/src/core/task-evaluation.ts
14126
14983
  var digestBytes = (value) => createHash12("sha256").update(value).digest("hex");
14127
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path17.sep}`);
14128
- var relative = (root, candidate) => path17.relative(root, candidate).split(path17.sep).join("/") || ".";
14984
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path19.sep}`);
14985
+ var relative = (root, candidate) => path19.relative(root, candidate).split(path19.sep).join("/") || ".";
14129
14986
  var canonicalPath = (value) => {
14130
14987
  if (typeof value !== "string" || !value)
14131
14988
  return null;
14132
14989
  if (value.includes("\\") || value.split("/").includes(".."))
14133
14990
  return null;
14134
- const normalized = path17.posix.normalize(value);
14991
+ const normalized = path19.posix.normalize(value);
14135
14992
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
14136
14993
  return null;
14137
14994
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -14167,7 +15024,7 @@ var compareCodeUnits2 = (left, right) => {
14167
15024
  return left.length - right.length;
14168
15025
  };
14169
15026
  var gitPaths = (root) => {
14170
- const result = spawnSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
15027
+ const result = spawnSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
14171
15028
  cwd: root,
14172
15029
  encoding: "buffer"
14173
15030
  });
@@ -14179,21 +15036,21 @@ var gitPaths = (root) => {
14179
15036
  git: !/not a git repository/i.test(stderr)
14180
15037
  };
14181
15038
  }
14182
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")) : [];
14183
- const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
15039
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")) : [];
15040
+ const stagedDeleted = spawnSync11("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
14184
15041
  if (stagedDeleted.status !== 0) {
14185
15042
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
14186
15043
  if (!/not a git repository/i.test(stderr))
14187
15044
  return { paths, uncertain: true, git: true };
14188
15045
  } else if (stagedDeleted.stdout) {
14189
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")));
15046
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")));
14190
15047
  }
14191
15048
  return { paths: [...new Set(paths)], uncertain: false, git: true };
14192
15049
  };
14193
15050
  var walk = (root, directory, output) => {
14194
15051
  let names;
14195
15052
  try {
14196
- names = fs14.readdirSync(directory);
15053
+ names = fs15.readdirSync(directory);
14197
15054
  } catch {
14198
15055
  return true;
14199
15056
  }
@@ -14201,10 +15058,10 @@ var walk = (root, directory, output) => {
14201
15058
  for (const name of names) {
14202
15059
  if (name === ".git" || name === ".workit")
14203
15060
  continue;
14204
- const target = path17.join(directory, name);
15061
+ const target = path19.join(directory, name);
14205
15062
  let stat;
14206
15063
  try {
14207
- stat = fs14.lstatSync(target);
15064
+ stat = fs15.lstatSync(target);
14208
15065
  } catch {
14209
15066
  uncertain = true;
14210
15067
  continue;
@@ -14219,14 +15076,14 @@ var walk = (root, directory, output) => {
14219
15076
  var scopeRoots = (root, scope) => {
14220
15077
  const roots = [];
14221
15078
  for (const item of scope.paths.length ? scope.paths : ["."]) {
14222
- const target = path17.resolve(root, item);
15079
+ const target = path19.resolve(root, item);
14223
15080
  if (!inside(root, target))
14224
15081
  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` }] });
14225
15082
  let ancestor = target;
14226
- while (!fs14.existsSync(ancestor) && ancestor !== root)
14227
- ancestor = path17.dirname(ancestor);
15083
+ while (!fs15.existsSync(ancestor) && ancestor !== root)
15084
+ ancestor = path19.dirname(ancestor);
14228
15085
  try {
14229
- if (!inside(root, fs14.realpathSync(ancestor)))
15086
+ if (!inside(root, fs15.realpathSync(ancestor)))
14230
15087
  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` }] });
14231
15088
  } catch {
14232
15089
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -14238,7 +15095,7 @@ var scopeRoots = (root, scope) => {
14238
15095
  function captureCandidate(root, scope, environment = []) {
14239
15096
  let checkout;
14240
15097
  try {
14241
- checkout = fs14.realpathSync(root);
15098
+ checkout = fs15.realpathSync(root);
14242
15099
  } catch {
14243
15100
  return failure2("invalid_input", "candidate root does not exist");
14244
15101
  }
@@ -14256,11 +15113,11 @@ function captureCandidate(root, scope, environment = []) {
14256
15113
  if (item === ".git" || item === ".workit")
14257
15114
  continue;
14258
15115
  names.add(item);
14259
- if (!fs14.existsSync(target))
15116
+ if (!fs15.existsSync(target))
14260
15117
  continue;
14261
15118
  let stat;
14262
15119
  try {
14263
- stat = fs14.lstatSync(target);
15120
+ stat = fs15.lstatSync(target);
14264
15121
  } catch {
14265
15122
  uncertain = true;
14266
15123
  continue;
@@ -14276,17 +15133,17 @@ function captureCandidate(root, scope, environment = []) {
14276
15133
  for (const item of [...names].toSorted()) {
14277
15134
  if (!scopeMatches(item, normalizedScope))
14278
15135
  continue;
14279
- const target = path17.join(checkout, item);
15136
+ const target = path19.join(checkout, item);
14280
15137
  try {
14281
- const stat = fs14.lstatSync(target);
15138
+ const stat = fs15.lstatSync(target);
14282
15139
  if (stat.isSymbolicLink()) {
14283
- const link = fs14.readlinkSync(target);
15140
+ const link = fs15.readlinkSync(target);
14284
15141
  files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
14285
15142
  } else if (stat.isFile()) {
14286
15143
  files.push({
14287
15144
  path: item,
14288
15145
  kind: "file",
14289
- digest: digestBytes(fs14.readFileSync(target)),
15146
+ digest: digestBytes(fs15.readFileSync(target)),
14290
15147
  executable: (stat.mode & 73) !== 0
14291
15148
  });
14292
15149
  } else if (!stat.isDirectory()) {
@@ -14311,7 +15168,7 @@ function captureCandidate(root, scope, environment = []) {
14311
15168
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
14312
15169
  if (values.some(({ value }) => value === null))
14313
15170
  completeness = "uncertain";
14314
- const headResult = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
15171
+ const headResult = spawnSync11("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
14315
15172
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
14316
15173
  completeness = "uncertain";
14317
15174
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -14451,7 +15308,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
14451
15308
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
14452
15309
  let root;
14453
15310
  try {
14454
- root = fs14.realpathSync(checkoutRoot);
15311
+ root = fs15.realpathSync(checkoutRoot);
14455
15312
  } catch {
14456
15313
  return failure2("permission_denied", "checkout root is unavailable");
14457
15314
  }
@@ -14460,23 +15317,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
14460
15317
  continue;
14461
15318
  if (!reference.digest)
14462
15319
  return failure2("invalid_input", "document references require a byte digest");
14463
- const target = path17.resolve(root, reference.path);
14464
- if (target !== root && !target.startsWith(`${root}${path17.sep}`))
15320
+ const target = path19.resolve(root, reference.path);
15321
+ if (target !== root && !target.startsWith(`${root}${path19.sep}`))
14465
15322
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
14466
15323
  fields: [
14467
15324
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
14468
15325
  ]
14469
15326
  });
14470
15327
  try {
14471
- const below = path17.relative(root, target);
15328
+ const below = path19.relative(root, target);
14472
15329
  let current = root;
14473
- for (const segment of below.split(path17.sep)) {
15330
+ for (const segment of below.split(path19.sep)) {
14474
15331
  if (!segment)
14475
15332
  continue;
14476
- current = path17.join(current, segment);
14477
- const stat = fs14.lstatSync(current);
14478
- const real = fs14.realpathSync(current);
14479
- if (real !== root && !real.startsWith(`${root}${path17.sep}`))
15333
+ current = path19.join(current, segment);
15334
+ const stat = fs15.lstatSync(current);
15335
+ const real = fs15.realpathSync(current);
15336
+ if (real !== root && !real.startsWith(`${root}${path19.sep}`))
14480
15337
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
14481
15338
  fields: [
14482
15339
  {
@@ -14492,7 +15349,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
14492
15349
  if (current !== target && !stat.isDirectory())
14493
15350
  return failure2("invalid_input", "document reference ancestor is not a directory");
14494
15351
  }
14495
- if (digestBytes(fs14.readFileSync(target)) !== reference.digest)
15352
+ if (digestBytes(fs15.readFileSync(target)) !== reference.digest)
14496
15353
  return failure2("permission_denied", "decision document bytes have changed");
14497
15354
  } catch {
14498
15355
  return failure2("invalid_input", "decision document is unavailable");
@@ -14913,9 +15770,9 @@ Autonomy contract:
14913
15770
  Prose is a note: only a configured check the CLI ran (\`workit check <name>\`)
14914
15771
  satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
14915
15772
  - Author is not verifier: your own verdict is \`--self\`, shown as
14916
- self-reviewed, never verified. High risk, \`verification: independent\` and
14917
- merge need a verifier with its own host-derived session (a subagent's
14918
- \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
15773
+ self-reviewed, never verified. High risk, a focused or full lane and
15774
+ merge under \`verification: self|independent\` need a verifier with its
15775
+ own host-derived session (a subagent's \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
14919
15776
  - A local-commit endpoint does not imply PR readiness, and a local commit alone
14920
15777
  is not evidence of a requested remote push. Before reporting delivery,
14921
15778
  reconcile every requested item and observe it (\`workit verify-delivery\`).
@@ -15077,21 +15934,21 @@ var exportDigest = (bundle) => sha256({
15077
15934
  task: bundle.task
15078
15935
  });
15079
15936
  // packages/workit-core/src/core/route-intent.ts
15080
- import { spawnSync as spawnSync12 } from "node:child_process";
15081
- import { existsSync as existsSync6 } from "node:fs";
15082
- import path20 from "node:path";
15937
+ import { spawnSync as spawnSync13 } from "node:child_process";
15938
+ import { existsSync as existsSync7 } from "node:fs";
15939
+ import path22 from "node:path";
15083
15940
 
15084
15941
  // packages/workit-core/src/hooks/raw-git.ts
15085
- import { spawnSync as spawnSync11 } from "node:child_process";
15942
+ import { spawnSync as spawnSync12 } from "node:child_process";
15086
15943
  import { createHash as createHash14 } from "node:crypto";
15087
- import fs16 from "node:fs";
15944
+ import fs17 from "node:fs";
15088
15945
  import os4 from "node:os";
15089
- import path19 from "node:path";
15946
+ import path21 from "node:path";
15090
15947
 
15091
15948
  // packages/workit-core/src/hooks/session-marker.ts
15092
15949
  import { createHash as createHash13 } from "node:crypto";
15093
- import fs15 from "node:fs";
15094
- import path18 from "node:path";
15950
+ import fs16 from "node:fs";
15951
+ import path20 from "node:path";
15095
15952
  var sessionMarkerFile = (input, session, store) => {
15096
15953
  let dir = store;
15097
15954
  if (dir === undefined) {
@@ -15101,13 +15958,13 @@ var sessionMarkerFile = (input, session, store) => {
15101
15958
  dir = location.dir;
15102
15959
  }
15103
15960
  const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
15104
- return path18.join(dir, "hooks", `skills-${name}.json`);
15961
+ return path20.join(dir, "hooks", `skills-${name}.json`);
15105
15962
  };
15106
15963
  var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
15107
15964
  var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
15108
15965
  var readRaw = (file) => {
15109
15966
  try {
15110
- return fs15.readFileSync(file, "utf8");
15967
+ return fs16.readFileSync(file, "utf8");
15111
15968
  } catch {
15112
15969
  return "";
15113
15970
  }
@@ -15128,13 +15985,13 @@ var parseMarker = (text) => {
15128
15985
  };
15129
15986
  var readSessionMarker = (file) => parseMarker(readRaw(file));
15130
15987
  var writeAtomic = (file, marker) => {
15131
- fs15.mkdirSync(path18.dirname(file), { recursive: true });
15988
+ fs16.mkdirSync(path20.dirname(file), { recursive: true });
15132
15989
  const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
15133
15990
  try {
15134
- fs15.writeFileSync(temp, JSON.stringify(marker));
15135
- fs15.renameSync(temp, file);
15991
+ fs16.writeFileSync(temp, JSON.stringify(marker));
15992
+ fs16.renameSync(temp, file);
15136
15993
  } catch (error) {
15137
- fs15.rmSync(temp, { force: true });
15994
+ fs16.rmSync(temp, { force: true });
15138
15995
  throw error;
15139
15996
  }
15140
15997
  };
@@ -15656,17 +16513,17 @@ var GIT_VALUE_OPTIONS = new Set([
15656
16513
  ]);
15657
16514
  var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
15658
16515
  var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
15659
- var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
16516
+ var ASSIGNMENT2 = /^([A-Za-z_]\w*)=([\s\S]*)$/;
15660
16517
  var MAX_DEPTH = 4;
15661
16518
  var MAX_EXPANSIONS = 16;
15662
- var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
16519
+ var isAbsolute = (value) => path21.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
15663
16520
  var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
15664
16521
  var joinDir = (base, raw) => {
15665
16522
  const next = raw.replace(HERE, ".");
15666
16523
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
15667
16524
  return;
15668
- const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
15669
- return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
16525
+ const expanded = next === "~" || next.startsWith("~/") ? path21.join(os4.homedir(), next.slice(1)) : next;
16526
+ return base === null || isAbsolute(expanded) ? expanded : path21.join(base, expanded);
15670
16527
  };
15671
16528
  var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
15672
16529
  var cdTarget = (words) => {
@@ -15683,7 +16540,7 @@ var stripPrefixes = (input) => {
15683
16540
  const first = words[0];
15684
16541
  if (first === undefined)
15685
16542
  return { words, env };
15686
- const assignment = ASSIGNMENT.exec(first);
16543
+ const assignment = ASSIGNMENT2.exec(first);
15687
16544
  if (assignment) {
15688
16545
  if (GIT_CONFIG_ENV.test(assignment[1]))
15689
16546
  env[assignment[1]] = assignment[2];
@@ -15742,10 +16599,10 @@ var assignments = (words, vars) => {
15742
16599
  while (rest[0]?.startsWith("-"))
15743
16600
  rest = rest.slice(1);
15744
16601
  }
15745
- if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
16602
+ if (rest.length === 0 || !rest.every((word) => ASSIGNMENT2.test(word)))
15746
16603
  return null;
15747
16604
  return rest.map((word) => {
15748
- const [, name, value] = ASSIGNMENT.exec(word);
16605
+ const [, name, value] = ASSIGNMENT2.exec(word);
15749
16606
  return [name, expandWord(value, vars)];
15750
16607
  });
15751
16608
  };
@@ -15933,7 +16790,7 @@ var splitString = (line) => {
15933
16790
  return line.slice(index).join(" ").slice(15);
15934
16791
  if (/^-S./.test(word))
15935
16792
  return [word.slice(2), ...line.slice(index + 1)].join(" ");
15936
- if (!word.startsWith("-") && !ASSIGNMENT.test(word))
16793
+ if (!word.startsWith("-") && !ASSIGNMENT2.test(word))
15937
16794
  return null;
15938
16795
  }
15939
16796
  return null;
@@ -16200,6 +17057,24 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
16200
17057
  }
16201
17058
  return found;
16202
17059
  }
17060
+ var WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
17061
+ function skipWorkitGlobals(args) {
17062
+ let cwd = null;
17063
+ let at = 0;
17064
+ for (;at < args.length; at++) {
17065
+ const match = WORKIT_GLOBAL.exec(args[at]);
17066
+ if (match?.[1] === "=") {
17067
+ if (args[at].startsWith("--cwd="))
17068
+ cwd = args[at].slice(6);
17069
+ } else if (match && args[at + 1] !== undefined) {
17070
+ if (args[at] === "--cwd")
17071
+ cwd = args[at + 1];
17072
+ at++;
17073
+ } else if (args[at] !== "--json")
17074
+ break;
17075
+ }
17076
+ return { rest: args.slice(at), cwd };
17077
+ }
16203
17078
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
16204
17079
  var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
16205
17080
  var pushDestination = (destination) => {
@@ -16478,7 +17353,7 @@ var repoAt = (dir) => {
16478
17353
  return { location, dir, branch: resolveTaskKey(dir, location).branch };
16479
17354
  };
16480
17355
  var isWorkitWorkspace = (repo) => {
16481
- if (fs16.existsSync(repo.location.dir))
17356
+ if (fs17.existsSync(repo.location.dir))
16482
17357
  return true;
16483
17358
  try {
16484
17359
  return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
@@ -16499,17 +17374,17 @@ var headSha2 = (repo) => {
16499
17374
  if (!git || git.reftable)
16500
17375
  return null;
16501
17376
  try {
16502
- const head = fs16.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
17377
+ const head = fs17.readFileSync(path21.join(git.gitDir, "HEAD"), "utf8").trim();
16503
17378
  if (/^[0-9a-f]{40,64}$/.test(head))
16504
17379
  return head;
16505
17380
  const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
16506
17381
  if (!ref)
16507
17382
  return null;
16508
- const common = path19.dirname(repo.location.dir);
17383
+ const common = path21.dirname(repo.location.dir);
16509
17384
  try {
16510
- return fs16.readFileSync(path19.join(common, ref), "utf8").trim() || null;
17385
+ return fs17.readFileSync(path21.join(common, ref), "utf8").trim() || null;
16511
17386
  } catch {}
16512
- const packed = fs16.readFileSync(path19.join(common, "packed-refs"), "utf8");
17387
+ const packed = fs17.readFileSync(path21.join(common, "packed-refs"), "utf8");
16513
17388
  return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
16514
17389
  } catch {
16515
17390
  return null;
@@ -16521,7 +17396,7 @@ var gitConfig = (dir, invocation, cache) => {
16521
17396
  if (cached)
16522
17397
  return cached;
16523
17398
  const config = new Map;
16524
- const listed = spawnSync11("git", [...invocation.config ?? [], "config", "-z", "--list"], {
17399
+ const listed = spawnSync12("git", [...invocation.config ?? [], "config", "-z", "--list"], {
16525
17400
  cwd: dir,
16526
17401
  encoding: "utf8",
16527
17402
  timeout: 3000,
@@ -16597,8 +17472,8 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
16597
17472
  const sub = invocation.args[0];
16598
17473
  if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
16599
17474
  return [invocation];
16600
- const dir = path19.resolve(root, invocation.dir ?? ".");
16601
- if (!fs16.existsSync(dir))
17475
+ const dir = path21.resolve(root, invocation.dir ?? ".");
17476
+ if (!fs17.existsSync(dir))
16602
17477
  return [invocation];
16603
17478
  const config = gitConfig(dir, invocation, cache);
16604
17479
  const alias = configValue(config, `alias.${sub.toLowerCase()}`);
@@ -16620,11 +17495,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
16620
17495
  var normalizedUrl = (url, dir) => {
16621
17496
  const trimmed = url.trim();
16622
17497
  if (/^file:\/\//i.test(trimmed))
16623
- return path19.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
17498
+ return path21.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
16624
17499
  const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
16625
17500
  const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
16626
17501
  if (!scheme && !scp)
16627
- return path19.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
17502
+ return path21.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
16628
17503
  const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
16629
17504
  return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
16630
17505
  };
@@ -16644,7 +17519,7 @@ var rewrittenUrl = (url, config) => {
16644
17519
  }
16645
17520
  return url;
16646
17521
  };
16647
- var pushUrl = (config, remote, dir) => {
17522
+ var pushUrl2 = (config, remote, dir) => {
16648
17523
  const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
16649
17524
  return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
16650
17525
  };
@@ -16672,7 +17547,7 @@ var toPushRemote = (repo, remote, config) => {
16672
17547
  const pushRemote = pushRemoteOf(config, repo.branch);
16673
17548
  if (pushRemote === null || pushRemote === remote)
16674
17549
  return true;
16675
- return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
17550
+ return pushUrl2(config, remote, repo.dir) === pushUrl2(config, pushRemote, repo.dir);
16676
17551
  };
16677
17552
  var quote = (text) => `\`${text}\``;
16678
17553
  var refusal = (action, branch, dir) => {
@@ -16685,7 +17560,7 @@ var refusal = (action, branch, dir) => {
16685
17560
  };
16686
17561
  var refFacts = (repo, remote) => {
16687
17562
  const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
16688
- const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
17563
+ const listed = spawnSync12("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
16689
17564
  const remotePrefix = `refs/remotes/${remote}/`;
16690
17565
  for (const ref of (listed.stdout ?? "").split(`
16691
17566
  `)) {
@@ -16702,7 +17577,7 @@ var globBranches = (repo, glob) => {
16702
17577
  const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
16703
17578
  const [sourcePrefix, sourceSuffix = ""] = source.split("*");
16704
17579
  const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
16705
- const listed = spawnSync11("git", [
17580
+ const listed = spawnSync12("git", [
16706
17581
  "for-each-ref",
16707
17582
  "--format=%(refname)",
16708
17583
  sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
@@ -16779,7 +17654,7 @@ var tagRefusal = (action, facts, dir) => {
16779
17654
  return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (${shape} tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
16780
17655
  };
16781
17656
  var remoteHeads = (repo, remote) => {
16782
- const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
17657
+ const listed = spawnSync12("git", ["ls-remote", "--heads", remote], {
16783
17658
  cwd: repo.dir,
16784
17659
  encoding: "utf8",
16785
17660
  timeout: 3000,
@@ -16848,7 +17723,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
16848
17723
  const out = [];
16849
17724
  const repos = new Map;
16850
17725
  const repoFor = (relative) => {
16851
- const dir = path19.resolve(cwd, relative ?? ".");
17726
+ const dir = path21.resolve(cwd, relative ?? ".");
16852
17727
  if (!repos.has(dir)) {
16853
17728
  const repo = repoAt(dir);
16854
17729
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
@@ -16934,7 +17809,7 @@ var analysisDenial = (error) => ({
16934
17809
  });
16935
17810
  var NEXT_COMMAND = "next";
16936
17811
  var MARKER_TTL_SECONDS = 3600;
16937
- var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
17812
+ var markerFile = (repo, session, key) => path21.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
16938
17813
  var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
16939
17814
  function noteRawCommit(input, command, key) {
16940
17815
  try {
@@ -16945,14 +17820,14 @@ function noteRawCommit(input, command, key) {
16945
17820
  if (action.kind !== "commit")
16946
17821
  continue;
16947
17822
  const file = markerFile(repo, input.session.id, key);
16948
- fs16.mkdirSync(path19.dirname(file), { recursive: true });
16949
- fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
17823
+ fs17.mkdirSync(path21.dirname(file), { recursive: true });
17824
+ fs17.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
16950
17825
  `);
16951
17826
  }
16952
17827
  } catch {}
16953
17828
  }
16954
17829
  var headCommit = (dir) => {
16955
- const run = spawnSync11("git", [
17830
+ const run = spawnSync12("git", [
16956
17831
  "log",
16957
17832
  "-1",
16958
17833
  "--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
@@ -16969,11 +17844,11 @@ var headCommit = (dir) => {
16969
17844
  var settle2 = (input, repo, file) => {
16970
17845
  let marker;
16971
17846
  try {
16972
- marker = JSON.parse(fs16.readFileSync(file, "utf8"));
17847
+ marker = JSON.parse(fs17.readFileSync(file, "utf8"));
16973
17848
  } catch {
16974
17849
  return;
16975
17850
  }
16976
- fs16.rmSync(file, { force: true });
17851
+ fs17.rmSync(file, { force: true });
16977
17852
  const session = input.session.id;
16978
17853
  const now = Date.now() / 1000;
16979
17854
  if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
@@ -17022,7 +17897,7 @@ function settlePendingCommit(input) {
17022
17897
  if (!repo)
17023
17898
  return;
17024
17899
  const file = markerFile(repo, input.session.id, NEXT_COMMAND);
17025
- if (fs16.existsSync(file))
17900
+ if (fs17.existsSync(file))
17026
17901
  settle2(input, repo, file);
17027
17902
  } catch {}
17028
17903
  }
@@ -17166,7 +18041,7 @@ var targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.
17166
18041
  branch,
17167
18042
  dir: invocation.dir
17168
18043
  })) : []);
17169
- var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
18044
+ var localBranchExists = (dir, branch) => spawnSync13("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
17170
18045
  cwd: dir,
17171
18046
  stdio: "ignore",
17172
18047
  timeout: 5000
@@ -17176,8 +18051,8 @@ var judgedDir = (root, dir) => {
17176
18051
  return null;
17177
18052
  if (dir === null)
17178
18053
  return root;
17179
- const resolved = path20.resolve(root, dir);
17180
- return existsSync6(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
18054
+ const resolved = path22.resolve(root, dir);
18055
+ return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
17181
18056
  };
17182
18057
  var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
17183
18058
  let judged = false;
@@ -17412,7 +18287,7 @@ var judgeInputSummary = (raw) => {
17412
18287
 
17413
18288
  // packages/workit-core/src/autonomy.ts
17414
18289
  import os5 from "node:os";
17415
- import path21 from "node:path";
18290
+ import path23 from "node:path";
17416
18291
  var accountHome = () => {
17417
18292
  try {
17418
18293
  return os5.userInfo().homedir;
@@ -17421,10 +18296,10 @@ var accountHome = () => {
17421
18296
  }
17422
18297
  };
17423
18298
  var grantsHome = { resolve: accountHome };
17424
- var grantsDir = () => path21.join(grantsHome.resolve(), ".config", "workit");
18299
+ var grantsDir = () => path23.join(grantsHome.resolve(), ".config", "workit");
17425
18300
  var grantsOverride = () => {
17426
- const configured = path21.resolve(resolveConfigDir());
17427
- return configured === path21.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 ${path21.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
18301
+ const configured = path23.resolve(resolveConfigDir());
18302
+ return configured === path23.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 ${path23.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
17428
18303
  };
17429
18304
  var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
17430
18305
  var DEFAULT_GRANTS = Object.freeze({
@@ -17477,8 +18352,16 @@ function endpointView(entry, context) {
17477
18352
  ...issue ? { endpointIssue: issue } : {}
17478
18353
  };
17479
18354
  }
17480
- var DEFAULT_VERIFICATION = "self";
17481
- var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
18355
+ var VERIFICATION_MODES = [
18356
+ "proportional",
18357
+ "self",
18358
+ "independent"
18359
+ ];
18360
+ var DEFAULT_VERIFICATION = "proportional";
18361
+ var verificationOf = (entry) => {
18362
+ const value = entry?.verification;
18363
+ return VERIFICATION_MODES.includes(value) ? value : DEFAULT_VERIFICATION;
18364
+ };
17482
18365
  var isGrantValue = (value) => value === true || value === false || value === "verified";
17483
18366
  var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
17484
18367
  var fromAutoApprove = (value) => {
@@ -19225,9 +20108,9 @@ class WorkitCore {
19225
20108
  }
19226
20109
  }
19227
20110
  // packages/workit-core/src/core/repo-context.ts
19228
- import { spawnSync as spawnSync13 } from "node:child_process";
19229
- import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
19230
- import path22 from "node:path";
20111
+ import { spawnSync as spawnSync14 } from "node:child_process";
20112
+ import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
20113
+ import path24 from "node:path";
19231
20114
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
19232
20115
  try {
19233
20116
  gitRevisionParts(value);
@@ -19254,7 +20137,7 @@ var invalidRange = (cwd) => ({
19254
20137
  cwd
19255
20138
  });
19256
20139
  var runGit = (cwd, args) => {
19257
- const result = spawnSync13("git", args, { cwd, encoding: "utf8" });
20140
+ const result = spawnSync14("git", args, { cwd, encoding: "utf8" });
19258
20141
  return {
19259
20142
  stdout: (result.stdout ?? "").trimEnd(),
19260
20143
  stderr: (result.stderr ?? "").trimEnd(),
@@ -19361,7 +20244,7 @@ var findPrTemplate = (cwd) => {
19361
20244
  ]) {
19362
20245
  const parts = rel.split("/");
19363
20246
  const base = parts[parts.length - 1];
19364
- const dir = parts.length > 1 ? path22.join(cwd, ...parts.slice(0, -1)) : cwd;
20247
+ const dir = parts.length > 1 ? path24.join(cwd, ...parts.slice(0, -1)) : cwd;
19365
20248
  const actual = probeCaseInsensitive(dir, base);
19366
20249
  if (actual)
19367
20250
  return [...parts.slice(0, -1), actual].join("/");
@@ -19392,11 +20275,11 @@ var diffStatForRange = (cwd, range) => {
19392
20275
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
19393
20276
  };
19394
20277
  var packageScripts = (cwd, keys) => {
19395
- const pkgPath = path22.join(cwd, "package.json");
19396
- if (!existsSync7(pkgPath))
20278
+ const pkgPath = path24.join(cwd, "package.json");
20279
+ if (!existsSync8(pkgPath))
19397
20280
  return [];
19398
20281
  try {
19399
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
20282
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
19400
20283
  return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
19401
20284
  } catch {
19402
20285
  return [];
@@ -19418,9 +20301,9 @@ var documentationFiles = (cwd) => {
19418
20301
  if (entry.isDirectory()) {
19419
20302
  if (excluded.has(entry.name))
19420
20303
  continue;
19421
- walk(path22.join(dir, entry.name), depth + 1);
20304
+ walk(path24.join(dir, entry.name), depth + 1);
19422
20305
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
19423
- matches.push("./" + path22.relative(cwd, path22.join(dir, entry.name)).replaceAll("\\", "/"));
20306
+ matches.push("./" + path24.relative(cwd, path24.join(dir, entry.name)).replaceAll("\\", "/"));
19424
20307
  }
19425
20308
  }
19426
20309
  };
@@ -19428,15 +20311,15 @@ var documentationFiles = (cwd) => {
19428
20311
  return matches.toSorted().slice(0, 200);
19429
20312
  };
19430
20313
  var readTrimmed2 = (file, maxLines) => {
19431
- if (!existsSync7(file))
20314
+ if (!existsSync8(file))
19432
20315
  return "";
19433
- const lines = readFileSync5(file, "utf8").split(`
20316
+ const lines = readFileSync6(file, "utf8").split(`
19434
20317
  `);
19435
20318
  return lines.slice(0, maxLines).join(`
19436
20319
  `);
19437
20320
  };
19438
20321
  function prReadyContext(root, range) {
19439
- const cwd = path22.resolve(repoRoot(root));
20322
+ const cwd = path24.resolve(repoRoot(root));
19440
20323
  if (range !== undefined && !isResolvableContextRange(cwd, range))
19441
20324
  return invalidRange(cwd);
19442
20325
  let stdout = "";
@@ -19494,7 +20377,7 @@ ${status.stderr}` : "");
19494
20377
  stdout += `template_path: ${template}
19495
20378
 
19496
20379
  `;
19497
- stdout += readTrimmed2(path22.join(cwd, template), 220);
20380
+ stdout += readTrimmed2(path24.join(cwd, template), 220);
19498
20381
  } else {
19499
20382
  stdout += `template_path: none
19500
20383
 
@@ -19504,7 +20387,7 @@ ${status.stderr}` : "");
19504
20387
  stdout += `
19505
20388
  `;
19506
20389
  stdout += printSection("Recent Validation Signals");
19507
- if (existsSync7(path22.join(cwd, "package.json"))) {
20390
+ if (existsSync8(path24.join(cwd, "package.json"))) {
19508
20391
  stdout += `package.json detected. Common scripts:
19509
20392
  `;
19510
20393
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -19513,7 +20396,7 @@ ${status.stderr}` : "");
19513
20396
  `) + `
19514
20397
  `;
19515
20398
  }
19516
- if (existsSync7(path22.join(cwd, "Cargo.toml")) || existsSync7(path22.join(cwd, "src-tauri/Cargo.toml"))) {
20399
+ if (existsSync8(path24.join(cwd, "Cargo.toml")) || existsSync8(path24.join(cwd, "src-tauri/Cargo.toml"))) {
19517
20400
  stdout += `Rust project detected.
19518
20401
  `;
19519
20402
  }
@@ -19546,7 +20429,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
19546
20429
  - Apply with the host-native editor, preserving the repository changelog conventions.
19547
20430
  - If Unreleased already has duplicate category headings, normalize_only first.`;
19548
20431
  function changelogContext(root, range) {
19549
- const cwd = path22.resolve(repoRoot(root));
20432
+ const cwd = path24.resolve(repoRoot(root));
19550
20433
  if (range !== undefined && !isResolvableContextRange(cwd, range))
19551
20434
  return invalidRange(cwd);
19552
20435
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -19564,8 +20447,8 @@ function changelogContext(root, range) {
19564
20447
  stdout += CHANGELOG_RULES + `
19565
20448
  `;
19566
20449
  stdout += printSection("Existing CHANGELOG.md");
19567
- const changelogPath = path22.join(cwd, "CHANGELOG.md");
19568
- if (existsSync7(changelogPath)) {
20450
+ const changelogPath = path24.join(cwd, "CHANGELOG.md");
20451
+ if (existsSync8(changelogPath)) {
19569
20452
  stdout += readTrimmed2(changelogPath, 260) + `
19570
20453
  `;
19571
20454
  } else {
@@ -19584,7 +20467,7 @@ function changelogContext(root, range) {
19584
20467
  return { stdout, stderr: "", exitCode: 0, cwd };
19585
20468
  }
19586
20469
  function docsRefreshContext(root, range) {
19587
- const cwd = path22.resolve(repoRoot(root));
20470
+ const cwd = path24.resolve(repoRoot(root));
19588
20471
  if (range !== undefined && !isResolvableContextRange(cwd, range))
19589
20472
  return invalidRange(cwd);
19590
20473
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -19606,8 +20489,8 @@ function docsRefreshContext(root, range) {
19606
20489
  `) + `
19607
20490
  `;
19608
20491
  stdout += printSection("README Preview");
19609
- const readme = path22.join(cwd, "README.md");
19610
- if (existsSync7(readme)) {
20492
+ const readme = path24.join(cwd, "README.md");
20493
+ if (existsSync8(readme)) {
19611
20494
  stdout += readTrimmed2(readme, 220) + `
19612
20495
  `;
19613
20496
  } else {
@@ -19615,10 +20498,10 @@ function docsRefreshContext(root, range) {
19615
20498
  `;
19616
20499
  }
19617
20500
  stdout += printSection("Package Scripts");
19618
- const pkgPath = path22.join(cwd, "package.json");
19619
- if (existsSync7(pkgPath)) {
20501
+ const pkgPath = path24.join(cwd, "package.json");
20502
+ if (existsSync8(pkgPath)) {
19620
20503
  try {
19621
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
20504
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
19622
20505
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
19623
20506
  `;
19624
20507
  } catch {}
@@ -19629,7 +20512,7 @@ function docsRefreshContext(root, range) {
19629
20512
  return { stdout, stderr: "", exitCode: 0, cwd };
19630
20513
  }
19631
20514
  function releaseNotesContext(root, rangeOrTag) {
19632
- const cwd = path22.resolve(repoRoot(root));
20515
+ const cwd = path24.resolve(repoRoot(root));
19633
20516
  if (!rangeOrTag) {
19634
20517
  return { stdout: "", stderr: `ERROR: release tag or range required
19635
20518
  `, exitCode: 1, cwd };
@@ -19667,7 +20550,7 @@ function releaseNotesContext(root, rangeOrTag) {
19667
20550
  `;
19668
20551
  stdout += printSection("Existing Release Files");
19669
20552
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
19670
- if (existsSync7(path22.join(cwd, rel)))
20553
+ if (existsSync8(path24.join(cwd, rel)))
19671
20554
  stdout += rel + `
19672
20555
  `;
19673
20556
  }
@@ -19696,46 +20579,6 @@ function releaseNotesContext(root, rangeOrTag) {
19696
20579
 
19697
20580
  // packages/workit-core/src/core/tracker-issues.ts
19698
20581
  import { spawnSync as spawnSync15 } from "node:child_process";
19699
-
19700
- // packages/workit-core/src/core/pr-create.ts
19701
- import fs17 from "node:fs";
19702
- import path23 from "node:path";
19703
- import { spawnSync as spawnSync14 } from "node:child_process";
19704
- // packages/workit-core/src/forge/pr-body.ts
19705
- function parseGhIssue(value) {
19706
- const m = /issues\/(\d+)/.exec(value);
19707
- return m ? m[1] : String(value).trim().replace(/^#/, "");
19708
- }
19709
-
19710
- // packages/workit-core/src/core/pr-create.ts
19711
- function whichOnPath(tool) {
19712
- const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
19713
- for (const dir of (process.env.PATH ?? "").split(path23.delimiter)) {
19714
- if (!dir)
19715
- continue;
19716
- for (const name of names) {
19717
- const candidate = path23.join(dir, name);
19718
- try {
19719
- fs17.accessSync(candidate, fs17.constants.X_OK);
19720
- return candidate;
19721
- } catch {}
19722
- }
19723
- }
19724
- return null;
19725
- }
19726
- var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
19727
- var pushRemote = (cwd) => {
19728
- const result = spawnSync14("git", ["remote", "get-url", "--push", "--all", "origin"], {
19729
- cwd,
19730
- encoding: "utf8"
19731
- });
19732
- if (result.status !== 0)
19733
- return null;
19734
- const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
19735
- return urls.length === 1 ? urls[0] : null;
19736
- };
19737
-
19738
- // packages/workit-core/src/core/tracker-issues.ts
19739
20582
  var repoPathFromRemote = (remote) => {
19740
20583
  let rest = (remote || "").trim().replace(/\/+$/, "");
19741
20584
  if (!rest)
@@ -19835,7 +20678,7 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
19835
20678
 
19836
20679
  // packages/workit-core/src/core/youtrack.ts
19837
20680
  import fs18 from "node:fs";
19838
- import path24 from "node:path";
20681
+ import path25 from "node:path";
19839
20682
 
19840
20683
  // packages/workit-core/src/core/templates.ts
19841
20684
  var repoRoot2 = assetRoot();
@@ -19848,8 +20691,8 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
19848
20691
  // packages/workit-core/src/core/youtrack.ts
19849
20692
  var ISSUE_RE = /^[A-Z]+-\d+$/;
19850
20693
  var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
19851
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path24.join(configDir(), "youtrack.json");
19852
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path24.join(resolveConfigDir(), "youtrack.json");
20694
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path25.join(configDir(), "youtrack.json");
20695
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path25.join(resolveConfigDir(), "youtrack.json");
19853
20696
  var youTrackTokenModeOk = (p) => {
19854
20697
  if (process.platform === "win32")
19855
20698
  return true;
@@ -19917,7 +20760,7 @@ var youTrackToken = () => {
19917
20760
  if ("error" in loaded)
19918
20761
  return loaded;
19919
20762
  const tokenFile = String(loaded.config.tokenFile ?? "");
19920
- const tokenPath = tokenFile ? path24.isAbsolute(tokenFile) ? path24.resolve(tokenFile) : path24.resolve(process.cwd(), tokenFile) : "";
20763
+ const tokenPath = tokenFile ? path25.isAbsolute(tokenFile) ? path25.resolve(tokenFile) : path25.resolve(process.cwd(), tokenFile) : "";
19921
20764
  if (!tokenPath || !fs18.existsSync(tokenPath))
19922
20765
  return { error: "missing youtrack.token" };
19923
20766
  if (!youTrackTokenModeOk(tokenPath))
@@ -20274,15 +21117,15 @@ function resolveGitRevision(root, value) {
20274
21117
  }
20275
21118
  function resolveInside(root, candidate) {
20276
21119
  const base = realpathSync5(root);
20277
- const target = path25.resolve(base, candidate);
20278
- if (target !== base && !target.startsWith(base + path25.sep)) {
21120
+ const target = path26.resolve(base, candidate);
21121
+ if (target !== base && !target.startsWith(base + path26.sep)) {
20279
21122
  throw new Error("path must stay inside repository root");
20280
21123
  }
20281
21124
  let ancestor = target;
20282
- while (!existsSync8(ancestor))
20283
- ancestor = path25.dirname(ancestor);
21125
+ while (!existsSync9(ancestor))
21126
+ ancestor = path26.dirname(ancestor);
20284
21127
  const canonicalAncestor = realpathSync5(ancestor);
20285
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path25.sep)) {
21128
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path26.sep)) {
20286
21129
  throw new Error("path must stay inside repository root");
20287
21130
  }
20288
21131
  return target;
@@ -20328,7 +21171,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
20328
21171
  // packages/workit-core/src/hooks/context.ts
20329
21172
  import { createHash as createHash15 } from "node:crypto";
20330
21173
  import fs19 from "node:fs";
20331
- import path26 from "node:path";
21174
+ import path27 from "node:path";
20332
21175
  var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
20333
21176
  var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
20334
21177
  var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
@@ -20413,7 +21256,7 @@ function entryCompactContext(store, entry, session, context) {
20413
21256
  return null;
20414
21257
  const freshness = { mode: "signal", current: () => recentSignal(store.root) };
20415
21258
  const observed = task?.data.evidence.some(isObservedCheck) ?? false;
20416
- const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path26.resolve(store.root, ref.path)] : []));
21259
+ const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path27.resolve(store.root, ref.path)] : []));
20417
21260
  const observedFiles = filesKey(files);
20418
21261
  const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
20419
21262
  if (!compact.ok || !compact.data)
@@ -20491,14 +21334,14 @@ var turnMarker = (input) => {
20491
21334
  if (location instanceof Error)
20492
21335
  return null;
20493
21336
  const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
20494
- return path26.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
21337
+ return path27.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
20495
21338
  } catch {
20496
21339
  return null;
20497
21340
  }
20498
21341
  };
20499
21342
  var writeTurnDigest = (file, digest) => {
20500
21343
  try {
20501
- fs19.mkdirSync(path26.dirname(file), { recursive: true });
21344
+ fs19.mkdirSync(path27.dirname(file), { recursive: true });
20502
21345
  fs19.writeFileSync(file, `${JSON.stringify({ digest })}
20503
21346
  `);
20504
21347
  } catch {}
@@ -20513,7 +21356,7 @@ var pruneTurnDigests = (dir, now) => {
20513
21356
  for (const name of fs19.readdirSync(dir)) {
20514
21357
  if (!name.startsWith("turn-"))
20515
21358
  continue;
20516
- const file = path26.join(dir, name);
21359
+ const file = path27.join(dir, name);
20517
21360
  try {
20518
21361
  if (now - fs19.statSync(file).mtimeMs > TURN_TTL_MS)
20519
21362
  fs19.rmSync(file, { force: true });
@@ -20543,7 +21386,7 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
20543
21386
  const file = turnMarker(input);
20544
21387
  if (!file)
20545
21388
  return;
20546
- pruneTurnDigests(path26.dirname(file), Date.now());
21389
+ pruneTurnDigests(path27.dirname(file), Date.now());
20547
21390
  if (text)
20548
21391
  writeTurnDigest(file, digestOf(text));
20549
21392
  else
@@ -20564,7 +21407,7 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
20564
21407
  // packages/workit-core/src/core/heavy-rules.ts
20565
21408
  import fs20 from "node:fs";
20566
21409
  import os6 from "node:os";
20567
- import path27 from "node:path";
21410
+ import path28 from "node:path";
20568
21411
  var EMPTY2 = () => ({ deny: [], allow: [] });
20569
21412
  var readText2 = (file) => {
20570
21413
  try {
@@ -20605,19 +21448,19 @@ function parseJsonc(text) {
20605
21448
  return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
20606
21449
  }
20607
21450
  function gitTop(cwd) {
20608
- for (let dir = path27.resolve(cwd);; dir = path27.dirname(dir)) {
20609
- if (fs20.existsSync(path27.join(dir, ".git")))
21451
+ for (let dir = path28.resolve(cwd);; dir = path28.dirname(dir)) {
21452
+ if (fs20.existsSync(path28.join(dir, ".git")))
20610
21453
  return dir;
20611
- if (path27.dirname(dir) === dir)
21454
+ if (path28.dirname(dir) === dir)
20612
21455
  return null;
20613
21456
  }
20614
21457
  }
20615
21458
  var projectDirs = (cwd) => {
20616
21459
  const top = gitTop(cwd);
20617
21460
  const dirs = [];
20618
- for (let dir = path27.resolve(cwd);; dir = path27.dirname(dir)) {
21461
+ for (let dir = path28.resolve(cwd);; dir = path28.dirname(dir)) {
20619
21462
  dirs.push(dir);
20620
- if (!top || dir === top || path27.dirname(dir) === dir)
21463
+ if (!top || dir === top || path28.dirname(dir) === dir)
20621
21464
  return dirs;
20622
21465
  }
20623
21466
  };
@@ -20629,12 +21472,12 @@ var CLAUDE_MANAGED = {
20629
21472
  };
20630
21473
  function claudeSettingsFiles(cwd, env, platform = process.platform) {
20631
21474
  const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
20632
- const user = env.CLAUDE_CONFIG_DIR || path27.join(homeOf(env), ".claude");
21475
+ const user = env.CLAUDE_CONFIG_DIR || path28.join(homeOf(env), ".claude");
20633
21476
  return [
20634
21477
  ...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
20635
- path27.join(user, "settings.json"),
20636
- path27.join(project, ".claude", "settings.json"),
20637
- path27.join(project, ".claude", "settings.local.json")
21478
+ path28.join(user, "settings.json"),
21479
+ path28.join(project, ".claude", "settings.json"),
21480
+ path28.join(project, ".claude", "settings.local.json")
20638
21481
  ];
20639
21482
  }
20640
21483
  var bashPattern = (entry) => {
@@ -20724,10 +21567,10 @@ var opencodeConfig = (value, rules) => {
20724
21567
  function opencodeRules(cwd, env = process.env) {
20725
21568
  const rules = EMPTY2();
20726
21569
  try {
20727
- const config = env.XDG_CONFIG_HOME || path27.join(homeOf(env), ".config");
21570
+ const config = env.XDG_CONFIG_HOME || path28.join(homeOf(env), ".config");
20728
21571
  const files = [
20729
- ...OPENCODE_NAMES.map((name) => path27.join(config, "opencode", name)),
20730
- ...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path27.join(dir, name), path27.join(dir, ".opencode", name)])),
21572
+ ...OPENCODE_NAMES.map((name) => path28.join(config, "opencode", name)),
21573
+ ...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path28.join(dir, name), path28.join(dir, ".opencode", name)])),
20731
21574
  ...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
20732
21575
  ];
20733
21576
  for (const file of files) {
@@ -20771,9 +21614,9 @@ var codexRule = (body) => {
20771
21614
  function codexRules(cwd, env = process.env) {
20772
21615
  const rules = EMPTY2();
20773
21616
  try {
20774
- const home = env.CODEX_HOME || path27.join(homeOf(env), ".codex");
21617
+ const home = env.CODEX_HOME || path28.join(homeOf(env), ".codex");
20775
21618
  const top = gitTop(cwd) ?? cwd;
20776
- for (const dir of [path27.join(home, "rules"), path27.join(top, ".codex", "rules")]) {
21619
+ for (const dir of [path28.join(home, "rules"), path28.join(top, ".codex", "rules")]) {
20777
21620
  let files;
20778
21621
  try {
20779
21622
  files = fs20.readdirSync(dir).filter((name) => name.endsWith(".rules"));
@@ -20783,7 +21626,7 @@ function codexRules(cwd, env = process.env) {
20783
21626
  return "unreadable";
20784
21627
  }
20785
21628
  for (const name of files) {
20786
- const text = readText2(path27.join(dir, name)) ?? "";
21629
+ const text = readText2(path28.join(dir, name)) ?? "";
20787
21630
  for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
20788
21631
  const rule = codexRule(part);
20789
21632
  if (!rule)
@@ -20802,8 +21645,8 @@ function cursorRules(cwd, env = process.env) {
20802
21645
  try {
20803
21646
  const top = gitTop(cwd) ?? cwd;
20804
21647
  for (const file of [
20805
- path27.join(homeOf(env), ".cursor", "cli-config.json"),
20806
- path27.join(top, ".cursor", "cli.json")
21648
+ path28.join(homeOf(env), ".cursor", "cli-config.json"),
21649
+ path28.join(top, ".cursor", "cli.json")
20807
21650
  ]) {
20808
21651
  const text = readText2(file);
20809
21652
  if (text === null)
@@ -20879,21 +21722,21 @@ function wrapAllowed(read, words, command, options = {}) {
20879
21722
  // packages/workit-core/src/core/heavy.ts
20880
21723
  import fs21 from "node:fs";
20881
21724
  import os7 from "node:os";
20882
- import path28 from "node:path";
21725
+ import path29 from "node:path";
20883
21726
  function stateDir(env, platform = process.platform) {
20884
21727
  if (env.WORKFLOW_TOOLKIT_STATE)
20885
21728
  return env.WORKFLOW_TOOLKIT_STATE;
20886
21729
  if (env.XDG_STATE_HOME)
20887
- return path28.join(env.XDG_STATE_HOME, "workit");
21730
+ return path29.join(env.XDG_STATE_HOME, "workit");
20888
21731
  const home = env.HOME || os7.homedir();
20889
21732
  if (platform === "darwin")
20890
- return path28.join(home, "Library", "Application Support", "workit");
21733
+ return path29.join(home, "Library", "Application Support", "workit");
20891
21734
  if (platform === "win32")
20892
- return path28.join(env.LOCALAPPDATA ?? path28.join(home, "AppData", "Local"), "workit");
20893
- return path28.join(home, ".local", "state", "workit");
21735
+ return path29.join(env.LOCALAPPDATA ?? path29.join(home, "AppData", "Local"), "workit");
21736
+ return path29.join(home, ".local", "state", "workit");
20894
21737
  }
20895
21738
  var HEAVY_COMMANDS_FILE = "heavy-commands.json";
20896
- var heavyCommandsFile = (env) => path28.join(stateDir(env), HEAVY_COMMANDS_FILE);
21739
+ var heavyCommandsFile = (env) => path29.join(stateDir(env), HEAVY_COMMANDS_FILE);
20897
21740
  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;
20898
21741
  var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
20899
21742
  var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
@@ -21168,7 +22011,7 @@ function machinePatterns(env) {
21168
22011
  return isRecord5(value) ? stringList(value.patterns) : [];
21169
22012
  }
21170
22013
  function repoPatterns(cwd) {
21171
- return repoHeavyCommands(gitTop(cwd) ?? path28.resolve(cwd));
22014
+ return repoHeavyCommands(gitTop(cwd) ?? path29.resolve(cwd));
21172
22015
  }
21173
22016
  function heavyLines(lines, options) {
21174
22017
  const found = [];
@@ -21246,12 +22089,12 @@ function heavyPlan(command, options) {
21246
22089
  }
21247
22090
  function workitOnPath2(env = process.env) {
21248
22091
  const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
21249
- for (const dir of (env.PATH ?? "").split(path28.delimiter))
22092
+ for (const dir of (env.PATH ?? "").split(path29.delimiter))
21250
22093
  for (const name of names) {
21251
22094
  if (!dir)
21252
22095
  continue;
21253
22096
  try {
21254
- fs21.accessSync(path28.join(dir, name), fs21.constants.X_OK);
22097
+ fs21.accessSync(path29.join(dir, name), fs21.constants.X_OK);
21255
22098
  return true;
21256
22099
  } catch {}
21257
22100
  }
@@ -21270,20 +22113,20 @@ function leaseRewrite(command) {
21270
22113
  // packages/workit-core/src/core/heavy-words.ts
21271
22114
  import fs22 from "node:fs";
21272
22115
  import os8 from "node:os";
21273
- import path29 from "node:path";
22116
+ import path30 from "node:path";
21274
22117
  var HEAVY_WORDS = new Set(("npm npx pnpm pnpx yarn bun bunx deno corepack make gmake docker podman nerdctl " + "python python3 py uv poetry pdm pipenv hatch rye bundle rake nx " + "jest vitest mocha ava tap karma jasmine nyc c8 tsc vue-tsc svelte-check eslint stylelint " + "tslint webpack webpack-cli rollup parcel tsup vite next nuxt nuxi astro ng react-scripts " + "playwright cypress wdio testcafe turbo lerna sonar-scanner sonarless sonarless-mcp go " + "golangci-lint gotestsum staticcheck cargo mvn mvnw gradle gradlew ant sbt bazel bazelisk " + "buck2 dotnet msbuild pytest py.test tox nox mypy pyright pylint coverage maturin pip pip3 " + "rspec rails phpunit pest phpstan psalm composer mix swift xcodebuild flutter dart pod cmake " + "ninja meson ctest scons docker-compose buildah earthly").split(" "));
21275
22118
  var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
21276
22119
  var stateDir2 = (env) => {
21277
22120
  if (env.WORKFLOW_TOOLKIT_STATE)
21278
22121
  return env.WORKFLOW_TOOLKIT_STATE;
21279
22122
  if (env.XDG_STATE_HOME)
21280
- return path29.join(env.XDG_STATE_HOME, "workit");
22123
+ return path30.join(env.XDG_STATE_HOME, "workit");
21281
22124
  const home = env.HOME || os8.homedir();
21282
22125
  if (process.platform === "darwin")
21283
- return path29.join(home, "Library", "Application Support", "workit");
22126
+ return path30.join(home, "Library", "Application Support", "workit");
21284
22127
  if (process.platform === "win32")
21285
- return path29.join(env.LOCALAPPDATA ?? path29.join(home, "AppData", "Local"), "workit");
21286
- return path29.join(home, ".local", "state", "workit");
22128
+ return path30.join(env.LOCALAPPDATA ?? path30.join(home, "AppData", "Local"), "workit");
22129
+ return path30.join(home, ".local", "state", "workit");
21287
22130
  };
21288
22131
  var readJson2 = (file) => {
21289
22132
  try {
@@ -21294,15 +22137,15 @@ var readJson2 = (file) => {
21294
22137
  };
21295
22138
  var patternWords = (cwd, env) => {
21296
22139
  const patterns = [];
21297
- const machine = readJson2(path29.join(stateDir2(env), "heavy-commands.json"));
22140
+ const machine = readJson2(path30.join(stateDir2(env), "heavy-commands.json"));
21298
22141
  if (Array.isArray(machine?.patterns))
21299
22142
  patterns.push(...machine.patterns);
21300
- for (let dir = path29.resolve(cwd);; dir = path29.dirname(dir)) {
21301
- if (fs22.existsSync(path29.join(dir, ".git"))) {
22143
+ for (let dir = path30.resolve(cwd);; dir = path30.dirname(dir)) {
22144
+ if (fs22.existsSync(path30.join(dir, ".git"))) {
21302
22145
  patterns.push(...repoHeavyCommands(dir));
21303
22146
  break;
21304
22147
  }
21305
- if (path29.dirname(dir) === dir)
22148
+ if (path30.dirname(dir) === dir)
21306
22149
  break;
21307
22150
  }
21308
22151
  const words = new Set;
@@ -21372,16 +22215,16 @@ var splitDenial = (plan) => ({
21372
22215
  unblock: plan.unblock
21373
22216
  });
21374
22217
  // packages/workit-core/src/hooks/write-gate.ts
21375
- import { realpathSync as realpathSync6 } from "node:fs";
22218
+ import { existsSync as existsSync10, realpathSync as realpathSync6 } from "node:fs";
21376
22219
  import os10 from "node:os";
21377
- import path31 from "node:path";
22220
+ import path32 from "node:path";
21378
22221
 
21379
22222
  // packages/workit-core/src/core/live-session.ts
21380
22223
  import { spawnSync as spawnSync17 } from "node:child_process";
21381
22224
  import { createHash as createHash16 } from "node:crypto";
21382
22225
  import fs23 from "node:fs";
21383
22226
  import os9 from "node:os";
21384
- import path30 from "node:path";
22227
+ import path31 from "node:path";
21385
22228
  var LIVE_SESSION_TTL_MS = 15 * 60000;
21386
22229
  var HEARTBEAT_EVERY_MS = 30000;
21387
22230
  var liveProbe = {
@@ -21455,15 +22298,15 @@ var liveOwner = (input, transport, env = process.env) => {
21455
22298
  };
21456
22299
  };
21457
22300
  var checkoutOf = (target) => {
21458
- let dir = path30.resolve(target);
21459
- while (!fs23.existsSync(dir) && path30.dirname(dir) !== dir)
21460
- dir = path30.dirname(dir);
22301
+ let dir = path31.resolve(target);
22302
+ while (!fs23.existsSync(dir) && path31.dirname(dir) !== dir)
22303
+ dir = path31.dirname(dir);
21461
22304
  if (fs23.existsSync(dir) && !fs23.statSync(dir).isDirectory())
21462
- dir = path30.dirname(dir);
21463
- const parts = dir.split(path30.sep);
22305
+ dir = path31.dirname(dir);
22306
+ const parts = dir.split(path31.sep);
21464
22307
  const dotGit = parts.lastIndexOf(".git");
21465
22308
  if (dotGit > 0)
21466
- dir = parts.slice(0, dotGit).join(path30.sep) || path30.sep;
22309
+ dir = parts.slice(0, dotGit).join(path31.sep) || path31.sep;
21467
22310
  const location = resolveStore(dir);
21468
22311
  if (location instanceof Error || !location.shared || !location.top)
21469
22312
  return null;
@@ -21473,9 +22316,9 @@ var checkoutOf = (target) => {
21473
22316
  } catch {}
21474
22317
  return top;
21475
22318
  };
21476
- var markerFile2 = (top) => path30.join(resolveStateDir(), "live-sessions", `${createHash16("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
22319
+ var markerFile2 = (top) => path31.join(resolveStateDir(), "live-sessions", `${createHash16("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
21477
22320
  var checkoutKey = (top, platform = process.platform) => {
21478
- let key = path30.resolve(top);
22321
+ let key = path31.resolve(top);
21479
22322
  try {
21480
22323
  key = fs23.realpathSync.native(key);
21481
22324
  } catch {}
@@ -21570,7 +22413,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
21570
22413
  checkout,
21571
22414
  heartbeatAt: now.toISOString()
21572
22415
  };
21573
- fs23.mkdirSync(path30.dirname(file), { recursive: true });
22416
+ fs23.mkdirSync(path31.dirname(file), { recursive: true });
21574
22417
  const temp = `${file}.${process.pid}.tmp`;
21575
22418
  fs23.writeFileSync(temp, JSON.stringify(marker));
21576
22419
  fs23.renameSync(temp, file);
@@ -21582,26 +22425,26 @@ var NONE3 = { kind: "none" };
21582
22425
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
21583
22426
  var canonical2 = (file) => {
21584
22427
  let rest = "";
21585
- for (let dir = path31.resolve(file);; dir = path31.dirname(dir)) {
22428
+ for (let dir = path32.resolve(file);; dir = path32.dirname(dir)) {
21586
22429
  try {
21587
- return path31.join(realpathSync6.native(dir), rest);
22430
+ return path32.join(realpathSync6.native(dir), rest);
21588
22431
  } catch {
21589
- if (path31.dirname(dir) === dir)
21590
- return path31.resolve(file);
21591
- rest = rest ? path31.join(path31.basename(dir), rest) : path31.basename(dir);
22432
+ if (path32.dirname(dir) === dir)
22433
+ return path32.resolve(file);
22434
+ rest = rest ? path32.join(path32.basename(dir), rest) : path32.basename(dir);
21592
22435
  }
21593
22436
  }
21594
22437
  };
21595
22438
  var exempt = (cwd, root, file, plans) => {
21596
22439
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
21597
- const relative = path31.relative(fold(canonical2(root)), fold(canonical2(path31.resolve(cwd, file))));
21598
- if (relative.startsWith("..") || path31.isAbsolute(relative))
22440
+ const relative = path32.relative(fold(canonical2(root)), fold(canonical2(path32.resolve(cwd, file))));
22441
+ if (relative.startsWith("..") || path32.isAbsolute(relative))
21599
22442
  return true;
21600
- const posix = relative.split(path31.sep).join("/");
22443
+ const posix = relative.split(path32.sep).join("/");
21601
22444
  const segments = posix.split("/");
21602
22445
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
21603
22446
  };
21604
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
22447
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
21605
22448
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
21606
22449
  var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
21607
22450
  var wrappedScript = (input) => {
@@ -21613,9 +22456,9 @@ var wrappedScript = (input) => {
21613
22456
  return null;
21614
22457
  if ("script" in tail)
21615
22458
  return tail.script;
21616
- const [shell, flag, script] = tail.argv;
21617
- if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
21618
- return script ?? null;
22459
+ const script = shellScript2(tail.argv);
22460
+ if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
22461
+ return script;
21619
22462
  return tail.argv.map(quoteWord2).join(" ");
21620
22463
  };
21621
22464
  var editsOf = (input) => {
@@ -21694,6 +22537,94 @@ var editsOf = (input) => {
21694
22537
  return [];
21695
22538
  }
21696
22539
  };
22540
+ var shellScript2 = (words) => {
22541
+ if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
22542
+ return null;
22543
+ for (let index = 1;index < words.length; index++) {
22544
+ const word = words[index];
22545
+ if (/^-[a-z]*c$/.test(word))
22546
+ return words[index + 1] ?? null;
22547
+ if (/^[-+][oO]$/.test(word))
22548
+ index++;
22549
+ else if (!/^[-+]/.test(word))
22550
+ return null;
22551
+ }
22552
+ return null;
22553
+ };
22554
+ var XARGS_VALUE = /^-[IinLPdsEa]$/;
22555
+ var runsWriter = (words) => {
22556
+ const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
22557
+ const edits = editsOf(filled);
22558
+ return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
22559
+ };
22560
+ var opaqueWrites = (input) => {
22561
+ let words = input;
22562
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
22563
+ if (words[0] === "xargs") {
22564
+ let at = 1;
22565
+ while (words[at]?.startsWith("-"))
22566
+ at += XARGS_VALUE.test(words[at]) ? 2 : 1;
22567
+ return runsWriter(words.slice(at)) ? ["."] : null;
22568
+ }
22569
+ words = words.slice(1);
22570
+ }
22571
+ const [verb, ...args] = words;
22572
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
22573
+ const files = operands(args);
22574
+ const has = (pattern) => args.some((arg) => pattern.test(arg));
22575
+ const listed = (named) => named.length ? named : ["."];
22576
+ if (name === "unlink" || name === "shred")
22577
+ return listed(files);
22578
+ if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
22579
+ return listed(has(/^--reference/) ? files : files.slice(1));
22580
+ if (name === "rsync") {
22581
+ const dest = files.at(-1);
22582
+ const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
22583
+ return dest === undefined ? ["."] : remote ? [] : [dest];
22584
+ }
22585
+ if (name === "find") {
22586
+ const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
22587
+ const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
22588
+ const end = args.findIndex((arg) => /^[-(!]/.test(arg));
22589
+ return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
22590
+ }
22591
+ if (/^g?awk$/.test(name)) {
22592
+ if (!has(/^-i|^--include/))
22593
+ return null;
22594
+ const named = [];
22595
+ let program = !has(/^-f$|^--file/);
22596
+ for (let index = 0;index < args.length; index++)
22597
+ if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
22598
+ index++;
22599
+ else if (args[index].startsWith("-"))
22600
+ continue;
22601
+ else if (program)
22602
+ program = false;
22603
+ else
22604
+ named.push(args[index]);
22605
+ return listed(named);
22606
+ }
22607
+ if (name === "perl")
22608
+ return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
22609
+ if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
22610
+ return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
22611
+ return shellScript2(words) === null ? null : ["."];
22612
+ };
22613
+ var linkSources = (args, at) => {
22614
+ const names = operands([...args]);
22615
+ const into = args.indexOf("-t");
22616
+ const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
22617
+ const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
22618
+ const target = dest === undefined ? null : at(dest);
22619
+ const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
22620
+ const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync10(target) || /[\\/]$/.test(dest ?? "") ? target : path32.dirname(target);
22621
+ return sources.flatMap((source) => {
22622
+ if (symbolic && base !== null && !path32.isAbsolute(homeExpanded(source)))
22623
+ return [path32.resolve(base, source)];
22624
+ const resolved = at(source);
22625
+ return resolved ? [resolved] : [];
22626
+ });
22627
+ };
21697
22628
  var shellWrites = (command, depth = 0) => {
21698
22629
  const targets = [];
21699
22630
  let unknown = false;
@@ -21718,7 +22649,7 @@ var shellWrites = (command, depth = 0) => {
21718
22649
  return { writes: true, targets: unknown ? null : targets };
21719
22650
  };
21720
22651
  var cache2 = new Map;
21721
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path31.resolve(root, file)) ?? "-").join("\x00");
22652
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path32.resolve(root, file)) ?? "-").join("\x00");
21722
22653
  var gateFor = (cwd) => {
21723
22654
  const store = new TaskStore(cwd);
21724
22655
  const listed = store.listTaskIndex();
@@ -21797,32 +22728,36 @@ var gitMutationDirs = (cwd, command, dialect) => {
21797
22728
  return [];
21798
22729
  if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
21799
22730
  return [];
21800
- return [path31.resolve(cwd, invocation.dir ?? ".")];
22731
+ return [path32.resolve(cwd, invocation.dir ?? ".")];
21801
22732
  });
21802
22733
  };
21803
22734
  var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
21804
22735
  var PUSHD2 = new Set(["pushd", "push-location"]);
21805
22736
  var POPD2 = new Set(["popd", "pop-location"]);
21806
- var cdInto = (dir, words) => {
22737
+ var UNKNOWN_DIR = path32.resolve(path32.parse(process.cwd()).root, ".workit-unknown-dir");
22738
+ var cdInto = (dir, words, strict = false) => {
22739
+ const lost = strict ? UNKNOWN_DIR : undefined;
21807
22740
  const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
21808
22741
  const target = named === undefined ? undefined : homeExpanded(named);
21809
- if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
21810
- return;
21811
- return target === undefined ? os10.homedir() : path31.resolve(dir, target);
22742
+ if (target === "-" || target && /^~|[$`*?]/.test(target))
22743
+ return lost;
22744
+ if (dir === undefined)
22745
+ return lost;
22746
+ return target === undefined ? os10.homedir() : path32.resolve(dir, target);
21812
22747
  };
21813
22748
  var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
21814
22749
  var homeExpanded = (file) => {
21815
22750
  const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
21816
22751
  return match ? (process.env.HOME || os10.homedir()) + file.slice(match[0].length) : file;
21817
22752
  };
21818
- var segmentTargets = (cwd, command, dialect, depth = 0) => {
22753
+ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
21819
22754
  const targets = [];
21820
22755
  let dir = cwd;
21821
22756
  let stack = [];
21822
22757
  const groups = [];
21823
22758
  const at = (file) => {
21824
22759
  const home = homeExpanded(file);
21825
- return path31.isAbsolute(home) ? home : dir === undefined ? null : path31.resolve(dir, home);
22760
+ return path32.isAbsolute(home) ? home : dir === undefined ? null : path32.resolve(dir, home);
21826
22761
  };
21827
22762
  for (const segment of segmentsOf(command, dialect)) {
21828
22763
  if (segment.group === "open") {
@@ -21839,21 +22774,35 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
21839
22774
  const words = lead < 0 ? [] : segment.words.slice(lead);
21840
22775
  const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
21841
22776
  if (CD2.has(name)) {
21842
- dir = cdInto(dir, words);
22777
+ dir = cdInto(dir, words, strict);
21843
22778
  continue;
21844
22779
  }
21845
22780
  if (PUSHD2.has(name)) {
21846
22781
  stack.push(dir);
21847
- dir = cdInto(dir, words);
22782
+ dir = cdInto(dir, words, strict);
21848
22783
  continue;
21849
22784
  }
21850
22785
  if (POPD2.has(name)) {
21851
- dir = stack.length ? stack.pop() : undefined;
22786
+ dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
21852
22787
  continue;
21853
22788
  }
21854
- const inner = depth < 4 ? wrappedScript(words) : null;
22789
+ const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
21855
22790
  if (inner !== null && dir !== undefined)
21856
- targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
22791
+ targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
22792
+ if (strict && dir !== undefined) {
22793
+ const runIn = dir;
22794
+ for (const sub of depth < 4 ? segment.subs ?? [] : [])
22795
+ targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
22796
+ const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
22797
+ for (const file of opaque ?? []) {
22798
+ const resolved = at(file);
22799
+ if (resolved)
22800
+ targets.push(file === "." ? path32.join(resolved, DIR_TARGET) : resolved);
22801
+ }
22802
+ const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
22803
+ if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
22804
+ targets.push(...linkSources(words.slice(ln + 1), at));
22805
+ }
21857
22806
  for (const file of segment.redirects)
21858
22807
  if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
21859
22808
  const resolved = at(file);
@@ -21863,18 +22812,36 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
21863
22812
  const edits = editsOf(segment.words);
21864
22813
  if (edits === null) {
21865
22814
  if (dir !== undefined)
21866
- targets.push(path31.join(dir, DIR_TARGET));
22815
+ targets.push(path32.join(dir, DIR_TARGET));
21867
22816
  } else
21868
22817
  for (const file of edits) {
21869
22818
  const resolved = at(file);
21870
22819
  if (resolved)
21871
22820
  targets.push(resolved);
21872
22821
  }
21873
- 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), "")))
21874
- targets.push(path31.join(dir, DIR_TARGET));
22822
+ if (dir !== undefined)
22823
+ for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
22824
+ const { at: runsIn, args } = workitCwd(dir, invocation);
22825
+ if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
22826
+ targets.push(path32.join(runsIn, DIR_TARGET));
22827
+ }
21875
22828
  }
21876
22829
  return targets;
21877
22830
  };
22831
+ var workitCwd = (dir, args) => {
22832
+ const { rest, cwd } = skipWorkitGlobals(args);
22833
+ return { at: cwd === null ? dir : path32.resolve(dir, homeExpanded(cwd)), args: rest };
22834
+ };
22835
+ var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
22836
+ try {
22837
+ return [
22838
+ ...segmentTargets(cwd, command, dialect, 0, strict),
22839
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path32.join(dir, DIR_TARGET))
22840
+ ];
22841
+ } catch {
22842
+ return [];
22843
+ }
22844
+ };
21878
22845
  var overridden = (command, dialect) => {
21879
22846
  const segments = segmentsOf(command, dialect);
21880
22847
  if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
@@ -21906,7 +22873,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
21906
22873
  var guarded = (targets) => {
21907
22874
  const seen = new Set;
21908
22875
  return targets.filter((target) => {
21909
- const dir = path31.dirname(target);
22876
+ const dir = path32.dirname(target);
21910
22877
  if (seen.has(dir))
21911
22878
  return false;
21912
22879
  seen.add(dir);
@@ -21927,7 +22894,7 @@ var claimAll = (owner, targets, deps) => {
21927
22894
  for (const target of guarded(targets))
21928
22895
  claimLiveSession(target, owner, deps);
21929
22896
  };
21930
- var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path31.resolve(cwd, file)) : [path31.join(cwd, DIR_TARGET)];
22897
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path32.resolve(cwd, file)) : [path32.join(cwd, DIR_TARGET)];
21931
22898
  var issuedTarget = (cwd, targets) => {
21932
22899
  const state = resolveStateDir();
21933
22900
  const found = targets.find((target) => underIssued(state, cwd, homeExpanded(target), process.platform, canonical2));
@@ -21935,7 +22902,7 @@ var issuedTarget = (cwd, targets) => {
21935
22902
  };
21936
22903
  var underIssued = (state, cwd, target, platform = process.platform, real = (file) => file) => {
21937
22904
  const win = platform === "win32";
21938
- const p = win ? path31.win32 : path31.posix;
22905
+ const p = win ? path32.win32 : path32.posix;
21939
22906
  const fold = (value) => win || platform === "darwin" ? value.toLowerCase() : value;
21940
22907
  const named = win ? target.replace(/^\/([a-zA-Z])(?=\/|$)/, "$1:") : target;
21941
22908
  const relative = p.relative(fold(real(p.resolve(state))), fold(real(p.resolve(cwd, named))));
@@ -21980,16 +22947,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
21980
22947
  } catch {}
21981
22948
  const writes = shellWrites(command);
21982
22949
  let targets = [];
21983
- if (owner) {
21984
- try {
21985
- targets = [
21986
- ...segmentTargets(cwd, command, dialect),
21987
- ...gitMutationDirs(cwd, command, dialect).map((dir) => path31.join(dir, DIR_TARGET))
21988
- ];
21989
- } catch {
21990
- targets = [];
21991
- }
21992
- }
22950
+ if (owner)
22951
+ targets = shellMutationTargets(cwd, command, dialect);
21993
22952
  if (owner && targets.length) {
21994
22953
  const live = liveGate(owner, targets, deps);
21995
22954
  if (live.kind !== "none") {
@@ -22034,6 +22993,24 @@ function beforeWriteGate(cwd, paths) {
22034
22993
  }
22035
22994
  // packages/workit-core/src/hooks/stop.ts
22036
22995
  import { spawnSync as spawnSync18 } from "node:child_process";
22996
+
22997
+ // packages/workit-core/src/core/policy/current-judgment.ts
22998
+ function currentJudgment(cwd) {
22999
+ try {
23000
+ const store = new TaskStore(cwd);
23001
+ const listed = store.listTaskIndex();
23002
+ const key = store.currentKey();
23003
+ if (!listed.ok || !key.ok)
23004
+ return null;
23005
+ const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
23006
+ const task = entry ? store.readTask(entry.id) : null;
23007
+ return task?.ok === true ? latestJudgment(task.data) : null;
23008
+ } catch {
23009
+ return null;
23010
+ }
23011
+ }
23012
+
23013
+ // packages/workit-core/src/hooks/stop.ts
22037
23014
  var NONE4 = { kind: "none" };
22038
23015
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
22039
23016
  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;
@@ -22126,29 +23103,32 @@ var redChecks = (rows, pr, head, endpoint) => {
22126
23103
  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)" : ""}.`
22127
23104
  };
22128
23105
  };
22129
- var taskJudgment = (cwd) => {
22130
- try {
22131
- const store = new TaskStore(cwd);
22132
- const listed = store.listTaskIndex();
22133
- const key = store.currentKey();
22134
- if (!listed.ok || !key.ok)
22135
- return null;
22136
- const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
22137
- const task = entry ? store.readTask(entry.id) : null;
22138
- return task?.ok === true ? latestJudgment(task.data) : null;
22139
- } catch {
22140
- return null;
22141
- }
23106
+ var prBase = (rows) => {
23107
+ const seen = rows.findLast((row) => (row.type === "pr.status" || row.type === "pr.created") && row.observer === "workit_cli" && typeof row.base === "string");
23108
+ return typeof seen?.base === "string" ? seen.base : null;
22142
23109
  };
22143
- var unverified = (cwd, branch, all, descriptor, verification) => {
23110
+ var unverified = (cwd, branch, all, descriptor, verification, head) => {
22144
23111
  const check = checkVerdicts(cwd, branch, all);
22145
23112
  if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
22146
23113
  return null;
22147
- const judgment = taskJudgment(cwd);
23114
+ const judgment = currentJudgment(cwd);
22148
23115
  const high = judgment?.riskTier === "high";
22149
- if (judgment && !judgment.behaviorChange && !high)
22150
- return null;
22151
- const independent = verification === "independent" || high;
23116
+ let independent;
23117
+ if (verification === "proportional") {
23118
+ const lane = branchLane(cwd, {
23119
+ base: prBase(all.filter((row) => row.branch === branch)),
23120
+ head,
23121
+ judgment
23122
+ });
23123
+ const need = laneNeed(lane.lane);
23124
+ if (need === "none")
23125
+ return null;
23126
+ independent = need === "non-author";
23127
+ } else {
23128
+ if (judgment && !judgment.behaviorChange && !high)
23129
+ return null;
23130
+ independent = verification === "independent" || high;
23131
+ }
22152
23132
  if (!independent && check.review === "self-reviewed")
22153
23133
  return null;
22154
23134
  const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
@@ -22195,7 +23175,7 @@ function stopObligation(input, descriptor) {
22195
23175
  }
22196
23176
  if (!delivered || endpoint !== "green" && endpoint !== "merged")
22197
23177
  return null;
22198
- const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification);
23178
+ const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification, head);
22199
23179
  return left ? { ...left, head } : null;
22200
23180
  } catch {
22201
23181
  return null;
@@ -22493,9 +23473,9 @@ var writePaths = (input) => {
22493
23473
  };
22494
23474
  // packages/workit-core/src/hooks/standing.ts
22495
23475
  import fs24 from "node:fs";
22496
- import path32 from "node:path";
23476
+ import path33 from "node:path";
22497
23477
  var readPlans = (store) => {
22498
- const dir = path32.join(store, "fanouts");
23478
+ const dir = path33.join(store, "fanouts");
22499
23479
  let names;
22500
23480
  try {
22501
23481
  names = fs24.readdirSync(dir).filter((name) => name.endsWith(".json"));
@@ -22505,7 +23485,7 @@ var readPlans = (store) => {
22505
23485
  const plans = [];
22506
23486
  for (const name of names) {
22507
23487
  try {
22508
- const raw = JSON.parse(fs24.readFileSync(path32.join(dir, name), "utf8"));
23488
+ const raw = JSON.parse(fs24.readFileSync(path33.join(dir, name), "utf8"));
22509
23489
  if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
22510
23490
  continue;
22511
23491
  const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
@@ -22628,9 +23608,266 @@ var cwdRewrite = (command) => {
22628
23608
  return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
22629
23609
  };
22630
23610
 
23611
+ // packages/workit-core/src/hooks/worktree-confine.ts
23612
+ import { createHash as createHash17 } from "node:crypto";
23613
+ import fs25 from "node:fs";
23614
+ import path34 from "node:path";
23615
+ var FANOUT_TOKEN_FILE = "workit-fanout-token";
23616
+ var NONE5 = { kind: "none" };
23617
+ var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
23618
+ var canonical3 = (file) => {
23619
+ let rest = "";
23620
+ for (let dir = path34.resolve(file);; dir = path34.dirname(dir)) {
23621
+ try {
23622
+ return path34.join(fs25.realpathSync.native(dir), rest);
23623
+ } catch {
23624
+ if (path34.dirname(dir) === dir)
23625
+ return path34.resolve(file);
23626
+ rest = rest ? path34.join(path34.basename(dir), rest) : path34.basename(dir);
23627
+ }
23628
+ }
23629
+ };
23630
+ var within = (child, parent) => {
23631
+ const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
23632
+ const relative = path34.relative(fold(parent), fold(child));
23633
+ return relative === "" || !relative.startsWith("..") && !path34.isAbsolute(relative);
23634
+ };
23635
+ var checkoutOf2 = (file) => {
23636
+ for (let dir = canonical3(file);; dir = path34.dirname(dir)) {
23637
+ const dotGit = path34.join(dir, ".git");
23638
+ if (fs25.existsSync(dotGit)) {
23639
+ if (!fs25.statSync(dotGit).isFile())
23640
+ return { root: dir, workit: false };
23641
+ const admin = /^gitdir:\s*(.+)$/mu.exec(fs25.readFileSync(dotGit, "utf8"))?.[1]?.trim();
23642
+ return {
23643
+ root: dir,
23644
+ workit: Boolean(admin) && fs25.existsSync(path34.join(path34.resolve(dir, admin), FANOUT_TOKEN_FILE))
23645
+ };
23646
+ }
23647
+ if (path34.dirname(dir) === dir)
23648
+ return null;
23649
+ }
23650
+ };
23651
+ var workitWorktreeOf = (file) => {
23652
+ try {
23653
+ const checkout = checkoutOf2(file);
23654
+ return checkout?.workit ? checkout.root : null;
23655
+ } catch {
23656
+ return null;
23657
+ }
23658
+ };
23659
+ var bindingDir = () => path34.join(resolveStateDir(), "worktree-bind");
23660
+ var bindingFile = (session, agentId) => path34.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
23661
+ var readBinding = (file) => {
23662
+ try {
23663
+ const value = JSON.parse(fs25.readFileSync(file, "utf8"));
23664
+ return typeof value.worktree === "string" ? value : null;
23665
+ } catch {
23666
+ return null;
23667
+ }
23668
+ };
23669
+ var boundWorktree = (input) => {
23670
+ const agentId = input.session.agentId;
23671
+ if (!agentId)
23672
+ return null;
23673
+ const binding = readBinding(bindingFile(input.session.id, agentId));
23674
+ return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
23675
+ };
23676
+ var bind = (input, worktree) => {
23677
+ const agentId = input.session.agentId;
23678
+ const file = bindingFile(input.session.id, agentId);
23679
+ const binding = {
23680
+ worktree,
23681
+ session: input.session.id,
23682
+ agentId,
23683
+ at: new Date().toISOString()
23684
+ };
23685
+ fs25.mkdirSync(path34.dirname(file), { recursive: true });
23686
+ fs25.writeFileSync(file, JSON.stringify(binding));
23687
+ try {
23688
+ if (inWorkitWorkspace(input.cwd))
23689
+ appendHookObserved(input.cwd, {
23690
+ type: "worktree.bound",
23691
+ actor: { host: input.host, session: input.session.id, agentId },
23692
+ agentType: input.session.agentType,
23693
+ worktree
23694
+ });
23695
+ } catch {}
23696
+ };
23697
+ var hostIsolated = (cwd, agentId) => {
23698
+ try {
23699
+ if (agentId === null)
23700
+ return false;
23701
+ const checkout = checkoutOf2(cwd);
23702
+ if (!checkout)
23703
+ return false;
23704
+ const dotGit = path34.join(checkout.root, ".git");
23705
+ if (!fs25.statSync(dotGit).isFile())
23706
+ return false;
23707
+ const admin = /^gitdir:\s*(.+)$/mu.exec(fs25.readFileSync(dotGit, "utf8"))?.[1]?.trim();
23708
+ if (!admin)
23709
+ return false;
23710
+ const adminDir = path34.resolve(checkout.root, admin);
23711
+ const back = fs25.readFileSync(path34.join(adminDir, "gitdir"), "utf8").trim();
23712
+ if (path34.relative(canonical3(path34.resolve(adminDir, back)), canonical3(dotGit)) !== "")
23713
+ return false;
23714
+ const common = path34.join(adminDir, "commondir");
23715
+ const gitDir = fs25.existsSync(common) ? path34.resolve(adminDir, fs25.readFileSync(common, "utf8").trim()) : adminDir;
23716
+ const expected = path34.join(path34.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
23717
+ return path34.relative(canonical3(expected), checkout.root) === "";
23718
+ } catch {
23719
+ return false;
23720
+ }
23721
+ };
23722
+ var shown = (target) => path34.basename(target) === ".workit-live-target" ? path34.dirname(target) : target;
23723
+ var unboundDeny = (input, target) => {
23724
+ 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.";
23725
+ return {
23726
+ kind: "deny",
23727
+ 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}`,
23728
+ unblock
23729
+ };
23730
+ };
23731
+ var outsideDeny = (input, worktree, target) => {
23732
+ 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.`;
23733
+ return {
23734
+ kind: "deny",
23735
+ reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
23736
+ unblock
23737
+ };
23738
+ };
23739
+ var unknownDirDeny = (input, worktree) => {
23740
+ const wt = worktree ?? "<worktree>";
23741
+ const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
23742
+ return {
23743
+ kind: "deny",
23744
+ reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
23745
+ unblock
23746
+ };
23747
+ };
23748
+ function confineImplementer(input, targets) {
23749
+ try {
23750
+ if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
23751
+ return NONE5;
23752
+ if (targets.some((target) => within(path34.resolve(target), UNKNOWN_DIR)))
23753
+ return unknownDirDeny(input, boundWorktree(input));
23754
+ const bindings = canonical3(bindingDir());
23755
+ const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
23756
+ if (judged.length === 0)
23757
+ return NONE5;
23758
+ let worktree = boundWorktree(input);
23759
+ if (!worktree) {
23760
+ for (const { real } of judged) {
23761
+ worktree = within(real, bindings) ? null : workitWorktreeOf(real);
23762
+ if (worktree)
23763
+ break;
23764
+ }
23765
+ if (!worktree)
23766
+ return unboundDeny(input, judged[0].named);
23767
+ bind(input, worktree);
23768
+ }
23769
+ const root = canonical3(worktree);
23770
+ const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
23771
+ return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
23772
+ } catch {
23773
+ return NONE5;
23774
+ }
23775
+ }
23776
+ var MUTATING_REMOTE = new Set([
23777
+ "add",
23778
+ "remove",
23779
+ "rm",
23780
+ "rename",
23781
+ "set-url",
23782
+ "set-head",
23783
+ "set-branches",
23784
+ "prune"
23785
+ ]);
23786
+ var CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
23787
+ var CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
23788
+ var CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
23789
+ var leadOwnedGit = (args) => {
23790
+ const [sub = "", ...rest] = args;
23791
+ if (rest.some((arg) => arg === "--help" || arg === "-h"))
23792
+ return false;
23793
+ const names = rest.filter((arg) => !arg.startsWith("-"));
23794
+ const flag = (pattern) => rest.some((arg) => pattern.test(arg));
23795
+ switch (sub) {
23796
+ case "worktree":
23797
+ return names[0] !== undefined && names[0] !== "list";
23798
+ case "update-ref":
23799
+ return true;
23800
+ case "symbolic-ref":
23801
+ return names.length > 1 || flag(/^(?:-d|--delete)$/);
23802
+ case "branch":
23803
+ return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
23804
+ case "tag":
23805
+ return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
23806
+ case "push": {
23807
+ if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
23808
+ return true;
23809
+ const refspecs = names.slice(1);
23810
+ return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
23811
+ }
23812
+ case "replace":
23813
+ case "filter-branch":
23814
+ case "gc":
23815
+ case "prune":
23816
+ case "repack":
23817
+ case "sparse-checkout":
23818
+ return true;
23819
+ case "notes":
23820
+ case "reflog":
23821
+ case "submodule":
23822
+ return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
23823
+ case "stash":
23824
+ return names[0] === "drop" || names[0] === "clear";
23825
+ case "remote":
23826
+ return MUTATING_REMOTE.has(names[0] ?? "");
23827
+ case "config": {
23828
+ if (rest.some((arg) => CONFIG_READ.test(arg)))
23829
+ return false;
23830
+ if (rest.some((arg) => CONFIG_WRITE.test(arg)))
23831
+ return true;
23832
+ let operands = 0;
23833
+ for (let index = 0;index < rest.length; index++)
23834
+ if (CONFIG_VALUE.test(rest[index]))
23835
+ index++;
23836
+ else if (!rest[index].startsWith("-"))
23837
+ operands++;
23838
+ return operands > 1;
23839
+ }
23840
+ default:
23841
+ return false;
23842
+ }
23843
+ };
23844
+ var leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
23845
+ if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
23846
+ return false;
23847
+ const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
23848
+ return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
23849
+ });
23850
+ var leadOwnedDeny = (input, command) => {
23851
+ 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.";
23852
+ return {
23853
+ kind: "deny",
23854
+ reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
23855
+ unblock
23856
+ };
23857
+ };
23858
+ function confineImplementerShell(input, command, dialect = "posix") {
23859
+ try {
23860
+ if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
23861
+ return NONE5;
23862
+ if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
23863
+ return leadOwnedDeny(input, command);
23864
+ } catch {}
23865
+ return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
23866
+ }
23867
+
22631
23868
  // packages/workit-core/src/hooks/handle.ts
22632
23869
  var usable2 = (value) => value === "native" || value === "partial";
22633
- var NONE5 = { kind: "none" };
23870
+ var NONE6 = { kind: "none" };
22634
23871
  var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
22635
23872
  var offered = new Set;
22636
23873
  var ROLES = {
@@ -22643,6 +23880,7 @@ var workitRole = (descriptor, agentType) => {
22643
23880
  const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
22644
23881
  return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
22645
23882
  };
23883
+ var implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer";
22646
23884
  var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
22647
23885
  var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
22648
23886
  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\`).`;
@@ -22650,7 +23888,7 @@ var subagentStartText = (input, descriptor, event) => {
22650
23888
  const session = input.session.id ?? null;
22651
23889
  const role = workitRole(descriptor, event.agentType);
22652
23890
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
22653
- const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
23891
+ const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
22654
23892
  const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
22655
23893
  if (role === "implementer" && isolated)
22656
23894
  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.`;
@@ -22658,7 +23896,7 @@ var subagentStartText = (input, descriptor, event) => {
22658
23896
  return `${isolatedGit(input.cwd)}
22659
23897
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
22660
23898
  if (role === "implementer")
22661
- 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.`;
23899
+ 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.`;
22662
23900
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
22663
23901
  if (role !== "judge" || !event.agentId)
22664
23902
  return readOnly;
@@ -22689,7 +23927,7 @@ function decide(input, deps) {
22689
23927
  switch (event.kind) {
22690
23928
  case "session.start": {
22691
23929
  if (!usable2(descriptor.context.sessionStart))
22692
- return NONE5;
23930
+ return NONE6;
22693
23931
  const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
22694
23932
  const offer = event.source === "startup" && (key === null || !offered.has(key));
22695
23933
  if (offer && key !== null)
@@ -22704,26 +23942,31 @@ function decide(input, deps) {
22704
23942
  }
22705
23943
  case "context.turn": {
22706
23944
  if (!usable2(descriptor.context.perTurn))
22707
- return NONE5;
23945
+ return NONE6;
22708
23946
  const current = turnContextText(input, descriptor);
22709
23947
  const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
22710
- return withContextLine(text ? { kind: "context", text } : NONE5, promptNudge(input, event.prompt));
23948
+ return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
22711
23949
  }
22712
23950
  case "shell.pre": {
22713
23951
  const skillRead = skillReadIn(event.command, event.dialect);
22714
23952
  if (skillRead)
22715
23953
  recordSkillLoad(input, skillRead, "read");
22716
23954
  const canDeny = usable2(descriptor.shellPolicy.deny);
22717
- const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
23955
+ const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
22718
23956
  if (policy.kind !== "none")
22719
23957
  return policy;
22720
23958
  const postTool = usable2(descriptor.events["shell.post"].support);
22721
23959
  if (!postTool)
22722
23960
  settlePendingCommit(input);
22723
- const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
23961
+ const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
22724
23962
  if (raw.kind === "deny")
22725
23963
  return raw;
22726
- const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
23964
+ if (canDeny && implementerAgent(input, descriptor)) {
23965
+ const confined = confineImplementerShell(input, event.command, event.dialect);
23966
+ if (confined.kind === "deny")
23967
+ return confined;
23968
+ }
23969
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
22727
23970
  if (gate.kind === "deny")
22728
23971
  return gate;
22729
23972
  const plan = heavyGate(input.cwd, event.command, {
@@ -22746,10 +23989,10 @@ function decide(input, deps) {
22746
23989
  command: rewrittenTo,
22747
23990
  input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
22748
23991
  text
22749
- } : NONE5;
23992
+ } : NONE6;
22750
23993
  if (descriptor.transport !== "hook-process")
22751
23994
  return rewritten(null);
22752
- const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
23995
+ const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
22753
23996
  if (!lease)
22754
23997
  return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
22755
23998
  return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
@@ -22758,9 +24001,17 @@ function decide(input, deps) {
22758
24001
  case "shell.post":
22759
24002
  if (usable2(descriptor.events["shell.post"].support))
22760
24003
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
22761
- return NONE5;
22762
- case "write.pre":
22763
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
24004
+ return NONE6;
24005
+ case "write.pre": {
24006
+ if (!usable2(descriptor.events["write.pre"].support))
24007
+ return NONE6;
24008
+ if (implementerAgent(input, descriptor)) {
24009
+ const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
24010
+ if (confined.kind === "deny")
24011
+ return confined;
24012
+ }
24013
+ return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
24014
+ }
22764
24015
  case "subagent.start":
22765
24016
  if (workitRole(descriptor, event.agentType) === "judge")
22766
24017
  trackJudge(input, event.agentId, true);
@@ -22777,14 +24028,14 @@ function decide(input, deps) {
22777
24028
  case "tool.pre":
22778
24029
  if (event.skill)
22779
24030
  recordSkillLoad(input, event.skill, "tool");
22780
- return NONE5;
24031
+ return NONE6;
22781
24032
  case "stop":
22782
- return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE5;
24033
+ return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
22783
24034
  case "subagent.stop":
22784
24035
  trackJudge(input, event.agentId, false);
22785
- return NONE5;
24036
+ return NONE6;
22786
24037
  case "prompt.submit":
22787
- return NONE5;
24038
+ return NONE6;
22788
24039
  }
22789
24040
  }
22790
24041
  // packages/workit-core/src/hooks/deadline.ts
@@ -23091,14 +24342,14 @@ var observeToolResult = (event, ctx) => {
23091
24342
  var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? null : promptNudge(hookInput(ctx, { kind: "context.turn", prompt }), prompt);
23092
24343
 
23093
24344
  // packages/workit-pi/src/launches.ts
23094
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
24345
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
23095
24346
  import os11 from "node:os";
23096
- import path33 from "node:path";
24347
+ import path35 from "node:path";
23097
24348
  var launchDir = (root) => {
23098
24349
  const location = resolveStore(root);
23099
- return location instanceof Error ? null : path33.join(location.dir, "pi-launches");
24350
+ return location instanceof Error ? null : path35.join(location.dir, "pi-launches");
23100
24351
  };
23101
- var fileFor = (dir, workerId) => path33.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
24352
+ var fileFor = (dir, workerId) => path35.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
23102
24353
  var recordLaunch = (root, attempt) => {
23103
24354
  const dir = launchDir(root);
23104
24355
  if (!dir)
@@ -23144,7 +24395,7 @@ var abandonedLaunches = (root) => {
23144
24395
  const found = [];
23145
24396
  for (const name of names) {
23146
24397
  try {
23147
- const attempt = JSON.parse(readFileSync6(path33.join(dir, name), "utf8"));
24398
+ const attempt = JSON.parse(readFileSync7(path35.join(dir, name), "utf8"));
23148
24399
  if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
23149
24400
  if (!alive(attempt))
23150
24401
  found.push(attempt);