@brainervirus/workit-codex 8.10.0 → 8.12.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.
@@ -91,9 +91,9 @@ Autonomy contract:
91
91
  ship), go to the effective endpoint in \`workit grant show\`: \`pr\` opens
92
92
  the PR; \`green\` babysits it without asking (workit-ship) to merge-ready,
93
93
  never merging; \`merged\` also lands it with \`workit pr merge\`.
94
- Merge and release need a workspace grant, which only the user raises from
95
- their own terminal (a blocked verb names how); never edit workspaces.json or
96
- route around a grant_required.
94
+ Merge and release need a workspace grant only the user raises from their
95
+ own terminal (a blocked verb names how); never edit workspaces.json or route
96
+ around a grant_required.
97
97
  - Ask only for a product or preference choice, or for authority you lack, with
98
98
  your recommended answer. Facts are yours: read, run or prototype.
99
99
  - Label claims measured (run and seen this session), inferred, or guess.
@@ -102,17 +102,19 @@ Autonomy contract:
102
102
  - Author is not verifier: your own verdict is \`--self\`, shown as
103
103
  self-reviewed, never verified. High risk, \`verification: independent\` and
104
104
  merge need a verifier with its own host-derived session (a subagent's
105
- \`<lead>:<child>\`, set by the host); never hand-mint one such as \`<lead>-v1\`.
105
+ \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
106
106
  - A local-commit endpoint does not imply PR readiness, and a local commit alone
107
107
  is not evidence of a requested remote push. Before reporting delivery,
108
108
  reconcile every requested item and observe it (\`workit verify-delivery\`).
109
109
 
110
110
  CLI first: if a step has one right answer, use the \`workit\` verb instead of
111
111
  hand-running git, gh or glab (\`workit git|pr|ci|check\`; \`workit help\` lists
112
- the rest; without \`workit\` on PATH, \`npx -y @brainervirus/workit-cli\`).
112
+ the rest; lacking \`workit\`, \`npx -y @brainervirus/workit-cli\`).
113
+ Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
114
+ an amend.
113
115
  The task families (task, policy, evidence, finding, decision, worker, state)
114
116
  are optional continuity for tracked work; each branch has one implicit task,
115
- created by the first recording, and a solo edit needs no task. A decision
117
+ and a solo edit needs no task. A decision
116
118
  records a user choice; it never authorizes an effect. Judge work once with
117
119
  \`workit policy assess --judge risk= behavior= product-choice= plan=\`.
118
120
  Across repositories, bind each item to its checkout, branch and endpoint,
@@ -8960,7 +8962,7 @@ function loadCheckConfig(cwd) {
8960
8962
  gates[gate] = names;
8961
8963
  }
8962
8964
  }
8963
- return { source: CHECKS_FILE, root, checks, gates, error: null };
8965
+ return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
8964
8966
  }
8965
8967
  var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
8966
8968
  function matchesNamedCheck(config, name, argv, cwd) {
@@ -12377,6 +12379,7 @@ var branchNames = (workspaceRoot) => {
12377
12379
  return new Set((r.stdout ?? "").split(`
