@brainervirus/workit-codex 8.10.0 → 8.11.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workit",
3
- "version": "8.10.0",
3
+ "version": "8.11.0",
4
4
  "description": "Adaptive engineering workflow rails for Codex",
5
5
  "author": {
6
6
  "name": "BrainerVirus"
@@ -22270,6 +22270,71 @@ var gitContext = (workspaceRoot, paths = []) => {
22270
22270
  ...failure ? { stderr: failure.stderr, exitCode: failure.exitCode } : {}
22271
22271
  };
22272
22272
  };
22273
+
22274
+ // packages/workit-core/src/hooks/raw-git.ts
22275
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
22276
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
22277
+ var CD = new Set(["cd", "chdir", "set-location", "sl"]);
22278
+ var PUSHD = new Set(["pushd", "push-location"]);
22279
+ var POPD = new Set(["popd", "pop-location"]);
22280
+ var GIT_VALUE_OPTIONS = new Set([
22281
+ "-c",
22282
+ "--git-dir",
22283
+ "--work-tree",
22284
+ "--namespace",
22285
+ "--exec-path",
22286
+ "--config-env",
22287
+ "--super-prefix"
22288
+ ]);
22289
+ var LAUNCHERS = new Set([
22290
+ "npx",
22291
+ "bunx",
22292
+ "pnpx",
22293
+ "-y",
22294
+ "--yes",
22295
+ "pnpm",
22296
+ "dlx",
22297
+ "exec",
22298
+ "env",
22299
+ "sudo",
22300
+ "command"
22301
+ ]);
22302
+ var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
22303
+ var VALUE_OPTIONS = {
22304
+ git: new Set([
22305
+ "-m",
22306
+ "--message",
22307
+ "-F",
22308
+ "--file",
22309
+ "-C",
22310
+ "-c",
22311
+ "--reuse-message",
22312
+ "--reedit-message",
22313
+ "--author",
22314
+ "--date",
22315
+ "--trailer",
22316
+ "-t",
22317
+ "--template",
22318
+ "--cleanup",
22319
+ "--fixup",
22320
+ "--squash",
22321
+ "-o",
22322
+ "--push-option",
22323
+ "--repo",
22324
+ "--receive-pack",
22325
+ "--exec"
22326
+ ]),
22327
+ gh: new Set(["-b", "--body", "-F", "--body-file", "-t", "--title", "--subject", "-A", "--author-email"].concat(["-B", "--base", "-H", "--head", "-l", "--label", "-a", "--assignee"]).concat(["-r", "--reviewer", "-m", "--milestone", "-p", "--project", "-T", "--template"]).concat(["--match-head-commit", "-R", "--repo"])),
22328
+ glab: new Set(["-m", "--message", "--squash-message", "--sha", "-t", "--title", "-d", "--description"].concat(["-b", "--target-branch", "-s", "--source-branch", "-l", "--label"]).concat(["-a", "--assignee", "--reviewer", "-R", "--repo"]))
22329
+ };
22330
+
22331
+ // packages/workit-core/src/core/route-intent.ts
22332
+ var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
22333
+ var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
22334
+ var WORKTREE_CREATE = new Set(["-b", "-B"]);
22335
+ 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"]));
22336
+ var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
22337
+ var WORKTREE_VALUES = new Set(["--reason"]);
22273
22338
  // packages/workit-core/src/core/task-engine.ts
22274
22339
  import { realpathSync as realpathSync4 } from "node:fs";
22275
22340
 
@@ -28101,64 +28166,8 @@ var cache = new Map;
28101
28166
  var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
28102
28167
  // packages/workit-core/src/hooks/write-gate.ts
28103
28168
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
28104
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
28169
+ var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
28105
28170
  var cache2 = new Map;
28106
- // packages/workit-core/src/hooks/raw-git.ts
28107
- var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
28108
- var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
28109
- var CD = new Set(["cd", "chdir", "set-location", "sl"]);
28110
- var PUSHD = new Set(["pushd", "push-location"]);
28111
- var POPD = new Set(["popd", "pop-location"]);
28112
- var GIT_VALUE_OPTIONS = new Set([
28113
- "-c",
28114
- "--git-dir",
28115
- "--work-tree",
28116
- "--namespace",
28117
- "--exec-path",
28118
- "--config-env",
28119
- "--super-prefix"
28120
- ]);
28121
- var LAUNCHERS = new Set([
28122
- "npx",
28123
- "bunx",
28124
- "pnpx",
28125
- "-y",
28126
- "--yes",
28127
- "pnpm",
28128
- "dlx",
28129
- "exec",
28130
- "env",
28131
- "sudo",
28132
- "command"
28133
- ]);
28134
- var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
28135
- var VALUE_OPTIONS = {
28136
- git: new Set([
28137
- "-m",
28138
- "--message",
28139
- "-F",
28140
- "--file",
28141
- "-C",
28142
- "-c",
28143
- "--reuse-message",
28144
- "--reedit-message",
28145
- "--author",
28146
- "--date",
28147
- "--trailer",
28148
- "-t",
28149
- "--template",
28150
- "--cleanup",
28151
- "--fixup",
28152
- "--squash",
28153
- "-o",
28154
- "--push-option",
28155
- "--repo",
28156
- "--receive-pack",
28157
- "--exec"
28158
- ]),
28159
- gh: new Set(["-b", "--body", "-F", "--body-file", "-t", "--title", "--subject", "-A", "--author-email"].concat(["-B", "--base", "-H", "--head", "-l", "--label", "-a", "--assignee"]).concat(["-r", "--reviewer", "-m", "--milestone", "-p", "--project", "-T", "--template"]).concat(["--match-head-commit", "-R", "--repo"])),
28160
- glab: new Set(["-m", "--message", "--squash-message", "--sha", "-t", "--title", "-d", "--description"].concat(["-b", "--target-branch", "-s", "--source-branch", "-l", "--label"]).concat(["-a", "--assignee", "--reviewer", "-R", "--repo"]))
28161
- };
28162
28171
  // packages/workit-core/src/core/skill-manifests.ts
