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