12378
12380
  `).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
12379
12381
  };
12382
+ var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
12380
12383
  var detectBranchPolicy = (workspaceRoot) => {
12381
12384
  const names = branchNames(workspaceRoot);
12382
12385
  const develop = names.has("develop");
@@ -12505,6 +12508,7 @@ var workspaceConfigSchema = object({
12505
12508
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
12506
12509
  defaultEndpoint: unknown().optional(),
12507
12510
  verification: _enum(["self", "independent"]).optional(),
12511
+ trailers: boolean2().optional(),
12508
12512
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
12509
12513
  kind,
12510
12514
  union([boolean2(), literal("verified")]).optional()
@@ -12786,8 +12790,8 @@ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd,
12786
12790
  var runtimeWorkspaceIndexSchema = object({
12787
12791
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
12788
12792
  }).passthrough();
12789
- var matchRuntimeWorkspace = (cwd) => {
12790
- const file = path11.join(configDir(), "workspaces.json");
12793
+ var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
12794
+ const file = path11.join(dir, "workspaces.json");
12791
12795
  let raw;
12792
12796
  try {
12793
12797
  raw = readFileSync3(file, "utf8");
@@ -12816,12 +12820,30 @@ var matchRuntimeWorkspace = (cwd) => {
12816
12820
  const matches = matchingEntries(cwd, entries ?? []);
12817
12821
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
12818
12822
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
12819
- return selected ? { file, selected } : null;
12823
+ return selected ? { file, selected, matches } : null;
12820
12824
  };
12821
12825
  var resolveRuntimeReleaseTracks = (cwd) => {
12822
12826
  const matched = matchRuntimeWorkspace(cwd);
12823
12827
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
12824
12828
  };
12829
+ var workspaceTrailers = (cwd, dir) => {
12830
+ let matched;
12831
+ try {
12832
+ matched = matchRuntimeWorkspace(cwd, dir);
12833
+ } catch {
12834
+ return { enabled: true, workspace: null, from: null };
12835
+ }
12836
+ if (!matched)
12837
+ return { enabled: true, workspace: null, from: null };
12838
+ const { selected, matches } = matched;
12839
+ const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
12840
+ const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
12841
+ return {
12842
+ enabled: source?.trailers !== false,
12843
+ workspace: selected.name,
12844
+ from: source?.name ?? null
12845
+ };
12846
+ };
12825
12847
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
12826
12848
  const matched = matchRuntimeWorkspace(cwd);
12827
12849
  if (!matched)
@@ -17664,6 +17686,18 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
17664
17686
  else
17665
17687
  clearTurnDigest(file);
17666
17688
  };
17689
+ // packages/workit-core/src/core/route-intent.ts
17690
+ import { spawnSync as spawnSync12 } from "node:child_process";
17691
+ import { existsSync as existsSync6 } from "node:fs";
17692
+ import path20 from "node:path";
17693
+
17694
+ // packages/workit-core/src/hooks/raw-git.ts
17695
+ import { spawnSync as spawnSync11 } from "node:child_process";
17696
+ import { createHash as createHash14 } from "node:crypto";
17697
+ import fs16 from "node:fs";
17698
+ import os4 from "node:os";
17699
+ import path19 from "node:path";
17700
+
17667
17701
  // packages/workit-core/src/core/branch.ts
17668
17702
  import { createHash as createHash12 } from "node:crypto";
17669
17703
  var withReleaseTracks = (workspaceRoot, policy) => {
@@ -17701,10 +17735,72 @@ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
17701
17735
  provenance: { commitPolicy: selected.source }
17702
17736
  };
17703
17737
  }
17738
+ var KIND_SYNONYMS = {
17739
+ fix: "bugfix",
17740
+ fixes: "bugfix",
17741
+ bug: "bugfix",
17742
+ bugs: "bugfix",
17743
+ bugfixes: "bugfix",
17744
+ feat: "feature",
17745
+ features: "feature",
17746
+ chore: "feature",
17747
+ docs: "feature",
17748
+ doc: "feature",
17749
+ refactor: "feature",
17750
+ test: "feature",
17751
+ tests: "feature",
17752
+ ci: "feature",
17753
+ build: "feature",
17754
+ perf: "feature",
17755
+ style: "feature",
17756
+ deps: "feature",
17757
+ task: "feature",
17758
+ enhancement: "feature",
17759
+ hot: "hotfix",
17760
+ hotfixes: "hotfix",
17761
+ releases: "release",
17762
+ rel: "release"
17763
+ };
17764
+ var allowedKinds = (allowed) => [
17765
+ ...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
17766
+ ];
17767
+ var editDistance = (a, b) => {
17768
+ const row = Array.from({ length: b.length + 1 }, (_, index) => index);
17769
+ for (let i = 1;i <= a.length; i++) {
17770
+ let previous = row[0];
17771
+ row[0] = i;
17772
+ for (let j = 1;j <= b.length; j++) {
17773
+ const saved = row[j];
17774
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
17775
+ previous = saved;
17776
+ }
17777
+ }
17778
+ return row[b.length];
17779
+ };
17780
+ var suggestBranchName = (name, kinds) => {
17781
+ if (!kinds.length)
17782
+ return null;
17783
+ const slash = name.indexOf("/");
17784
+ const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
17785
+ const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
17786
+ const synonym = KIND_SYNONYMS[prefix];
17787
+ const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
17788
+ const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
17789
+ return `${kind}/${rest}`;
17790
+ };
17791
+ var kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", ");
17792
+ var aliasNote = (kinds) => {
17793
+ const notes = [
17794
+ ["fix", "bugfix"],
17795
+ ["chore", "feature"]
17796
+ ].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
17797
+ return notes.length ? ` (${notes.join(", ")})` : "";
17798
+ };
17704
17799
  var validateBranchNamePolicy = (name, policy, source) => {
17705
17800
  const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
17801
+ const kinds = allowedKinds(policy.allowed);
17706
17802
  if (policy.protected.has(name.toLowerCase())) {
17707
- const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
17803
+ const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
17708
17804
  return {
17709
17805
  ok: false,
17710
17806
  rule: "protected_ref",
@@ -17715,7 +17811,8 @@ var validateBranchNamePolicy = (name, policy, source) => {
17715
17811
  };
17716
17812
  }
17717
17813
  if (!policy.allowed.some((pattern) => pattern.test(name))) {
17718
- const correction = `choose a branch matching one of: ${allowed.join(", ")}`;
17814
+ const suggestion = suggestBranchName(name, kinds);
17815
+ const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
17719
17816
  return {
17720
17817
  ok: false,
17721
17818
  rule: "allowed_pattern",
@@ -17758,31 +17855,87 @@ var validateBranchNameFor = (workspaceRoot, name) => {
17758
17855
  }
17759
17856
  };
17760
17857
  var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
17761
-
17762
- // packages/workit-core/src/core/route-intent.ts
17763
- var shellBranchTarget = (command) => {
17764
- if (!command.trim() || /[\r\n;&|<>`'"$()\\*?{}~!#]/u.test(command) || command.includes("[") || command.includes("]"))
17765
- return null;
17766
- const args = command.trim().split(/\s+/u);
17767
- if (args[0] !== "git")
17858
+ var gitFlowOf = (root, integration) => {
17859
+ try {
17860
+ if (resolveBranchPolicyFor(root).preset !== "gitflow")
17861
+ return null;
17862
+ const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
17863
+ const names = branchNames(root);
17864
+ const production = productionBranchOf(names);
17865
+ const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
17866
+ return { develop, production, developExists: names.has(develop) };
17867
+ } catch {
17768
17868
  return null;
17769
- const option = args[2];
17770
- const target = args[1] === "switch" && ["-c", "--create", "-C", "--force-create"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "checkout" && ["-b", "-B"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "branch" && (args.length === 3 || args.length === 4) ? args[2] : null;
17771
- return target && !target.startsWith("-") ? target : null;
17869
+ }
17772
17870
  };
17773
- var shellBranchPolicyViolation = (root, command) => {
17774
- const target = shellBranchTarget(command);
17775
- return target === null ? null : validateBranchNameFor(root, target);
17871
+ var releaseBranchesOf = (cwd) => {
17872
+ try {
17873
+ const resolved = resolveBranchPolicyFor(cwd);
17874
+ const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
17875
+ return {
17876
+ preset: resolved.preset,
17877
+ branches: flow ? [flow.production, flow.develop] : [],
17878
+ production: flow ? flow.production : resolved.defaultTargetBranch
17879
+ };
17880
+ } catch {
17881
+ return { preset: "unknown", branches: [], production: null };
17882
+ }
17776
17883
  };
17777
-
17778
- // packages/workit-core/src/hooks/policy.ts
17779
- var shellPolicy = (cwd, command) => {
17780
- const policy = shellBranchPolicyViolation(cwd, command);
17781
- return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
17884
+ var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
17885
+ var branchGuidance = (cwd) => {
17886
+ try {
17887
+ const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
17888
+ const kinds = allowedKinds(patterns);
17889
+ const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
17890
+ const anyName = patterns.some((pattern) => pattern.source === "^.*$");
17891
+ const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
17892
+ const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
17893
+ const resolved = vcsConfig("resolve", cwd);
17894
+ const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
17895
+ const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
17896
+ if (flow?.developExists)
17897
+ return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
17898
+ const names = branchNames(cwd);
17899
+ const configured = String(resolved.baseBranch ?? integration ?? "") || null;
17900
+ const trunk = productionBranchOf(names);
17901
+ const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
17902
+ return `${allowed} ${base}`;
17903
+ } catch {
17904
+ return null;
17905
+ }
17782
17906
  };
17783
- // packages/workit-core/src/hooks/write-gate.ts
17784
- import { realpathSync as realpathSync5 } from "node:fs";
17907
+ var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
17908
+
17909
+ // packages/workit-core/src/hooks/session-marker.ts
17910
+ import { createHash as createHash13 } from "node:crypto";
17911
+ import fs15 from "node:fs";
17785
17912
  import path18 from "node:path";
17913
+ var sessionMarkerFile = (input, session, store) => {
17914
+ let dir = store;
17915
+ if (dir === undefined) {
17916
+ const location = resolveStore(input.cwd);
17917
+ if (location instanceof Error || !location.shared)
17918
+ return null;
17919
+ dir = location.dir;
17920
+ }
17921
+ const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
17922
+ return path18.join(dir, "hooks", `skills-${name}.json`);
17923
+ };
17924
+ var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
17925
+ var readSessionMarker = (file) => {
17926
+ try {
17927
+ const value = JSON.parse(fs15.readFileSync(file, "utf8"));
17928
+ return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
17929
+ } catch {
17930
+ return { nudged: [], loaded: [], raw: [] };
17931
+ }
17932
+ };
17933
+ var writeSessionMarker = (file, marker) => {
17934
+ try {
17935
+ fs15.mkdirSync(path18.dirname(file), { recursive: true });
17936
+ fs15.writeFileSync(file, JSON.stringify(marker));
17937
+ } catch {}
17938
+ };
17786
17939
 
17787
17940
  // packages/workit-core/src/hooks/shell-words.ts
17788
17941
  var closingQuote = (command, start, dialect) => {
@@ -17907,301 +18060,87 @@ var segmentsOf = (command, dialect = "posix") => {
17907
18060
  return segments;
17908
18061
  };
17909
18062
 
17910
- // packages/workit-core/src/hooks/write-gate.ts
18063
+ // packages/workit-core/src/hooks/raw-git.ts
17911
18064
  var NONE2 = { kind: "none" };
17912
- var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
17913
- var canonical2 = (file) => {
17914
- let rest = "";
17915
- for (let dir = path18.resolve(file);; dir = path18.dirname(dir)) {
17916
- try {
17917
- return path18.join(realpathSync5(dir), rest);
17918
- } catch {
17919
- if (path18.dirname(dir) === dir)
17920
- return path18.resolve(file);
17921
- rest = rest ? path18.join(path18.basename(dir), rest) : path18.basename(dir);
17922
- }
17923
- }
18065
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
18066
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
18067
+ var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
18068
+ var CD = new Set(["cd", "chdir", "set-location", "sl"]);
18069
+ var PUSHD = new Set(["pushd", "push-location"]);
18070
+ var POPD = new Set(["popd", "pop-location"]);
18071
+ var GIT_VALUE_OPTIONS = new Set([
18072
+ "-c",
18073
+ "--git-dir",
18074
+ "--work-tree",
18075
+ "--namespace",
18076
+ "--exec-path",
18077
+ "--config-env",
18078
+ "--super-prefix"
18079
+ ]);
18080
+ var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
18081
+ var joinDir = (base, next) => {
18082
+ if (base === undefined || /[$`*?]/.test(next) || next === "-")
18083
+ return;
18084
+ const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
18085
+ return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
17924
18086
  };
17925
- var exempt = (cwd, root, file, plans) => {
17926
- const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
17927
- const relative = path18.relative(fold(canonical2(root)), fold(canonical2(path18.resolve(cwd, file))));
17928
- if (relative.startsWith("..") || path18.isAbsolute(relative))
17929
- return true;
17930
- const posix = relative.split(path18.sep).join("/");
17931
- const segments = posix.split("/");
17932
- return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
18087
+ var cdTarget = (words) => {
18088
+ const args = words.slice(1);
18089
+ const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
18090
+ if (flag >= 0)
18091
+ return args[flag + 1] ?? null;
18092
+ return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
17933
18093
  };
17934
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
17935
- var operands = (words) => words.filter((item) => !item.startsWith("-"));
17936
- var editsOf = (input) => {
18094
+ var stripPrefixes = (input) => {
17937
18095
  let words = input;
17938
- while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
17939
- words = words.slice(1);
17940
- const [verb, ...args] = words;
17941
- const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
17942
- if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
17943
- return [];
17944
- switch (name) {
17945
- case "touch":
17946
- case "mkdir":
17947
- case "rm":
17948
- case "rmdir":
17949
- case "tee":
17950
- case "mv":
17951
- case "truncate":
17952
- return operands(args);
17953
- case "cp":
17954
- case "ln":
17955
- case "install": {
17956
- const target = args.indexOf("-t");
17957
- if (target >= 0)
17958
- return args[target + 1] ? [args[target + 1]] : null;
17959
- const last = operands(args).at(-1);
17960
- return last ? [last] : null;
18096
+ for (;; ) {
18097
+ const first = words[0];
18098
+ if (first === undefined)
18099
+ return words;
18100
+ if (/^\w+=/.test(first))
18101
+ words = words.slice(1);
18102
+ else if (PREFIXES2.has(first)) {
18103
+ words = words.slice(1);
18104
+ while (words[0]?.startsWith("-"))
18105
+ words = words.slice(words[0] === "-u" ? 2 : 1);
18106
+ } else if (first === "timeout") {
18107
+ words = words.slice(1);
18108
+ while (words[0]?.startsWith("-"))
18109
+ words = words.slice(1);
18110
+ words = words.slice(1);
18111
+ } else
18112
+ return words;
18113
+ }
18114
+ };
18115
+ var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
18116
+ function rawInvocations(command, dialect = "posix", depth = 0) {
18117
+ const found = [];
18118
+ let dir = null;
18119
+ const groups = [];
18120
+ let stack = [];
18121
+ for (const segment of segmentsOf(command, dialect)) {
18122
+ if (segment.group === "open") {
18123
+ groups.push({ dir, stack: [...stack] });
18124
+ continue;
17961
18125
  }
17962
- case "dd":
17963
- return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
17964
- case "sed":
17965
- case "perl": {
17966
- if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
17967
- return [];
17968
- const scripted = args.some((item) => item === "-e" || item === "-f");
17969
- const files = [];
17970
- for (let index = 0;index < args.length; index++) {
17971
- if (args[index] === "-e" || args[index] === "-f")
17972
- index++;
17973
- else if (!args[index].startsWith("-"))
17974
- files.push(args[index]);
17975
- }
17976
- return scripted ? files : files.slice(1);
18126
+ if (segment.group === "close") {
18127
+ const saved = groups.pop();
18128
+ if (saved)
18129
+ ({ dir, stack } = saved);
18130
+ continue;
17977
18131
  }
17978
- case "patch":
17979
- return null;
17980
- case "set-content":
17981
- case "add-content":
17982
- case "out-file":
17983
- case "new-item":
17984
- case "ni":
17985
- case "remove-item":
17986
- case "del":
17987
- case "copy-item":
17988
- case "move-item": {
17989
- const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
17990
- if (flag >= 0)
17991
- return args[flag + 1] ? [args[flag + 1]] : null;
17992
- const files = operands(args);
17993
- if (name === "move-item")
17994
- return files.length ? files : null;
17995
- const file = name === "copy-item" ? files.at(-1) : files[0];
17996
- return file ? [file] : null;
18132
+ const words = stripPrefixes(segment.words);
18133
+ const name = commandName(words[0]);
18134
+ if (CD.has(name)) {
18135
+ const target = cdTarget(words);
18136
+ dir = target === null ? os4.homedir() : joinDir(dir, target);
18137
+ continue;
17997
18138
  }
17998
- case "git": {
17999
- const sub = operands(args)[0];
18000
- if (sub === "apply")
18001
- return null;
18002
- if (sub === "restore" || sub === "mv" || sub === "rm")
18003
- return operands(args).slice(1);
18004
- if (sub === "checkout" && args.includes("--"))
18005
- return args.slice(args.indexOf("--") + 1);
18006
- return [];
18007
- }
18008
- default:
18009
- return [];
18010
- }
18011
- };
18012
- var shellWrites = (command) => {
18013
- const targets = [];
18014
- let unknown = false;
18015
- for (const segment of segmentsOf(command)) {
18016
- targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
18017
- const edits = editsOf(segment.words);
18018
- if (edits === null)
18019
- unknown = true;
18020
- else
18021
- targets.push(...edits);
18022
- }
18023
- if (!unknown && targets.length === 0)
18024
- return { writes: false, targets: null };
18025
- return { writes: true, targets: unknown ? null : targets };
18026
- };
18027
- var cache2 = new Map;
18028
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path18.resolve(root, file)) ?? "-").join("\x00");
18029
- var gateFor = (cwd) => {
18030
- const store = new TaskStore(cwd);
18031
- const listed = store.listTaskIndex();
18032
- if (!listed.ok)
18033
- return null;
18034
- const key = store.currentKey();
18035
- if (!key.ok)
18036
- return null;
18037
- const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
18038
- if (!entry)
18039
- return { blockers: [], root: store.root, plans: new Set };
18040
- const ledger = ledgerPath(store.root);
18041
- const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
18042
- const hit = cache2.get(store.root);
18043
- if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
18044
- return hit;
18045
- const task = store.readTask(entry.id);
18046
- if (!task.ok)
18047
- return null;
18048
- const workspace = store.readWorkspace();
18049
- const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
18050
- const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
18051
- const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
18052
- return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
18053
- });
18054
- const gate = {
18055
- blockers,
18056
- root: store.root,
18057
- plans,
18058
- key: cacheKey,
18059
- planKey: planKeyOf(store.root, plans)
18060
- };
18061
- cache2.set(store.root, gate);
18062
- if (cache2.size > 32)
18063
- cache2.delete(cache2.keys().next().value);
18064
- return gate;
18065
- };
18066
- function writeGate(cwd, paths) {
18067
- try {
18068
- const gate = gateFor(cwd);
18069
- if (!gate || gate.blockers.length === 0)
18070
- return NONE2;
18071
- if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
18072
- return NONE2;
18073
- const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
18074
- return {
18075
- kind: "deny",
18076
- reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
18077
- unblock: gate.blockers[0].reason
18078
- };
18079
- } catch {
18080
- return NONE2;
18081
- }
18082
- }
18083
- // packages/workit-core/src/hooks/stop.ts
18084
- import { spawnSync as spawnSync12 } from "node:child_process";
18085
-
18086
- // packages/workit-core/src/hooks/raw-git.ts
18087
- import { spawnSync as spawnSync11 } from "node:child_process";
18088
- import { createHash as createHash14 } from "node:crypto";
18089
- import fs16 from "node:fs";
18090
- import os4 from "node:os";
18091
- import path20 from "node:path";
18092
-
18093
- // packages/workit-core/src/hooks/session-marker.ts
18094
- import { createHash as createHash13 } from "node:crypto";
18095
- import fs15 from "node:fs";
18096
- import path19 from "node:path";
18097
- var sessionMarkerFile = (input, session, store) => {
18098
- let dir = store;
18099
- if (dir === undefined) {
18100
- const location = resolveStore(input.cwd);
18101
- if (location instanceof Error || !location.shared)
18102
- return null;
18103
- dir = location.dir;
18104
- }
18105
- const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
18106
- return path19.join(dir, "hooks", `skills-${name}.json`);
18107
- };
18108
- var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
18109
- var readSessionMarker = (file) => {
18110
- try {
18111
- const value = JSON.parse(fs15.readFileSync(file, "utf8"));
18112
- return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
18113
- } catch {
18114
- return { nudged: [], loaded: [], raw: [] };
18115
- }
18116
- };
18117
- var writeSessionMarker = (file, marker) => {
18118
- try {
18119
- fs15.mkdirSync(path19.dirname(file), { recursive: true });
18120
- fs15.writeFileSync(file, JSON.stringify(marker));
18121
- } catch {}
18122
- };
18123
-
18124
- // packages/workit-core/src/hooks/raw-git.ts
18125
- var NONE3 = { kind: "none" };
18126
- var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
18127
- var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
18128
- var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
18129
- var CD = new Set(["cd", "chdir", "set-location", "sl"]);
18130
- var PUSHD = new Set(["pushd", "push-location"]);
18131
- var POPD = new Set(["popd", "pop-location"]);
18132
- var GIT_VALUE_OPTIONS = new Set([
18133
- "-c",
18134
- "--git-dir",
18135
- "--work-tree",
18136
- "--namespace",
18137
- "--exec-path",
18138
- "--config-env",
18139
- "--super-prefix"
18140
- ]);
18141
- var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
18142
- var joinDir = (base, next) => {
18143
- if (base === undefined || /[$`*?]/.test(next) || next === "-")
18144
- return;
18145
- const expanded = next === "~" || next.startsWith("~/") ? path20.join(os4.homedir(), next.slice(1)) : next;
18146
- return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
18147
- };
18148
- var cdTarget = (words) => {
18149
- const args = words.slice(1);
18150
- const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
18151
- if (flag >= 0)
18152
- return args[flag + 1] ?? null;
18153
- return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
18154
- };
18155
- var stripPrefixes = (input) => {
18156
- let words = input;
18157
- for (;; ) {
18158
- const first = words[0];
18159
- if (first === undefined)
18160
- return words;
18161
- if (/^\w+=/.test(first))
18162
- words = words.slice(1);
18163
- else if (PREFIXES3.has(first)) {
18164
- words = words.slice(1);
18165
- while (words[0]?.startsWith("-"))
18166
- words = words.slice(words[0] === "-u" ? 2 : 1);
18167
- } else if (first === "timeout") {
18168
- words = words.slice(1);
18169
- while (words[0]?.startsWith("-"))
18170
- words = words.slice(1);
18171
- words = words.slice(1);
18172
- } else
18173
- return words;
18174
- }
18175
- };
18176
- var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
18177
- function rawInvocations(command, dialect = "posix", depth = 0) {
18178
- const found = [];
18179
- let dir = null;
18180
- const groups = [];
18181
- let stack = [];
18182
- for (const segment of segmentsOf(command, dialect)) {
18183
- if (segment.group === "open") {
18184
- groups.push({ dir, stack: [...stack] });
18185
- continue;
18186
- }
18187
- if (segment.group === "close") {
18188
- const saved = groups.pop();
18189
- if (saved)
18190
- ({ dir, stack } = saved);
18191
- continue;
18192
- }
18193
- const words = stripPrefixes(segment.words);
18194
- const name = commandName(words[0]);
18195
- if (CD.has(name)) {
18196
- const target = cdTarget(words);
18197
- dir = target === null ? os4.homedir() : joinDir(dir, target);
18198
- continue;
18199
- }
18200
- if (PUSHD.has(name)) {
18201
- const target = cdTarget(words);
18202
- stack.push(dir);
18203
- dir = target === null || target === "-" ? undefined : joinDir(dir, target);
18204
- continue;
18139
+ if (PUSHD.has(name)) {
18140
+ const target = cdTarget(words);
18141
+ stack.push(dir);
18142
+ dir = target === null || target === "-" ? undefined : joinDir(dir, target);
18143
+ continue;
18205
18144
  }
18206
18145
  if (POPD.has(name)) {
18207
18146
  dir = stack.length ? stack.pop() : undefined;
@@ -18275,6 +18214,10 @@ var pushAction = (args) => {
18275
18214
  let force = false;
18276
18215
  let lease = false;
18277
18216
  let wholesale = false;
18217
+ let allTags = false;
18218
+ let deletes = false;
18219
+ let allBranches = false;
18220
+ let mirror = false;
18278
18221
  let remote = null;
18279
18222
  const positionals = [];
18280
18223
  for (let index = 1;index < args.length; index++) {
@@ -18295,14 +18238,22 @@ var pushAction = (args) => {
18295
18238
  force = true;
18296
18239
  else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
18297
18240
  lease = true;
18298
- else if (["--all", "--mirror", "--branches", "--tags"].includes(arg))
18299
- wholesale = true;
18241
+ else if (arg === "--delete" || arg === "-d")
18242
+ deletes = true;
18243
+ else if (arg === "--tags")
18244
+ allTags = wholesale = true;
18245
+ else if (arg === "--all" || arg === "--branches")
18246
+ allBranches = wholesale = true;
18247
+ else if (arg === "--mirror")
18248
+ mirror = wholesale = true;
18300
18249
  else if (!arg.startsWith("-"))
18301
18250
  positionals.push(arg);
18302
18251
  }
18303
18252
  const specs = remote === null ? positionals.slice(1) : positionals;
18304
18253
  remote ??= positionals[0] ?? null;
18305
18254
  const targets = [];
18255
+ const tags = [];
18256
+ const deleted = [];
18306
18257
  for (const raw of specs) {
18307
18258
  if (raw.startsWith("+"))
18308
18259
  force = true;
@@ -18311,16 +18262,29 @@ var pushAction = (args) => {
18311
18262
  const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
18312
18263
  if (!destination)
18313
18264
  continue;
18314
- if (destination === "HEAD")
18315
- targets.push(null);
18316
- else if (destination.startsWith("refs/heads/"))
18317
- targets.push(destination.slice(11));
18318
- else if (!destination.startsWith("refs/"))
18319
- targets.push(destination);
18265
+ const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
18266
+ if (destination.startsWith("refs/tags/"))
18267
+ tags.push(destination.slice(10));
18268
+ if (name === undefined)
18269
+ continue;
18270
+ targets.push(name);
18271
+ if (colon === 0 && name !== null)
18272
+ deleted.push(name);
18320
18273
  }
18321
18274
  if (specs.length === 0 && !wholesale)
18322
18275
  targets.push(null);
18323
- return { kind: "push", targets, blindForce: force && !lease, remote };
18276
+ return {
18277
+ kind: "push",
18278
+ targets,
18279
+ blindForce: force && !lease,
18280
+ remote,
18281
+ ...tags.length ? { tags } : {},
18282
+ ...allTags ? { allTags: true } : {},
18283
+ ...deletes ? { deletes: true } : {},
18284
+ ...deleted.length ? { deleted } : {},
18285
+ ...allBranches ? { allBranches: true } : {},
18286
+ ...mirror ? { mirror: true } : {}
18287
+ };
18324
18288
  };
18325
18289
  var VALUE_OPTIONS = {
18326
18290
  git: new Set([
@@ -18427,28 +18391,80 @@ var headSha2 = (repo) => {
18427
18391
  if (!git || git.reftable)
18428
18392
  return null;
18429
18393
  try {
18430
- const head = fs16.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
18394
+ const head = fs16.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
18431
18395
  if (/^[0-9a-f]{40,64}$/.test(head))
18432
18396
  return head;
18433
18397
  const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
18434
18398
  if (!ref)
18435
18399
  return null;
18436
- const common = path20.dirname(repo.location.dir);
18400
+ const common = path19.dirname(repo.location.dir);
18437
18401
  try {
18438
- return fs16.readFileSync(path20.join(common, ref), "utf8").trim() || null;
18402
+ return fs16.readFileSync(path19.join(common, ref), "utf8").trim() || null;
18439
18403
  } catch {}
18440
- const packed = fs16.readFileSync(path20.join(common, "packed-refs"), "utf8");
18404
+ const packed = fs16.readFileSync(path19.join(common, "packed-refs"), "utf8");
18441
18405
  return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
18442
18406
  } catch {
18443
18407
  return null;
18444
18408
  }
18445
18409
  };
18446
18410
  var quote = (text) => `\`${text}\``;
18447
- var refusal = (action, branch) => {
18411
+ var refusal = (action, branch, dir) => {
18448
18412
  if (action.kind === "merge")
18449
18413
  return `workit: raw ${quote(action.forge === "gh" ? "gh pr merge" : "glab mr merge")} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
18414
+ const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
18415
+ if (dir && branch && plainPush && releaseBranch(dir, branch))
18416
+ return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release v<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
18450
18417
  return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
18451
18418
  };
18419
+ var refFacts = (repo, remote) => {
18420
+ const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
18421
+ const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
18422
+ const remotePrefix = `refs/remotes/${remote}/`;
18423
+ for (const ref of (listed.stdout ?? "").split(`
18424
+ `)) {
18425
+ if (ref.startsWith("refs/heads/"))
18426
+ facts.heads.add(ref.slice(11));
18427
+ else if (ref.startsWith("refs/tags/"))
18428
+ facts.tags.add(ref.slice(10));
18429
+ else if (ref.startsWith(remotePrefix))
18430
+ facts.remoteHeads.add(ref.slice(remotePrefix.length));
18431
+ }
18432
+ return facts;
18433
+ };
18434
+ var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
18435
+ var tagRefusal = (action, facts) => {
18436
+ const tags = [
18437
+ ...action.tags ?? [],
18438
+ ...action.targets.filter((name) => name !== null && isTag(facts, name))
18439
+ ];
18440
+ if (!tags.length && !action.allTags)
18441
+ return null;
18442
+ const named = tags.length ? tags.join(" --release ") : "v<version>";
18443
+ return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (v<version> tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
18444
+ };
18445
+ var remoteHas = (repo, remote, branch) => {
18446
+ const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
18447
+ cwd: repo.dir,
18448
+ encoding: "utf8",
18449
+ timeout: 3000,
18450
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
18451
+ });
18452
+ return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
18453
+ };
18454
+ var offPolicyPush = (action, repo, facts, remote) => {
18455
+ const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
18456
+ for (const branch of names) {
18457
+ if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
18458
+ continue;
18459
+ if (facts.remoteHeads.has(branch))
18460
+ continue;
18461
+ const check = validateBranchNameFor(repo.dir, branch);
18462
+ if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
18463
+ continue;
18464
+ return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
18465
+ }
18466
+ return null;
18467
+ };
18452
18468
  var nudgeFor = (action, host, session) => {
18453
18469
  switch (action.kind) {
18454
18470
  case "commit": {
@@ -18474,7 +18490,7 @@ var actionsIn = (cwd, command, dialect) => {
18474
18490
  const action = rawAction(invocation);
18475
18491
  if (!action)
18476
18492
  continue;
18477
- const dir = path20.resolve(cwd, invocation.dir ?? ".");
18493
+ const dir = path19.resolve(cwd, invocation.dir ?? ".");
18478
18494
  if (!repos.has(dir)) {
18479
18495
  const repo = repoAt(dir);
18480
18496
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
@@ -18511,7 +18527,7 @@ var unseenNudges = (input, nudges, store) => {
18511
18527
  function rawGitPre(input, command, options = {}) {
18512
18528
  try {
18513
18529
  if (!mentionsRawTool(command))
18514
- return NONE3;
18530
+ return NONE2;
18515
18531
  const dialect = dialectOf(input);
18516
18532
  const nudges = new Map;
18517
18533
  let store = null;
@@ -18519,31 +18535,42 @@ function rawGitPre(input, command, options = {}) {
18519
18535
  store ??= repo.location.dir;
18520
18536
  if (action.kind === "merge")
18521
18537
  return deny(refusal(action, null));
18522
- if (action.kind === "push")
18538
+ if (action.kind === "push" && toPushRemote(repo, action.remote)) {
18539
+ if (action.mirror)
18540
+ return deny(`workit: raw ${quote("git push --mirror")} rewrites and deletes every remote ref, protected branches and tags included. Push one branch with ${quote("workit git push")}.`);
18523
18541
  for (const target of action.targets) {
18524
18542
  const branch = target ?? repo.branch;
18525
- if (branch && guardedBranch(repo.dir, branch) && toPushRemote(repo, action.remote))
18526
- return deny(refusal(action, branch));
18543
+ if (branch && guardedBranch(repo.dir, branch))
18544
+ return deny(refusal(action, branch, repo.dir));
18527
18545
  }
18546
+ const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
18547
+ const facts = refFacts(repo, remote);
18548
+ const tagDeny = tagRefusal(action, facts);
18549
+ if (tagDeny)
18550
+ return deny(tagDeny);
18551
+ const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
18552
+ if (nameDeny)
18553
+ return deny(nameDeny);
18554
+ }
18528
18555
  const nudge = nudgeFor(action, input.host, input.session.id);
18529
18556
  if (nudge && !nudges.has(nudgeKind(action)))
18530
18557
  nudges.set(nudgeKind(action), nudge);
18531
18558
  }
18532
18559
  if (options.nudge === false || !store || nudges.size === 0)
18533
- return NONE3;
18560
+ return NONE2;
18534
18561
  if (workitInvocations(command, dialect).length > 0)
18535
- return NONE3;
18562
+ return NONE2;
18536
18563
  const shown = unseenNudges(input, nudges, store);
18537
18564
  return shown.length ? { kind: "context", text: shown.join(`
18538
- `) } : NONE3;
18565
+ `) } : NONE2;
18539
18566
  } catch {
18540
- return NONE3;
18567
+ return NONE2;
18541
18568
  }
18542
18569
  }
18543
18570
  var deny = (reason) => ({ kind: "deny", reason, unblock: null });
18544
18571
  var NEXT_COMMAND = "next";
18545
18572
  var MARKER_TTL_SECONDS = 3600;
18546
- var markerFile = (repo, session, key) => path20.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
18573
+ var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
18547
18574
  var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
18548
18575
  function noteRawCommit(input, command, key) {
18549
18576
  try {
@@ -18554,7 +18581,7 @@ function noteRawCommit(input, command, key) {
18554
18581
  if (action.kind !== "commit")
18555
18582
  continue;
18556
18583
  const file = markerFile(repo, input.session.id, key);
18557
- fs16.mkdirSync(path20.dirname(file), { recursive: true });
18584
+ fs16.mkdirSync(path19.dirname(file), { recursive: true });
18558
18585
  fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
18559
18586
  `);
18560
18587
  }
@@ -18636,7 +18663,292 @@ function settlePendingCommit(input) {
18636
18663
  } catch {}
18637
18664
  }
18638
18665
 
18666
+ // packages/workit-core/src/core/route-intent.ts
18667
+ var UNKNOWABLE = /[$`*?[\]{}~]/u;
18668
+ var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
18669
+ var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
18670
+ var WORKTREE_CREATE = new Set(["-b", "-B"]);
18671
+ var BRANCH_NO_CREATE = new Set(["-d", "-D", "--delete", "-l", "--list", "-a", "--all", "-r", "--remotes", "--show-current"].concat(["-v", "-vv", "--verbose", "--contains", "--no-contains", "--merged", "--no-merged"]).concat(["--points-at", "--sort", "--format", "--column", "--no-column", "-u"]).concat(["--set-upstream-to", "--unset-upstream", "--edit-description", "-i", "--ignore-case"]));
18672
+ var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
18673
+ var flagValue = (args, flags) => {
18674
+ for (let index = 1;index < args.length; index++) {
18675
+ const arg = args[index];
18676
+ if (arg === "--")
18677
+ return null;
18678
+ if (flags.has(arg))
18679
+ return args[index + 1] ?? null;
18680
+ const eq = arg.indexOf("=");
18681
+ if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
18682
+ return arg.slice(eq + 1);
18683
+ if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
18684
+ return arg.slice(2);
18685
+ }
18686
+ return null;
18687
+ };
18688
+ var WORKTREE_VALUES = new Set(["--reason"]);
18689
+ var worktreeTarget = (args) => {
18690
+ const named = flagValue(args, WORKTREE_CREATE);
18691
+ if (named !== null)
18692
+ return named;
18693
+ const positionals = [];
18694
+ for (let index = 1;index < args.length; index++) {
18695
+ const arg = args[index];
18696
+ if (arg === "--detach" || arg === "-d")
18697
+ return null;
18698
+ if (WORKTREE_VALUES.has(arg))
18699
+ index++;
18700
+ else if (!arg.startsWith("-"))
18701
+ positionals.push(arg);
18702
+ }
18703
+ if (positionals.length !== 1)
18704
+ return null;
18705
+ const base = positionals[0].split(/[\\/]/u).findLast(Boolean) ?? "";
18706
+ return base && base !== "." && base !== ".." ? base : null;
18707
+ };
18708
+ var branchCommandTarget = (args) => {
18709
+ let rename = false;
18710
+ const positionals = [];
18711
+ for (let index = 1;index < args.length; index++) {
18712
+ const arg = args[index];
18713
+ if (arg === "--") {
18714
+ positionals.push(...args.slice(index + 1));
18715
+ break;
18716
+ }
18717
+ const flag = arg.split("=", 1)[0];
18718
+ if (BRANCH_NO_CREATE.has(flag))
18719
+ return null;
18720
+ if (BRANCH_RENAME.has(flag))
18721
+ rename = true;
18722
+ else if (!arg.startsWith("-"))
18723
+ positionals.push(arg);
18724
+ }
18725
+ if (positionals.length < 1 || positionals.length > 2)
18726
+ return null;
18727
+ return rename ? positionals.at(-1) ?? null : positionals[0];
18728
+ };
18729
+ var gitBranchCreation = (args) => {
18730
+ const [sub, second] = args;
18731
+ const target = sub === "switch" ? flagValue(args, SWITCH_CREATE) : sub === "checkout" ? flagValue(args, CHECKOUT_CREATE) : sub === "branch" ? branchCommandTarget(args) : sub === "worktree" && second === "add" ? worktreeTarget(args.slice(1)) : null;
18732
+ return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
18733
+ };
18734
+ var shellBranchTargets = (command, dialect = "posix") => {
18735
+ if (!/\bgit\b/u.test(command))
18736
+ return [];
18737
+ const found = [];
18738
+ for (const invocation of rawInvocations(command, dialect)) {
18739
+ if (invocation.tool !== "git")
18740
+ continue;
18741
+ const branch = gitBranchCreation(invocation.args);
18742
+ if (branch)
18743
+ found.push({ branch, dir: invocation.dir });
18744
+ }
18745
+ return found;
18746
+ };
18747
+ var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
18748
+ cwd: dir,
18749
+ stdio: "ignore",
18750
+ timeout: 5000
18751
+ }).status === 0;
18752
+ var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
18753
+ let judged = false;
18754
+ for (const target of shellBranchTargets(command, dialect)) {
18755
+ if (target.dir === undefined)
18756
+ continue;
18757
+ const dir = target.dir === null ? root : path20.resolve(root, target.dir);
18758
+ if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
18759
+ continue;
18760
+ if (localBranchExists(dir, target.branch))
18761
+ continue;
18762
+ judged = true;
18763
+ const check = validateBranchNameFor(dir, target.branch);
18764
+ if (!check.ok)
18765
+ return check;
18766
+ }
18767
+ return judged ? { ok: true } : null;
18768
+ };
18769
+
18770
+ // packages/workit-core/src/hooks/policy.ts
18771
+ var shellPolicy = (cwd, command, dialect = "posix") => {
18772
+ const policy = shellBranchPolicyViolation(cwd, command, dialect);
18773
+ return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
18774
+ };
18775
+ // packages/workit-core/src/hooks/write-gate.ts
18776
+ import { realpathSync as realpathSync5 } from "node:fs";
18777
+ import path21 from "node:path";
18778
+ var NONE3 = { kind: "none" };
18779
+ var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
18780
+ var canonical2 = (file) => {
18781
+ let rest = "";
18782
+ for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
18783
+ try {
18784
+ return path21.join(realpathSync5(dir), rest);
18785
+ } catch {
18786
+ if (path21.dirname(dir) === dir)
18787
+ return path21.resolve(file);
18788
+ rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
18789
+ }
18790
+ }
18791
+ };
18792
+ var exempt = (cwd, root, file, plans) => {
18793
+ const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
18794
+ const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
18795
+ if (relative.startsWith("..") || path21.isAbsolute(relative))
18796
+ return true;
18797
+ const posix = relative.split(path21.sep).join("/");
18798
+ const segments = posix.split("/");
18799
+ return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
18800
+ };
18801
+ var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
18802
+ var operands = (words) => words.filter((item) => !item.startsWith("-"));
18803
+ var editsOf = (input) => {
18804
+ let words = input;
18805
+ while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
18806
+ words = words.slice(1);
18807
+ const [verb, ...args] = words;
18808
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
18809
+ if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
18810
+ return [];
18811
+ switch (name) {
18812
+ case "touch":
18813
+ case "mkdir":
18814
+ case "rm":
18815
+ case "rmdir":
18816
+ case "tee":
18817
+ case "mv":
18818
+ case "truncate":
18819
+ return operands(args);
18820
+ case "cp":
18821
+ case "ln":
18822
+ case "install": {
18823
+ const target = args.indexOf("-t");
18824
+ if (target >= 0)
18825
+ return args[target + 1] ? [args[target + 1]] : null;
18826
+ const last = operands(args).at(-1);
18827
+ return last ? [last] : null;
18828
+ }
18829
+ case "dd":
18830
+ return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
18831
+ case "sed":
18832
+ case "perl": {
18833
+ if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
18834
+ return [];
18835
+ const scripted = args.some((item) => item === "-e" || item === "-f");
18836
+ const files = [];
18837
+ for (let index = 0;index < args.length; index++) {
18838
+ if (args[index] === "-e" || args[index] === "-f")
18839
+ index++;
18840
+ else if (!args[index].startsWith("-"))
18841
+ files.push(args[index]);
18842
+ }
18843
+ return scripted ? files : files.slice(1);
18844
+ }
18845
+ case "patch":
18846
+ return null;
18847
+ case "set-content":
18848
+ case "add-content":
18849
+ case "out-file":
18850
+ case "new-item":
18851
+ case "ni":
18852
+ case "remove-item":
18853
+ case "del":
18854
+ case "copy-item":
18855
+ case "move-item": {
18856
+ const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
18857
+ if (flag >= 0)
18858
+ return args[flag + 1] ? [args[flag + 1]] : null;
18859
+ const files = operands(args);
18860
+ if (name === "move-item")
18861
+ return files.length ? files : null;
18862
+ const file = name === "copy-item" ? files.at(-1) : files[0];
18863
+ return file ? [file] : null;
18864
+ }
18865
+ case "git": {
18866
+ const sub = operands(args)[0];
18867
+ if (sub === "apply")
18868
+ return null;
18869
+ if (sub === "restore" || sub === "mv" || sub === "rm")
18870
+ return operands(args).slice(1);
18871
+ if (sub === "checkout" && args.includes("--"))
18872
+ return args.slice(args.indexOf("--") + 1);
18873
+ return [];
18874
+ }
18875
+ default:
18876
+ return [];
18877
+ }
18878
+ };
18879
+ var shellWrites = (command) => {
18880
+ const targets = [];
18881
+ let unknown = false;
18882
+ for (const segment of segmentsOf(command)) {
18883
+ targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
18884
+ const edits = editsOf(segment.words);
18885
+ if (edits === null)
18886
+ unknown = true;
18887
+ else
18888
+ targets.push(...edits);
18889
+ }
18890
+ if (!unknown && targets.length === 0)
18891
+ return { writes: false, targets: null };
18892
+ return { writes: true, targets: unknown ? null : targets };
18893
+ };
18894
+ var cache2 = new Map;
18895
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
18896
+ var gateFor = (cwd) => {
18897
+ const store = new TaskStore(cwd);
18898
+ const listed = store.listTaskIndex();
18899
+ if (!listed.ok)
18900
+ return null;
18901
+ const key = store.currentKey();
18902
+ if (!key.ok)
18903
+ return null;
18904
+ const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
18905
+ if (!entry)
18906
+ return { blockers: [], root: store.root, plans: new Set };
18907
+ const ledger = ledgerPath(store.root);
18908
+ const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
18909
+ const hit = cache2.get(store.root);
18910
+ if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
18911
+ return hit;
18912
+ const task = store.readTask(entry.id);
18913
+ if (!task.ok)
18914
+ return null;
18915
+ const workspace = store.readWorkspace();
18916
+ const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
18917
+ const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
18918
+ const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
18919
+ return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
18920
+ });
18921
+ const gate = {
18922
+ blockers,
18923
+ root: store.root,
18924
+ plans,
18925
+ key: cacheKey,
18926
+ planKey: planKeyOf(store.root, plans)
18927
+ };
18928
+ cache2.set(store.root, gate);
18929
+ if (cache2.size > 32)
18930
+ cache2.delete(cache2.keys().next().value);
18931
+ return gate;
18932
+ };
18933
+ function writeGate(cwd, paths) {
18934
+ try {
18935
+ const gate = gateFor(cwd);
18936
+ if (!gate || gate.blockers.length === 0)
18937
+ return NONE3;
18938
+ if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
18939
+ return NONE3;
18940
+ const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
18941
+ return {
18942
+ kind: "deny",
18943
+ reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
18944
+ unblock: gate.blockers[0].reason
18945
+ };
18946
+ } catch {
18947
+ return NONE3;
18948
+ }
18949
+ }
18639
18950
  // packages/workit-core/src/hooks/stop.ts
18951
+ import { spawnSync as spawnSync13 } from "node:child_process";
18640
18952
  var NONE4 = { kind: "none" };
18641
18953
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
18642
18954
  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;
@@ -18657,7 +18969,7 @@ var asksUser = (message) => {
18657
18969
  };
18658
18970
  var short = (sha) => (sha ?? "?").slice(0, 12);
18659
18971
  var gitList = (cwd, args) => {
18660
- const run = spawnSync12("git", args, {
18972
+ const run = spawnSync13("git", args, {
18661
18973
  cwd,
18662
18974
  encoding: "utf8",
18663
18975
  timeout: 3000,
@@ -19020,13 +19332,13 @@ ${line}` };
19020
19332
  return decision;
19021
19333
  };
19022
19334
  // packages/workit-core/src/hooks/hosts/fields.ts
19023
- import { existsSync as existsSync6, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
19024
- import path21 from "node:path";
19335
+ import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
19336
+ import path22 from "node:path";
19025
19337
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
19026
19338
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
19027
19339
  var optionalText = (value) => typeof value === "string" ? value : null;
19028
19340
  var existingDirectory = (value) => {
19029
- if (!nonEmpty2(value) || !path21.isAbsolute(value) || !existsSync6(value))
19341
+ if (!nonEmpty2(value) || !path22.isAbsolute(value) || !existsSync7(value))
19030
19342
  return null;
19031
19343
  try {
19032
19344
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -19109,19 +19421,20 @@ var workitRole = (descriptor, agentType) => {
19109
19421
  };
19110
19422
  var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
19111
19423
  var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
19112
- var ISOLATED_GIT = 'Run git as plain, separate commands inside your worktree (`git switch -c <branch> <base>`, `git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a commit-msg hook that adds the trailer), 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`).';
19424
+ 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\`).`;
19113
19425
  var subagentStartText = (input, descriptor, event) => {
19114
19426
  const session = input.session.id ?? null;
19115
19427
  const role = workitRole(descriptor, event.agentType);
19116
19428
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
19117
19429
  const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
19430
+ const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
19118
19431
  if (role === "implementer" && isolated)
19119
- return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} Use a policy-compliant branch (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
19432
+ 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.`;
19120
19433
  if (isolated)
19121
- return `${ISOLATED_GIT}
19434
+ return `${isolatedGit(input.cwd)}
19122
19435
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
19123
19436
  if (role === "implementer")
19124
- return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
19437
+ 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.`;
19125
19438
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
19126
19439
  if (role !== "judge" || !event.agentId)
19127
19440
  return readOnly;
@@ -19161,7 +19474,7 @@ function handleHook(input, deps) {
19161
19474
  if (skillRead)
19162
19475
  recordSkillLoad(input, skillRead, "read");
19163
19476
  const canDeny = usable2(descriptor.shellPolicy.deny);
19164
- const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
19477
+ const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
19165
19478
  if (policy.kind !== "none")
19166
19479
  return policy;
19167
19480
  const postTool = usable2(descriptor.events["shell.post"].support);
@@ -19764,7 +20077,7 @@ var codexAdapter = {
19764
20077
 
19765
20078
  // packages/workit-core/src/hooks/hosts/cursor.ts
19766
20079
  import fs17 from "node:fs";
19767
- import path22 from "node:path";
20080
+ import path23 from "node:path";
19768
20081
  var undocumented = { support: "undocumented", native: null };
19769
20082
  var none = { support: "none", native: null };
19770
20083
  var CURSOR_DESCRIPTOR = {
@@ -19961,7 +20274,7 @@ var protocolEvent2 = (input) => {
19961
20274
  };
19962
20275
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
19963
20276
  var lastAssistantText = (file) => {
19964
- if (!file || !path22.isAbsolute(file))
20277
+ if (!file || !path23.isAbsolute(file))
19965
20278
  return null;
19966
20279
  try {
19967
20280
  const size = fs17.statSync(file).size;
@@ -19998,7 +20311,7 @@ var lastAssistantText = (file) => {
19998
20311
  return null;
19999
20312
  }
20000
20313
  };
20001
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path22.sep) ? root : `${root}${path22.sep}`);
20314
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path23.sep) ? root : `${root}${path23.sep}`);
20002
20315
  var hookCwd = (input) => {
20003
20316
  const cwd = existingDirectory(input.cwd);
20004
20317
  if (cwd && input.hook_event_name === "beforeShellExecution")