28163
28172
  if (false) {}
28164
28173
 
@@ -12377,6 +12377,7 @@ var branchNames = (workspaceRoot) => {
12377
12377
  return new Set((r.stdout ?? "").split(`
12378
12378
  `).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
12379
12379
  };
12380
+ var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
12380
12381
  var detectBranchPolicy = (workspaceRoot) => {
12381
12382
  const names = branchNames(workspaceRoot);
12382
12383
  const develop = names.has("develop");
@@ -17664,6 +17665,18 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
17664
17665
  else
17665
17666
  clearTurnDigest(file);
17666
17667
  };
17668
+ // packages/workit-core/src/core/route-intent.ts
17669
+ import { spawnSync as spawnSync12 } from "node:child_process";
17670
+ import { existsSync as existsSync6 } from "node:fs";
17671
+ import path20 from "node:path";
17672
+
17673
+ // packages/workit-core/src/hooks/raw-git.ts
17674
+ import { spawnSync as spawnSync11 } from "node:child_process";
17675
+ import { createHash as createHash14 } from "node:crypto";
17676
+ import fs16 from "node:fs";
17677
+ import os4 from "node:os";
17678
+ import path19 from "node:path";
17679
+
17667
17680
  // packages/workit-core/src/core/branch.ts
17668
17681
  import { createHash as createHash12 } from "node:crypto";
17669
17682
  var withReleaseTracks = (workspaceRoot, policy) => {
@@ -17701,10 +17714,72 @@ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
17701
17714
  provenance: { commitPolicy: selected.source }
17702
17715
  };
17703
17716
  }
17717
+ var KIND_SYNONYMS = {
17718
+ fix: "bugfix",
17719
+ fixes: "bugfix",
17720
+ bug: "bugfix",
17721
+ bugs: "bugfix",
17722
+ bugfixes: "bugfix",
17723
+ feat: "feature",
17724
+ features: "feature",
17725
+ chore: "feature",
17726
+ docs: "feature",
17727
+ doc: "feature",
17728
+ refactor: "feature",
17729
+ test: "feature",
17730
+ tests: "feature",
17731
+ ci: "feature",
17732
+ build: "feature",
17733
+ perf: "feature",
17734
+ style: "feature",
17735
+ deps: "feature",
17736
+ task: "feature",
17737
+ enhancement: "feature",
17738
+ hot: "hotfix",
17739
+ hotfixes: "hotfix",
17740
+ releases: "release",
17741
+ rel: "release"
17742
+ };
17743
+ var allowedKinds = (allowed) => [
17744
+ ...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
17745
+ ];
17746
+ var editDistance = (a, b) => {
17747
+ const row = Array.from({ length: b.length + 1 }, (_, index) => index);
17748
+ for (let i = 1;i <= a.length; i++) {
17749
+ let previous = row[0];
17750
+ row[0] = i;
17751
+ for (let j = 1;j <= b.length; j++) {
17752
+ const saved = row[j];
17753
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
17754
+ previous = saved;
17755
+ }
17756
+ }
17757
+ return row[b.length];
17758
+ };
17759
+ var suggestBranchName = (name, kinds) => {
17760
+ if (!kinds.length)
17761
+ return null;
17762
+ const slash = name.indexOf("/");
17763
+ const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
17764
+ const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
17765
+ const synonym = KIND_SYNONYMS[prefix];
17766
+ 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;
17767
+ const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
17768
+ return `${kind}/${rest}`;
17769
+ };
17770
+ var kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", ");
17771
+ var aliasNote = (kinds) => {
17772
+ const notes = [
17773
+ ["fix", "bugfix"],
17774
+ ["chore", "feature"]
17775
+ ].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
17776
+ return notes.length ? ` (${notes.join(", ")})` : "";
17777
+ };
17704
17778
  var validateBranchNamePolicy = (name, policy, source) => {
17705
17779
  const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
17780
+ const kinds = allowedKinds(policy.allowed);
17706
17781
  if (policy.protected.has(name.toLowerCase())) {
17707
- const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
17782
+ 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
17783
  return {
17709
17784
  ok: false,
17710
17785
  rule: "protected_ref",
@@ -17715,7 +17790,8 @@ var validateBranchNamePolicy = (name, policy, source) => {
17715
17790
  };
17716
17791
  }
17717
17792
  if (!policy.allowed.some((pattern) => pattern.test(name))) {
17718
- const correction = `choose a branch matching one of: ${allowed.join(", ")}`;
17793
+ const suggestion = suggestBranchName(name, kinds);
17794
+ 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
17795
  return {
17720
17796
  ok: false,
17721
17797
  rule: "allowed_pattern",
@@ -17758,31 +17834,87 @@ var validateBranchNameFor = (workspaceRoot, name) => {
17758
17834
  }
17759
17835
  };
17760
17836
  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")
17837
+ var gitFlowOf = (root, integration) => {
17838
+ try {
17839
+ if (resolveBranchPolicyFor(root).preset !== "gitflow")
17840
+ return null;
17841
+ const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
17842
+ const names = branchNames(root);
17843
+ const production = productionBranchOf(names);
17844
+ const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
17845
+ return { develop, production, developExists: names.has(develop) };
17846
+ } catch {
17768
17847
  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;
17848
+ }
17772
17849
  };
17773
- var shellBranchPolicyViolation = (root, command) => {
17774
- const target = shellBranchTarget(command);
17775
- return target === null ? null : validateBranchNameFor(root, target);
17850
+ var releaseBranchesOf = (cwd) => {
17851
+ try {
17852
+ const resolved = resolveBranchPolicyFor(cwd);
17853
+ const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
17854
+ return {
17855
+ preset: resolved.preset,
17856
+ branches: flow ? [flow.production, flow.develop] : [],
17857
+ production: flow ? flow.production : resolved.defaultTargetBranch
17858
+ };
17859
+ } catch {
17860
+ return { preset: "unknown", branches: [], production: null };
17861
+ }
17776
17862
  };
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" };
17863
+ var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
17864
+ var branchGuidance = (cwd) => {
17865
+ try {
17866
+ const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
17867
+ const kinds = allowedKinds(patterns);
17868
+ const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
17869
+ const anyName = patterns.some((pattern) => pattern.source === "^.*$");
17870
+ const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
17871
+ 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.`;
17872
+ const resolved = vcsConfig("resolve", cwd);
17873
+ const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
17874
+ const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
17875
+ if (flow?.developExists)
17876
+ 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}.`;
17877
+ const names = branchNames(cwd);
17878
+ const configured = String(resolved.baseBranch ?? integration ?? "") || null;
17879
+ const trunk = productionBranchOf(names);
17880
+ 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.";
17881
+ return `${allowed} ${base}`;
17882
+ } catch {
17883
+ return null;
17884
+ }
17782
17885
  };
17783
- // packages/workit-core/src/hooks/write-gate.ts
17784
- import { realpathSync as realpathSync5 } from "node:fs";
17886
+ var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
17887
+
17888
+ // packages/workit-core/src/hooks/session-marker.ts
17889
+ import { createHash as createHash13 } from "node:crypto";
17890
+ import fs15 from "node:fs";
17785
17891
  import path18 from "node:path";
17892
+ var sessionMarkerFile = (input, session, store) => {
17893
+ let dir = store;
17894
+ if (dir === undefined) {
17895
+ const location = resolveStore(input.cwd);
17896
+ if (location instanceof Error || !location.shared)
17897
+ return null;
17898
+ dir = location.dir;
17899
+ }
17900
+ const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
17901
+ return path18.join(dir, "hooks", `skills-${name}.json`);
17902
+ };
17903
+ var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
17904
+ var readSessionMarker = (file) => {
17905
+ try {
17906
+ const value = JSON.parse(fs15.readFileSync(file, "utf8"));
17907
+ return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
17908
+ } catch {
17909
+ return { nudged: [], loaded: [], raw: [] };
17910
+ }
17911
+ };
17912
+ var writeSessionMarker = (file, marker) => {
17913
+ try {
17914
+ fs15.mkdirSync(path18.dirname(file), { recursive: true });
17915
+ fs15.writeFileSync(file, JSON.stringify(marker));
17916
+ } catch {}
17917
+ };
17786
17918
 
17787
17919
  // packages/workit-core/src/hooks/shell-words.ts
17788
17920
  var closingQuote = (command, start, dialect) => {
@@ -17907,224 +18039,10 @@ var segmentsOf = (command, dialect = "posix") => {
17907
18039
  return segments;
17908
18040
  };
17909
18041
 
17910
- // packages/workit-core/src/hooks/write-gate.ts
17911
- 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
- }
17924
- };
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);
17933
- };
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) => {
17937
- 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;
17961
- }
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);
17977
- }
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;
17997
- }
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
18042
  // 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" };
18043
+ var NONE2 = { kind: "none" };
18126
18044
  var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
18127
- var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
18045
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
18128
18046
  var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
18129
18047
  var CD = new Set(["cd", "chdir", "set-location", "sl"]);
18130
18048
  var PUSHD = new Set(["pushd", "push-location"]);
@@ -18138,12 +18056,12 @@ var GIT_VALUE_OPTIONS = new Set([
18138
18056
  "--config-env",
18139
18057
  "--super-prefix"
18140
18058
  ]);
18141
- var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
18059
+ var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
18142
18060
  var joinDir = (base, next) => {
18143
18061
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
18144
18062
  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);
18063
+ const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
18064
+ return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
18147
18065
  };
18148
18066
  var cdTarget = (words) => {
18149
18067
  const args = words.slice(1);
@@ -18160,7 +18078,7 @@ var stripPrefixes = (input) => {
18160
18078
  return words;
18161
18079
  if (/^\w+=/.test(first))
18162
18080
  words = words.slice(1);
18163
- else if (PREFIXES3.has(first)) {
18081
+ else if (PREFIXES2.has(first)) {
18164
18082
  words = words.slice(1);
18165
18083
  while (words[0]?.startsWith("-"))
18166
18084
  words = words.slice(words[0] === "-u" ? 2 : 1);
@@ -18275,6 +18193,10 @@ var pushAction = (args) => {
18275
18193
  let force = false;
18276
18194
  let lease = false;
18277
18195
  let wholesale = false;
18196
+ let allTags = false;
18197
+ let deletes = false;
18198
+ let allBranches = false;
18199
+ let mirror = false;
18278
18200
  let remote = null;
18279
18201
  const positionals = [];
18280
18202
  for (let index = 1;index < args.length; index++) {
@@ -18295,14 +18217,22 @@ var pushAction = (args) => {
18295
18217
  force = true;
18296
18218
  else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
18297
18219
  lease = true;
18298
- else if (["--all", "--mirror", "--branches", "--tags"].includes(arg))
18299
- wholesale = true;
18220
+ else if (arg === "--delete" || arg === "-d")
18221
+ deletes = true;
18222
+ else if (arg === "--tags")
18223
+ allTags = wholesale = true;
18224
+ else if (arg === "--all" || arg === "--branches")
18225
+ allBranches = wholesale = true;
18226
+ else if (arg === "--mirror")
18227
+ mirror = wholesale = true;
18300
18228
  else if (!arg.startsWith("-"))
18301
18229
  positionals.push(arg);
18302
18230
  }
18303
18231
  const specs = remote === null ? positionals.slice(1) : positionals;
18304
18232
  remote ??= positionals[0] ?? null;
18305
18233
  const targets = [];
18234
+ const tags = [];
18235
+ const deleted = [];
18306
18236
  for (const raw of specs) {
18307
18237
  if (raw.startsWith("+"))
18308
18238
  force = true;
@@ -18311,16 +18241,29 @@ var pushAction = (args) => {
18311
18241
  const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
18312
18242
  if (!destination)
18313
18243
  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);
18244
+ const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
18245
+ if (destination.startsWith("refs/tags/"))
18246
+ tags.push(destination.slice(10));
18247
+ if (name === undefined)
18248
+ continue;
18249
+ targets.push(name);
18250
+ if (colon === 0 && name !== null)
18251
+ deleted.push(name);
18320
18252
  }
18321
18253
  if (specs.length === 0 && !wholesale)
18322
18254
  targets.push(null);
18323
- return { kind: "push", targets, blindForce: force && !lease, remote };
18255
+ return {
18256
+ kind: "push",
18257
+ targets,
18258
+ blindForce: force && !lease,
18259
+ remote,
18260
+ ...tags.length ? { tags } : {},
18261
+ ...allTags ? { allTags: true } : {},
18262
+ ...deletes ? { deletes: true } : {},
18263
+ ...deleted.length ? { deleted } : {},
18264
+ ...allBranches ? { allBranches: true } : {},
18265
+ ...mirror ? { mirror: true } : {}
18266
+ };
18324
18267
  };
18325
18268
  var VALUE_OPTIONS = {
18326
18269
  git: new Set([
@@ -18427,28 +18370,80 @@ var headSha2 = (repo) => {
18427
18370
  if (!git || git.reftable)
18428
18371
  return null;
18429
18372
  try {
18430
- const head = fs16.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
18373
+ const head = fs16.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
18431
18374
  if (/^[0-9a-f]{40,64}$/.test(head))
18432
18375
  return head;
18433
18376
  const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
18434
18377
  if (!ref)
18435
18378
  return null;
18436
- const common = path20.dirname(repo.location.dir);
18379
+ const common = path19.dirname(repo.location.dir);
18437
18380
  try {
18438
- return fs16.readFileSync(path20.join(common, ref), "utf8").trim() || null;
18381
+ return fs16.readFileSync(path19.join(common, ref), "utf8").trim() || null;
18439
18382
  } catch {}
18440
- const packed = fs16.readFileSync(path20.join(common, "packed-refs"), "utf8");
18383
+ const packed = fs16.readFileSync(path19.join(common, "packed-refs"), "utf8");
18441
18384
  return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
18442
18385
  } catch {
18443
18386
  return null;
18444
18387
  }
18445
18388
  };
18446
18389
  var quote = (text) => `\`${text}\``;
18447
- var refusal = (action, branch) => {
18390
+ var refusal = (action, branch, dir) => {
18448
18391
  if (action.kind === "merge")
18449
18392
  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")}).`;
18393
+ const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
18394
+ if (dir && branch && plainPush && releaseBranch(dir, branch))
18395
+ 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
18396
  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
18397
  };
18398
+ var refFacts = (repo, remote) => {
18399
+ const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
18400
+ const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
18401
+ const remotePrefix = `refs/remotes/${remote}/`;
18402
+ for (const ref of (listed.stdout ?? "").split(`
18403
+ `)) {
18404
+ if (ref.startsWith("refs/heads/"))
18405
+ facts.heads.add(ref.slice(11));
18406
+ else if (ref.startsWith("refs/tags/"))
18407
+ facts.tags.add(ref.slice(10));
18408
+ else if (ref.startsWith(remotePrefix))
18409
+ facts.remoteHeads.add(ref.slice(remotePrefix.length));
18410
+ }
18411
+ return facts;
18412
+ };
18413
+ var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
18414
+ var tagRefusal = (action, facts) => {
18415
+ const tags = [
18416
+ ...action.tags ?? [],
18417
+ ...action.targets.filter((name) => name !== null && isTag(facts, name))
18418
+ ];
18419
+ if (!tags.length && !action.allTags)
18420
+ return null;
18421
+ const named = tags.length ? tags.join(" --release ") : "v<version>";
18422
+ 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")}).`;
18423
+ };
18424
+ var remoteHas = (repo, remote, branch) => {
18425
+ const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
18426
+ cwd: repo.dir,
18427
+ encoding: "utf8",
18428
+ timeout: 3000,
18429
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
18430
+ });
18431
+ return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
18432
+ };
18433
+ var offPolicyPush = (action, repo, facts, remote) => {
18434
+ const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
18435
+ for (const branch of names) {
18436
+ if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
18437
+ continue;
18438
+ if (facts.remoteHeads.has(branch))
18439
+ continue;
18440
+ const check = validateBranchNameFor(repo.dir, branch);
18441
+ if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
18442
+ continue;
18443
+ return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
18444
+ }
18445
+ return null;
18446
+ };
18452
18447
  var nudgeFor = (action, host, session) => {
18453
18448
  switch (action.kind) {
18454
18449
  case "commit": {
@@ -18474,7 +18469,7 @@ var actionsIn = (cwd, command, dialect) => {
18474
18469
  const action = rawAction(invocation);
18475
18470
  if (!action)
18476
18471
  continue;
18477
- const dir = path20.resolve(cwd, invocation.dir ?? ".");
18472
+ const dir = path19.resolve(cwd, invocation.dir ?? ".");
18478
18473
  if (!repos.has(dir)) {
18479
18474
  const repo = repoAt(dir);
18480
18475
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
@@ -18511,7 +18506,7 @@ var unseenNudges = (input, nudges, store) => {
18511
18506
  function rawGitPre(input, command, options = {}) {
18512
18507
  try {
18513
18508
  if (!mentionsRawTool(command))
18514
- return NONE3;
18509
+ return NONE2;
18515
18510
  const dialect = dialectOf(input);
18516
18511
  const nudges = new Map;
18517
18512
  let store = null;
@@ -18519,31 +18514,42 @@ function rawGitPre(input, command, options = {}) {
18519
18514
  store ??= repo.location.dir;
18520
18515
  if (action.kind === "merge")
18521
18516
  return deny(refusal(action, null));
18522
- if (action.kind === "push")
18517
+ if (action.kind === "push" && toPushRemote(repo, action.remote)) {
18518
+ if (action.mirror)
18519
+ 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
18520
  for (const target of action.targets) {
18524
18521
  const branch = target ?? repo.branch;
18525
- if (branch && guardedBranch(repo.dir, branch) && toPushRemote(repo, action.remote))
18526
- return deny(refusal(action, branch));
18522
+ if (branch && guardedBranch(repo.dir, branch))
18523
+ return deny(refusal(action, branch, repo.dir));
18527
18524
  }
18525
+ const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
18526
+ const facts = refFacts(repo, remote);
18527
+ const tagDeny = tagRefusal(action, facts);
18528
+ if (tagDeny)
18529
+ return deny(tagDeny);
18530
+ const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
18531
+ if (nameDeny)
18532
+ return deny(nameDeny);
18533
+ }
18528
18534
  const nudge = nudgeFor(action, input.host, input.session.id);
18529
18535
  if (nudge && !nudges.has(nudgeKind(action)))
18530
18536
  nudges.set(nudgeKind(action), nudge);
18531
18537
  }
18532
18538
  if (options.nudge === false || !store || nudges.size === 0)
18533
- return NONE3;
18539
+ return NONE2;
18534
18540
  if (workitInvocations(command, dialect).length > 0)
18535
- return NONE3;
18541
+ return NONE2;
18536
18542
  const shown = unseenNudges(input, nudges, store);
18537
18543
  return shown.length ? { kind: "context", text: shown.join(`
18538
- `) } : NONE3;
18544
+ `) } : NONE2;
18539
18545
  } catch {
18540
- return NONE3;
18546
+ return NONE2;
18541
18547
  }
18542
18548
  }
18543
18549
  var deny = (reason) => ({ kind: "deny", reason, unblock: null });
18544
18550
  var NEXT_COMMAND = "next";
18545
18551
  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`);
18552
+ 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
18553
  var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
18548
18554
  function noteRawCommit(input, command, key) {
18549
18555
  try {
@@ -18554,7 +18560,7 @@ function noteRawCommit(input, command, key) {
18554
18560
  if (action.kind !== "commit")
18555
18561
  continue;
18556
18562
  const file = markerFile(repo, input.session.id, key);
18557
- fs16.mkdirSync(path20.dirname(file), { recursive: true });
18563
+ fs16.mkdirSync(path19.dirname(file), { recursive: true });
18558
18564
  fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
18559
18565
  `);
18560
18566
  }
@@ -18636,7 +18642,292 @@ function settlePendingCommit(input) {
18636
18642
  } catch {}
18637
18643
  }
18638
18644
 
18645
+ // packages/workit-core/src/core/route-intent.ts
18646
+ var UNKNOWABLE = /[$`*?[\]{}~]/u;
18647
+ var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
18648
+ var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
18649
+ var WORKTREE_CREATE = new Set(["-b", "-B"]);
18650
+ 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"]));
18651
+ var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
18652
+ var flagValue = (args, flags) => {
18653
+ for (let index = 1;index < args.length; index++) {
18654
+ const arg = args[index];
18655
+ if (arg === "--")
18656
+ return null;
18657
+ if (flags.has(arg))
18658
+ return args[index + 1] ?? null;
18659
+ const eq = arg.indexOf("=");
18660
+ if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
18661
+ return arg.slice(eq + 1);
18662
+ if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
18663
+ return arg.slice(2);
18664
+ }
18665
+ return null;
18666
+ };
18667
+ var WORKTREE_VALUES = new Set(["--reason"]);
18668
+ var worktreeTarget = (args) => {
18669
+ const named = flagValue(args, WORKTREE_CREATE);
18670
+ if (named !== null)
18671
+ return named;
18672
+ const positionals = [];
18673
+ for (let index = 1;index < args.length; index++) {
18674
+ const arg = args[index];
18675
+ if (arg === "--detach" || arg === "-d")
18676
+ return null;
18677
+ if (WORKTREE_VALUES.has(arg))
18678
+ index++;
18679
+ else if (!arg.startsWith("-"))
18680
+ positionals.push(arg);
18681
+ }
18682
+ if (positionals.length !== 1)
18683
+ return null;
18684
+ const base = positionals[0].split(/[\\/]/u).findLast(Boolean) ?? "";
18685
+ return base && base !== "." && base !== ".." ? base : null;
18686
+ };
18687
+ var branchCommandTarget = (args) => {
18688
+ let rename = false;
18689
+ const positionals = [];
18690
+ for (let index = 1;index < args.length; index++) {
18691
+ const arg = args[index];
18692
+ if (arg === "--") {
18693
+ positionals.push(...args.slice(index + 1));
18694
+ break;
18695
+ }
18696
+ const flag = arg.split("=", 1)[0];
18697
+ if (BRANCH_NO_CREATE.has(flag))
18698
+ return null;
18699
+ if (BRANCH_RENAME.has(flag))
18700
+ rename = true;
18701
+ else if (!arg.startsWith("-"))
18702
+ positionals.push(arg);
18703
+ }
18704
+ if (positionals.length < 1 || positionals.length > 2)
18705
+ return null;
18706
+ return rename ? positionals.at(-1) ?? null : positionals[0];
18707
+ };
18708
+ var gitBranchCreation = (args) => {
18709
+ const [sub, second] = args;
18710
+ 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;
18711
+ return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
18712
+ };
18713
+ var shellBranchTargets = (command, dialect = "posix") => {
18714
+ if (!/\bgit\b/u.test(command))
18715
+ return [];
18716
+ const found = [];
18717
+ for (const invocation of rawInvocations(command, dialect)) {
18718
+ if (invocation.tool !== "git")
18719
+ continue;
18720
+ const branch = gitBranchCreation(invocation.args);
18721
+ if (branch)
18722
+ found.push({ branch, dir: invocation.dir });
18723
+ }
18724
+ return found;
18725
+ };
18726
+ var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
18727
+ cwd: dir,
18728
+ stdio: "ignore",
18729
+ timeout: 5000
18730
+ }).status === 0;
18731
+ var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
18732
+ let judged = false;
18733
+ for (const target of shellBranchTargets(command, dialect)) {
18734
+ if (target.dir === undefined)
18735
+ continue;
18736
+ const dir = target.dir === null ? root : path20.resolve(root, target.dir);
18737
+ if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
18738
+ continue;
18739
+ if (localBranchExists(dir, target.branch))
18740
+ continue;
18741
+ judged = true;
18742
+ const check = validateBranchNameFor(dir, target.branch);
18743
+ if (!check.ok)
18744
+ return check;
18745
+ }
18746
+ return judged ? { ok: true } : null;
18747
+ };
18748
+
18749
+ // packages/workit-core/src/hooks/policy.ts
18750
+ var shellPolicy = (cwd, command, dialect = "posix") => {
18751
+ const policy = shellBranchPolicyViolation(cwd, command, dialect);
18752
+ return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
18753
+ };
18754
+ // packages/workit-core/src/hooks/write-gate.ts
18755
+ import { realpathSync as realpathSync5 } from "node:fs";
18756
+ import path21 from "node:path";
18757
+ var NONE3 = { kind: "none" };
18758
+ var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
18759
+ var canonical2 = (file) => {
18760
+ let rest = "";
18761
+ for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
18762
+ try {
18763
+ return path21.join(realpathSync5(dir), rest);
18764
+ } catch {
18765
+ if (path21.dirname(dir) === dir)
18766
+ return path21.resolve(file);
18767
+ rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
18768
+ }
18769
+ }
18770
+ };
18771
+ var exempt = (cwd, root, file, plans) => {
18772
+ const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
18773
+ const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
18774
+ if (relative.startsWith("..") || path21.isAbsolute(relative))
18775
+ return true;
18776
+ const posix = relative.split(path21.sep).join("/");
18777
+ const segments = posix.split("/");
18778
+ return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
18779
+ };
18780
+ var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
18781
+ var operands = (words) => words.filter((item) => !item.startsWith("-"));
18782
+ var editsOf = (input) => {
18783
+ let words = input;
18784
+ while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
18785
+ words = words.slice(1);
18786
+ const [verb, ...args] = words;
18787
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
18788
+ if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
18789
+ return [];
18790
+ switch (name) {
18791
+ case "touch":
18792
+ case "mkdir":
18793
+ case "rm":
18794
+ case "rmdir":
18795
+ case "tee":
18796
+ case "mv":
18797
+ case "truncate":
18798
+ return operands(args);
18799
+ case "cp":
18800
+ case "ln":
18801
+ case "install": {
18802
+ const target = args.indexOf("-t");
18803
+ if (target >= 0)
18804
+ return args[target + 1] ? [args[target + 1]] : null;
18805
+ const last = operands(args).at(-1);
18806
+ return last ? [last] : null;
18807
+ }
18808
+ case "dd":
18809
+ return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
18810
+ case "sed":
18811
+ case "perl": {
18812
+ if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
18813
+ return [];
18814
+ const scripted = args.some((item) => item === "-e" || item === "-f");
18815
+ const files = [];
18816
+ for (let index = 0;index < args.length; index++) {
18817
+ if (args[index] === "-e" || args[index] === "-f")
18818
+ index++;
18819
+ else if (!args[index].startsWith("-"))
18820
+ files.push(args[index]);
18821
+ }
18822
+ return scripted ? files : files.slice(1);
18823
+ }
18824
+ case "patch":
18825
+ return null;
18826
+ case "set-content":
18827
+ case "add-content":
18828
+ case "out-file":
18829
+ case "new-item":
18830
+ case "ni":
18831
+ case "remove-item":
18832
+ case "del":
18833
+ case "copy-item":
18834
+ case "move-item": {
18835
+ const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
18836
+ if (flag >= 0)
18837
+ return args[flag + 1] ? [args[flag + 1]] : null;
18838
+ const files = operands(args);
18839
+ if (name === "move-item")
18840
+ return files.length ? files : null;
18841
+ const file = name === "copy-item" ? files.at(-1) : files[0];
18842
+ return file ? [file] : null;
18843
+ }
18844
+ case "git": {
18845
+ const sub = operands(args)[0];
18846
+ if (sub === "apply")
18847
+ return null;
18848
+ if (sub === "restore" || sub === "mv" || sub === "rm")
18849
+ return operands(args).slice(1);
18850
+ if (sub === "checkout" && args.includes("--"))
18851
+ return args.slice(args.indexOf("--") + 1);
18852
+ return [];
18853
+ }
18854
+ default:
18855
+ return [];
18856
+ }
18857
+ };
18858
+ var shellWrites = (command) => {
18859
+ const targets = [];
18860
+ let unknown = false;
18861
+ for (const segment of segmentsOf(command)) {
18862
+ targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
18863
+ const edits = editsOf(segment.words);
18864
+ if (edits === null)
18865
+ unknown = true;
18866
+ else
18867
+ targets.push(...edits);
18868
+ }
18869
+ if (!unknown && targets.length === 0)
18870
+ return { writes: false, targets: null };
18871
+ return { writes: true, targets: unknown ? null : targets };
18872
+ };
18873
+ var cache2 = new Map;
18874
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
18875
+ var gateFor = (cwd) => {
18876
+ const store = new TaskStore(cwd);
18877
+ const listed = store.listTaskIndex();
18878
+ if (!listed.ok)
18879
+ return null;
18880
+ const key = store.currentKey();
18881
+ if (!key.ok)
18882
+ return null;
18883
+ const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
18884
+ if (!entry)
18885
+ return { blockers: [], root: store.root, plans: new Set };
18886
+ const ledger = ledgerPath(store.root);
18887
+ const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
18888
+ const hit = cache2.get(store.root);
18889
+ if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
18890
+ return hit;
18891
+ const task = store.readTask(entry.id);
18892
+ if (!task.ok)
18893
+ return null;
18894
+ const workspace = store.readWorkspace();
18895
+ const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
18896
+ const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
18897
+ const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
18898
+ return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
18899
+ });
18900
+ const gate = {
18901
+ blockers,
18902
+ root: store.root,
18903
+ plans,
18904
+ key: cacheKey,
18905
+ planKey: planKeyOf(store.root, plans)
18906
+ };
18907
+ cache2.set(store.root, gate);
18908
+ if (cache2.size > 32)
18909
+ cache2.delete(cache2.keys().next().value);
18910
+ return gate;
18911
+ };
18912
+ function writeGate(cwd, paths) {
18913
+ try {
18914
+ const gate = gateFor(cwd);
18915
+ if (!gate || gate.blockers.length === 0)
18916
+ return NONE3;
18917
+ if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
18918
+ return NONE3;
18919
+ const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
18920
+ return {
18921
+ kind: "deny",
18922
+ reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
18923
+ unblock: gate.blockers[0].reason
18924
+ };
18925
+ } catch {
18926
+ return NONE3;
18927
+ }
18928
+ }
18639
18929
  // packages/workit-core/src/hooks/stop.ts
18930
+ import { spawnSync as spawnSync13 } from "node:child_process";
18640
18931
  var NONE4 = { kind: "none" };
18641
18932
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
18642
18933
  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 +18948,7 @@ var asksUser = (message) => {
18657
18948
  };
18658
18949
  var short = (sha) => (sha ?? "?").slice(0, 12);
18659
18950
  var gitList = (cwd, args) => {
18660
- const run = spawnSync12("git", args, {
18951
+ const run = spawnSync13("git", args, {
18661
18952
  cwd,
18662
18953
  encoding: "utf8",
18663
18954
  timeout: 3000,
@@ -19020,13 +19311,13 @@ ${line}` };
19020
19311
  return decision;
19021
19312
  };
19022
19313
  // 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";
19314
+ import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
19315
+ import path22 from "node:path";
19025
19316
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
19026
19317
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
19027
19318
  var optionalText = (value) => typeof value === "string" ? value : null;
19028
19319
  var existingDirectory = (value) => {
19029
- if (!nonEmpty2(value) || !path21.isAbsolute(value) || !existsSync6(value))
19320
+ if (!nonEmpty2(value) || !path22.isAbsolute(value) || !existsSync7(value))
19030
19321
  return null;
19031
19322
  try {
19032
19323
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -19115,13 +19406,14 @@ var subagentStartText = (input, descriptor, event) => {
19115
19406
  const role = workitRole(descriptor, event.agentType);
19116
19407
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
19117
19408
  const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
19409
+ const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
19118
19410
  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.`;
19411
+ return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} 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
19412
  if (isolated)
19121
19413
  return `${ISOLATED_GIT}
19122
19414
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
19123
19415
  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.`;
19416
+ 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
19417
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
19126
19418
  if (role !== "judge" || !event.agentId)
19127
19419
  return readOnly;
@@ -19161,7 +19453,7 @@ function handleHook(input, deps) {
19161
19453
  if (skillRead)
19162
19454
  recordSkillLoad(input, skillRead, "read");
19163
19455
  const canDeny = usable2(descriptor.shellPolicy.deny);
19164
- const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
19456
+ const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
19165
19457
  if (policy.kind !== "none")
19166
19458
  return policy;
19167
19459
  const postTool = usable2(descriptor.events["shell.post"].support);
@@ -19764,7 +20056,7 @@ var codexAdapter = {
19764
20056
 
19765
20057
  // packages/workit-core/src/hooks/hosts/cursor.ts
19766
20058
  import fs17 from "node:fs";
19767
- import path22 from "node:path";
20059
+ import path23 from "node:path";
19768
20060
  var undocumented = { support: "undocumented", native: null };
19769
20061
  var none = { support: "none", native: null };
19770
20062
  var CURSOR_DESCRIPTOR = {
@@ -19961,7 +20253,7 @@ var protocolEvent2 = (input) => {
19961
20253
  };
19962
20254
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
19963
20255
  var lastAssistantText = (file) => {
19964
- if (!file || !path22.isAbsolute(file))
20256
+ if (!file || !path23.isAbsolute(file))
19965
20257
  return null;
19966
20258
  try {
19967
20259
  const size = fs17.statSync(file).size;
@@ -19998,7 +20290,7 @@ var lastAssistantText = (file) => {
19998
20290
  return null;
19999
20291
  }
20000
20292
  };
20001
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path22.sep) ? root : `${root}${path22.sep}`);
20293
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path23.sep) ? root : `${root}${path23.sep}`);
20002
20294
  var hookCwd = (input) => {
20003
20295
  const cwd = existingDirectory(input.cwd);
20004
20296
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -37,6 +37,6 @@ if (quiet)
37
37
  process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
38
38
  `);
39
39
  else {
40
- const { runCodexHook } = await import("./workit-hook-xhqhe7h0.js");
40
+ const { runCodexHook } = await import("./workit-hook-gtdf2ra8.js");
41
41
  await runCodexHook([payload]);
42
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-codex",
3
- "version": "8.10.0",
3
+ "version": "8.11.0",
4
4
  "private": false,
5
5
  "description": "Workit Codex CLI/desktop plugin — shared MCP transport, hooks, method skills, and verifier/reviewer/implementer agents",
6
6
  "keywords": [
@@ -39,8 +39,8 @@
39
39
  "build": "bun scripts/build.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@brainervirus/workit-core": "^8.10.0",
43
- "@brainervirus/workit-mcp": "^8.10.0"
42
+ "@brainervirus/workit-core": "^8.11.0",
43
+ "@brainervirus/workit-mcp": "^8.11.0"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">=24"