@brainervirus/workit-opencode 8.22.3 → 8.23.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.
@@ -20,14 +20,14 @@ units of ~100+ lines each. Coupled work stays with one owner. Workit never spawn
20
20
  PR (`references/orchestration.md`).
21
21
  2. **Record standing orders** (`workit ledger standing add "<order>"`): every
22
22
  user directive all workers share. Directives decay across resumes.
23
- 3. **Brief each worker verbatim** with `workit fanout brief <slice>`: plan
24
- fields, standing orders, SCRATCH, session id, fan-in and heavy-lease rules.
23
+ 3. **Brief each worker verbatim**: `workit fanout worktree create <slice>`
24
+ (every host), then `workit fanout brief <slice>` (WORKTREE, SCRATCH, rules).
25
25
  Never hand-edit it; change the plan or the standing orders and render again.
26
26
  4. **Spawn in a rolling window**, in the background, each in its own worktree:
27
27
  keep 4-6 in flight and refill from `spawnable` in `workit fanout status` as
28
28
  each one finishes. Model: the brief's `model:` line, else TIER
29
- (`references/orchestration.md`). Claude Code: the `implementer` agent;
30
- elsewhere first `workit fanout worktree create <slice>`.
29
+ (`references/orchestration.md`). Claude Code: the `implementer` agent, never
30
+ with `isolation: "worktree"`; Workit's hooks confine it to its WORKTREE.
31
31
  5. **Judge liveness by side effects only:** `workit fanout status` (head age,
32
32
  PR, CI, verdict, landed; STUCK past the TIMEBOX). Stop a stuck worker and
33
33
  observe that it exited (a timeout is not proof). Removing a worktree drops
@@ -25,6 +25,7 @@ REPORT: branch, head SHA, files changed, each VERIFY command with its exit code,
25
25
  each ACCEPTANCE line met / not met, rulings you made (`workit ledger ruling`),
26
26
  anything out of scope as a follow-up, not a diff.
27
27
  STANDING: <the standing orders, verbatim: `workit ledger standing add` records each one>
28
+ WORKTREE: <the path `workit fanout worktree create <slice>` made; the worker runs `workit --cwd <it> …` there>
28
29
  <identity line, per the lead's host: see "Identity and git rules per host" below>
29
30
  ```
30
31
 
@@ -48,6 +49,7 @@ FORBIDDEN: no edits outside SCOPE; no schema migration; no new packages
48
49
  FAN-IN: one PR per slice. Never rebase, retarget, merge or force-push: the lead owns topology.
49
50
  REPORT: as in the template
50
51
  STANDING: conventional commits; no comments that restate code; ask nothing, record rulings instead
52
+ WORKTREE: ../app-wt/usage-endpoint (made by `workit fanout worktree create usage-endpoint`; yours alone)
51
53
  ```
52
54
 
53
55
  ## Plan file
@@ -106,25 +108,29 @@ lead's host (`WORKIT_HOST`, or the host's own session variable):
106
108
 
107
109
  | Lead's host | Identity line | RULES 1 and 3 |
108
110
  | --- | --- | --- |
109
- | Claude Code | `Session: your env already has WORKIT_SESSION_ID (the lead's id)`; no `export`, no hand-minted id (SubagentStart issues `<lead>:<agent id>` only to verifiers and reviewers, never to an implementer, so the worker's commits carry the lead's id) | plain git: `git fetch origin && git switch -c <branch> origin/<base>` and `git commit -m <msg> --trailer "Workit-Session: <the env WORKIT_SESSION_ID>" --trailer "<host attribution>" -- <paths in SCOPE>`, because worktree isolation refuses every `workit git` form |
111
+ | Claude Code | `Session: your env already has WORKIT_SESSION_ID (the lead's id)`; no `export`, no hand-minted id (SubagentStart issues `<lead>:<agent id>` only to verifiers and reviewers, never to an implementer, so the worker's commits carry the lead's id) | `workit git branch <branch> --base <base>`, `workit git commit -m <msg> -- <paths in SCOPE>`: the implementer runs without Claude Code's worktree isolation, which refused every `workit git` form |
110
112
  | OpenCode, Pi, Codex | `Session: your host already sets WORKIT_SESSION_ID in your shell`; export nothing (the host derives the id and an export cannot replace it) | `workit git branch <branch> --base <base>`, `workit git commit -m <msg> -- <paths in SCOPE>` |
111
113
  | Cursor, CI, a plain terminal | `export WORKIT_SESSION_ID=<lead>-w-<slice>` (`+try<n>` for a race attempt): declared, not checked | the same `workit git` forms |
112
114
 
113
- `MODE: resume` is `git switch <branch>` on every host (the branch exists; the
114
- lead removed the dead worker's worktree after its exit, so the switch
115
- succeeds), then continue from its head. A race attempt (`--attempt <n>`)
116
- follows its host's row with the branch `<branch>-try<n>`.
115
+ Once `workit fanout worktree create <slice>` made the worktree, the brief's
116
+ WORKTREE line names it and RULES 1 and 3 run against it on every host:
117
+ `workit --cwd <worktree> git commit …`, `workit --cwd <worktree> check <name>`,
118
+ one verb per call. The worktree is already on the branch (MODE new or resume),
119
+ so no branch command comes first. Without one, `MODE: resume` is
120
+ `git switch <branch>` (the branch exists), then continue from its head. A race
121
+ attempt (`--attempt <n>`) follows its host's row with the branch
122
+ `<branch>-try<n>`.
117
123
 
118
124
  ## Worker rules (`workit fanout brief` appends them as RULES)
119
125
 
120
- 1. First command: create the branch (`MODE: new`; the worktree may start on a
121
- name that breaks branch policy) or `git switch` to it (`MODE: resume`),
122
- as the table above says for your host.
126
+ 1. Work only in WORKTREE, already on the branch, one verb per call against it
127
+ (`workit --cwd <worktree> …`); Workit's hooks deny an implementer's change
128
+ outside it. With no worktree made yet: create the branch (`MODE: new`) or
129
+ `git switch` to it (`MODE: resume`), as the table above says.
123
130
  2. Decide ambiguities yourself and record them:
124
131
  `workit ledger ruling "<what>" --why "<why>" --cost-if-wrong "<cost>"`.
125
132
  Stop only for an irreversible action, a security-sensitive one, or a side
126
133
  effect outside the worktree.
127
- 3. Commit as the table says (`workit git commit -m "<msg>" -- <paths in SCOPE>`,
128
- or plain git with the trailers on Claude Code); push only if the brief says
129
- so.
134
+ 3. Commit with `workit --cwd <worktree> git commit -m "<msg>" -- <paths in SCOPE>`;
135
+ push only if the brief says so.
130
136
  4. Never record a verdict on your own work.
@@ -35,8 +35,8 @@ parent is verified, appears there by itself.
35
35
 
36
36
  A slice is retried when it is STUCK, its worker exits without meeting
37
37
  ACCEPTANCE, or its verdict fails. Stop the worker and observe that it exited.
38
- Release its worktree: `workit fanout worktree release <slice>` (native
39
- worktrees: record `git -C <wt> status --short` first). Then spawn one fresh
38
+ Release its worktree: `workit fanout worktree release <slice>` (it records
39
+ `git status` first), and create it again for the retry. Then spawn one fresh
40
40
  worker with `workit fanout brief <slice> --mode resume`: same branch, and the
41
41
  brief points at its head and its ledger rows (last report, verdicts,
42
42
  rulings). If the retry fails too, escalate instead of a third worker:
@@ -69,9 +69,9 @@ list. The fixer applies that list once, then one focused recheck follows.
69
69
  When a hard slice has an uncertain approach and a cheap VERIFY, run 2-3
70
70
  attempts at once instead of retrying in series. `workit fanout brief <slice>
71
71
  --attempt <n>` gives each attempt its own branch (`<branch>-try<n>`), session
72
- and scratch dir. Each attempt needs its own worktree: Claude Code's
73
- `implementer` isolates natively; on other hosts `fanout worktree create` makes
74
- only the slice's own worktree, so run the attempts one after another there.
72
+ and scratch dir. Each attempt needs its own worktree, and `fanout worktree
73
+ create` makes only the slice's own one, so run the attempts one after another
74
+ there on every host.
75
75
  Verify every attempt, keep the one with the best verdict (accepted first,
76
76
  then the fewest changed files, then the earliest), point the slice branch at
77
77
  it (`git branch <branch> <winner>`), have the verifier record the verdict on
@@ -109,7 +109,7 @@ Which session id a verdict carries, per host:
109
109
  `WORKIT_SESSION_ID=<lead>:verifier-<random hex>`, a fresh suffix per
110
110
  verifier. Cursor cannot check it: it is declared, not derived.
111
111
  - A worker's commits: on Claude Code the lead's `WORKIT_SESSION_ID` already in
112
- its env (SubagentStart issues no implementer id; plain git, no export, so the
112
+ its env (SubagentStart issues no implementer id; no export, so the
113
113
  lead authored those commits: a verifier or reviewer records the verdict, not
114
114
  the lead); on OpenCode, Pi and Codex the id the host already
115
115
  puts in its shell; on Cursor, CI and a plain terminal the brief's
@@ -47,6 +47,8 @@ description: Build a requested change in small verified steps - follow local pat
47
47
  `workit verify-delivery push`).
48
48
 
49
49
  Independent slices that could run in parallel: fan them out (call the skill tool with `workit-fanout`).
50
+ One `implementer` slice: `workit fanout worktree create <slice>` before spawning,
51
+ then `workit fanout brief <slice>`; no host isolation (Workit's hooks confine it).
50
52
 
51
53
  Stay inline by default (workspace `delegation: inline-first`). Hand work off
52
54
  only past the thresholds: an explorer after about 3 tool calls or 10k tokens
package/dist/plugin.js CHANGED
@@ -16219,6 +16219,24 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
16219
16219
  }
16220
16220
  return found;
16221
16221
  }
16222
+ var WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
16223
+ function skipWorkitGlobals(args) {
16224
+ let cwd = null;
16225
+ let at = 0;
16226
+ for (;at < args.length; at++) {
16227
+ const match = WORKIT_GLOBAL.exec(args[at]);
16228
+ if (match?.[1] === "=") {
16229
+ if (args[at].startsWith("--cwd="))
16230
+ cwd = args[at].slice(6);
16231
+ } else if (match && args[at + 1] !== undefined) {
16232
+ if (args[at] === "--cwd")
16233
+ cwd = args[at + 1];
16234
+ at++;
16235
+ } else if (args[at] !== "--json")
16236
+ break;
16237
+ }
16238
+ return { rest: args.slice(at), cwd };
16239
+ }
16222
16240
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
16223
16241
  var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
16224
16242
  var pushDestination = (destination) => {
@@ -21349,7 +21367,7 @@ function heavyGate(cwd, command, options) {
21349
21367
  }
21350
21368
  }
21351
21369
  // packages/workit-core/src/hooks/write-gate.ts
21352
- import { realpathSync as realpathSync6 } from "node:fs";
21370
+ import { existsSync as existsSync9, realpathSync as realpathSync6 } from "node:fs";
21353
21371
  import os10 from "node:os";
21354
21372
  import path31 from "node:path";
21355
21373
 
@@ -21578,7 +21596,7 @@ var exempt = (cwd, root, file, plans) => {
21578
21596
  const segments = posix.split("/");
21579
21597
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
21580
21598
  };
21581
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
21599
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
21582
21600
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
21583
21601
  var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
21584
21602
  var wrappedScript = (input) => {
@@ -21590,9 +21608,9 @@ var wrappedScript = (input) => {
21590
21608
  return null;
21591
21609
  if ("script" in tail)
21592
21610
  return tail.script;
21593
- const [shell, flag, script] = tail.argv;
21594
- if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
21595
- return script ?? null;
21611
+ const script = shellScript2(tail.argv);
21612
+ if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
21613
+ return script;
21596
21614
  return tail.argv.map(quoteWord2).join(" ");
21597
21615
  };
21598
21616
  var editsOf = (input) => {
@@ -21671,6 +21689,94 @@ var editsOf = (input) => {
21671
21689
  return [];
21672
21690
  }
21673
21691
  };
21692
+ var shellScript2 = (words) => {
21693
+ if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
21694
+ return null;
21695
+ for (let index = 1;index < words.length; index++) {
21696
+ const word = words[index];
21697
+ if (/^-[a-z]*c$/.test(word))
21698
+ return words[index + 1] ?? null;
21699
+ if (/^[-+][oO]$/.test(word))
21700
+ index++;
21701
+ else if (!/^[-+]/.test(word))
21702
+ return null;
21703
+ }
21704
+ return null;
21705
+ };
21706
+ var XARGS_VALUE = /^-[IinLPdsEa]$/;
21707
+ var runsWriter = (words) => {
21708
+ const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
21709
+ const edits = editsOf(filled);
21710
+ return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
21711
+ };
21712
+ var opaqueWrites = (input) => {
21713
+ let words = input;
21714
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
21715
+ if (words[0] === "xargs") {
21716
+ let at = 1;
21717
+ while (words[at]?.startsWith("-"))
21718
+ at += XARGS_VALUE.test(words[at]) ? 2 : 1;
21719
+ return runsWriter(words.slice(at)) ? ["."] : null;
21720
+ }
21721
+ words = words.slice(1);
21722
+ }
21723
+ const [verb, ...args] = words;
21724
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
21725
+ const files = operands(args);
21726
+ const has = (pattern) => args.some((arg) => pattern.test(arg));
21727
+ const listed = (named) => named.length ? named : ["."];
21728
+ if (name === "unlink" || name === "shred")
21729
+ return listed(files);
21730
+ if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
21731
+ return listed(has(/^--reference/) ? files : files.slice(1));
21732
+ if (name === "rsync") {
21733
+ const dest = files.at(-1);
21734
+ const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
21735
+ return dest === undefined ? ["."] : remote ? [] : [dest];
21736
+ }
21737
+ if (name === "find") {
21738
+ const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
21739
+ const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
21740
+ const end = args.findIndex((arg) => /^[-(!]/.test(arg));
21741
+ return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
21742
+ }
21743
+ if (/^g?awk$/.test(name)) {
21744
+ if (!has(/^-i|^--include/))
21745
+ return null;
21746
+ const named = [];
21747
+ let program = !has(/^-f$|^--file/);
21748
+ for (let index = 0;index < args.length; index++)
21749
+ if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
21750
+ index++;
21751
+ else if (args[index].startsWith("-"))
21752
+ continue;
21753
+ else if (program)
21754
+ program = false;
21755
+ else
21756
+ named.push(args[index]);
21757
+ return listed(named);
21758
+ }
21759
+ if (name === "perl")
21760
+ return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
21761
+ if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
21762
+ return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
21763
+ return shellScript2(words) === null ? null : ["."];
21764
+ };
21765
+ var linkSources = (args, at) => {
21766
+ const names = operands([...args]);
21767
+ const into = args.indexOf("-t");
21768
+ const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
21769
+ const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
21770
+ const target = dest === undefined ? null : at(dest);
21771
+ const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
21772
+ const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync9(target) || /[\\/]$/.test(dest ?? "") ? target : path31.dirname(target);
21773
+ return sources.flatMap((source) => {
21774
+ if (symbolic && base !== null && !path31.isAbsolute(homeExpanded(source)))
21775
+ return [path31.resolve(base, source)];
21776
+ const resolved = at(source);
21777
+ return resolved ? [resolved] : [];
21778
+ });
21779
+ };
21674
21780
  var shellWrites = (command, depth = 0) => {
21675
21781
  const targets = [];
21676
21782
  let unknown = false;
@@ -21780,11 +21886,15 @@ var gitMutationDirs = (cwd, command, dialect) => {
21780
21886
  var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
21781
21887
  var PUSHD2 = new Set(["pushd", "push-location"]);
21782
21888
  var POPD2 = new Set(["popd", "pop-location"]);
21783
- var cdInto = (dir, words) => {
21889
+ var UNKNOWN_DIR = path31.resolve(path31.parse(process.cwd()).root, ".workit-unknown-dir");
21890
+ var cdInto = (dir, words, strict = false) => {
21891
+ const lost = strict ? UNKNOWN_DIR : undefined;
21784
21892
  const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
21785
21893
  const target = named === undefined ? undefined : homeExpanded(named);
21786
- if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
21787
- return;
21894
+ if (target === "-" || target && /^~|[$`*?]/.test(target))
21895
+ return lost;
21896
+ if (dir === undefined)
21897
+ return lost;
21788
21898
  return target === undefined ? os10.homedir() : path31.resolve(dir, target);
21789
21899
  };
21790
21900
  var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
@@ -21792,7 +21902,7 @@ var homeExpanded = (file) => {
21792
21902
  const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
21793
21903
  return match ? (process.env.HOME || os10.homedir()) + file.slice(match[0].length) : file;
21794
21904
  };
21795
- var segmentTargets = (cwd, command, dialect, depth = 0) => {
21905
+ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
21796
21906
  const targets = [];
21797
21907
  let dir = cwd;
21798
21908
  let stack = [];
@@ -21816,21 +21926,35 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
21816
21926
  const words = lead < 0 ? [] : segment.words.slice(lead);
21817
21927
  const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
21818
21928
  if (CD2.has(name)) {
21819
- dir = cdInto(dir, words);
21929
+ dir = cdInto(dir, words, strict);
21820
21930
  continue;
21821
21931
  }
21822
21932
  if (PUSHD2.has(name)) {
21823
21933
  stack.push(dir);
21824
- dir = cdInto(dir, words);
21934
+ dir = cdInto(dir, words, strict);
21825
21935
  continue;
21826
21936
  }
21827
21937
  if (POPD2.has(name)) {
21828
- dir = stack.length ? stack.pop() : undefined;
21938
+ dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
21829
21939
  continue;
21830
21940
  }
21831
- const inner = depth < 4 ? wrappedScript(words) : null;
21941
+ const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
21832
21942
  if (inner !== null && dir !== undefined)
21833
- targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
21943
+ targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
21944
+ if (strict && dir !== undefined) {
21945
+ const runIn = dir;
21946
+ for (const sub of depth < 4 ? segment.subs ?? [] : [])
21947
+ targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
21948
+ const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
21949
+ for (const file of opaque ?? []) {
21950
+ const resolved = at(file);
21951
+ if (resolved)
21952
+ targets.push(file === "." ? path31.join(resolved, DIR_TARGET) : resolved);
21953
+ }
21954
+ const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
21955
+ if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
21956
+ targets.push(...linkSources(words.slice(ln + 1), at));
21957
+ }
21834
21958
  for (const file of segment.redirects)
21835
21959
  if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
21836
21960
  const resolved = at(file);
@@ -21847,11 +21971,29 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
21847
21971
  if (resolved)
21848
21972
  targets.push(resolved);
21849
21973
  }
21850
- if (dir !== undefined && workitInvocations(segment.words.map(quoteWord2).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
21851
- targets.push(path31.join(dir, DIR_TARGET));
21974
+ if (dir !== undefined)
21975
+ for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
21976
+ const { at: runsIn, args } = workitCwd(dir, invocation);
21977
+ if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
21978
+ targets.push(path31.join(runsIn, DIR_TARGET));
21979
+ }
21852
21980
  }
21853
21981
  return targets;
21854
21982
  };
21983
+ var workitCwd = (dir, args) => {
21984
+ const { rest, cwd } = skipWorkitGlobals(args);
21985
+ return { at: cwd === null ? dir : path31.resolve(dir, homeExpanded(cwd)), args: rest };
21986
+ };
21987
+ var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
21988
+ try {
21989
+ return [
21990
+ ...segmentTargets(cwd, command, dialect, 0, strict),
21991
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path31.join(dir, DIR_TARGET))
21992
+ ];
21993
+ } catch {
21994
+ return [];
21995
+ }
21996
+ };
21855
21997
  var overridden = (command, dialect) => {
21856
21998
  const segments = segmentsOf(command, dialect);
21857
21999
  if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
@@ -21957,16 +22099,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
21957
22099
  } catch {}
21958
22100
  const writes = shellWrites(command);
21959
22101
  let targets = [];
21960
- if (owner) {
21961
- try {
21962
- targets = [
21963
- ...segmentTargets(cwd, command, dialect),
21964
- ...gitMutationDirs(cwd, command, dialect).map((dir) => path31.join(dir, DIR_TARGET))
21965
- ];
21966
- } catch {
21967
- targets = [];
21968
- }
21969
- }
22102
+ if (owner)
22103
+ targets = shellMutationTargets(cwd, command, dialect);
21970
22104
  if (owner && targets.length) {
21971
22105
  const live = liveGate(owner, targets, deps);
21972
22106
  if (live.kind !== "none") {
@@ -22544,6 +22678,19 @@ var standingSinceStart = (input) => {
22544
22678
  // packages/workit-core/src/hooks/cwd-normalize.ts
22545
22679
  var FILTERS = new Set(["tail", "head", "grep"]);
22546
22680
 
22681
+ // packages/workit-core/src/hooks/worktree-confine.ts
22682
+ var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
22683
+ var MUTATING_REMOTE = new Set([
22684
+ "add",
22685
+ "remove",
22686
+ "rm",
22687
+ "rename",
22688
+ "set-url",
22689
+ "set-head",
22690
+ "set-branches",
22691
+ "prune"
22692
+ ]);
22693
+
22547
22694
  // packages/workit-core/src/hooks/handle.ts
22548
22695
  var offered = new Set;
22549
22696
  var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
@@ -22652,7 +22799,7 @@ var OPENCODE_DESCRIPTOR = {
22652
22799
  import path37 from "node:path";
22653
22800
 
22654
22801
  // packages/workit-core/src/core/gitignore.ts
22655
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
22802
+ import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
22656
22803
  import path33 from "node:path";
22657
22804
  var GITIGNORE_ENTRIES = [
22658
22805
  "# workit: SDD working state (never commit)",
@@ -22674,7 +22821,7 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
22674
22821
  if (!confirmed)
22675
22822
  return { ok: false, error: "confirmed: true required" };
22676
22823
  const file = path33.join(workspaceRoot, ".gitignore");
22677
- const existing = existsSync9(file) ? readFileSync6(file, "utf8") : "";
22824
+ const existing = existsSync10(file) ? readFileSync6(file, "utf8") : "";
22678
22825
  const existingLines = new Set(existing.split(`
22679
22826
  `).map((l) => l.trim()).filter(Boolean));
22680
22827
  const added = [];
@@ -22693,19 +22840,19 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
22693
22840
  ` : "") + append.join(`
22694
22841
  `) + `
22695
22842
  `, "utf8");
22696
- } else if (!existsSync9(file)) {
22843
+ } else if (!existsSync10(file)) {
22697
22844
  writeFileSync4(file, "", "utf8");
22698
22845
  }
22699
22846
  return { ok: true, path: file, added };
22700
22847
  };
22701
22848
 
22702
22849
  // packages/workit-core/src/core/hygiene.ts
22703
- import { existsSync as existsSync10, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
22850
+ import { existsSync as existsSync11, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
22704
22851
  import path34 from "node:path";
22705
22852
  var repoRoot3 = assetRoot();
22706
22853
  var templatesDir = () => path34.join(repoRoot3, "templates", "hygiene");
22707
22854
  var packageJson = (root) => {
22708
- if (!existsSync10(path34.join(root, "package.json")))
22855
+ if (!existsSync11(path34.join(root, "package.json")))
22709
22856
  return null;
22710
22857
  try {
22711
22858
  return JSON.parse(readFileSync7(path34.join(root, "package.json"), "utf8"));
@@ -22714,7 +22861,7 @@ var packageJson = (root) => {
22714
22861
  }
22715
22862
  };
22716
22863
  var isOpenSource = (root) => {
22717
- if (existsSync10(path34.join(root, "LICENSE")))
22864
+ if (existsSync11(path34.join(root, "LICENSE")))
22718
22865
  return true;
22719
22866
  const pkg = packageJson(root);
22720
22867
  if (pkg && !pkg.private)
@@ -22742,10 +22889,10 @@ var planHygieneFiles = (root, opts = {}) => {
22742
22889
  files.push("LICENSE", "CONTRIBUTING.md");
22743
22890
  const planned = [];
22744
22891
  for (const file of files) {
22745
- if (existsSync10(path34.join(root, file)))
22892
+ if (existsSync11(path34.join(root, file)))
22746
22893
  continue;
22747
22894
  const tpl = path34.join(templatesDir(), file);
22748
- if (!existsSync10(tpl))
22895
+ if (!existsSync11(tpl))
22749
22896
  continue;
22750
22897
  const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path34.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
22751
22898
  planned.push({ path: path34.join(root, file), content });
@@ -23290,7 +23437,7 @@ import path41 from "node:path";
23290
23437
 
23291
23438
  // packages/workit-core/src/git/ops.ts
23292
23439
  import { spawnSync as spawnSync20 } from "node:child_process";
23293
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
23440
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
23294
23441
  import path40 from "node:path";
23295
23442
 
23296
23443
  // packages/workit-core/src/forge/redact.ts
@@ -23339,11 +23486,11 @@ function operationInProgress(cwd) {
23339
23486
  const conflicted = () => git5(cwd, ["ls-files", "-u"]).stdout.trim() !== "";
23340
23487
  for (const [index, [, operation]] of markers.entries()) {
23341
23488
  const marker = found[index];
23342
- if (!marker || !existsSync11(marker))
23489
+ if (!marker || !existsSync12(marker))
23343
23490
  continue;
23344
- if (index === 1 && existsSync11(path40.join(marker, "applying")))
23491
+ if (index === 1 && existsSync12(path40.join(marker, "applying")))
23345
23492
  return { operation: "am", commitAllowed: false, branch: null };
23346
- if (index === 0 && existsSync11(path40.join(marker, "amend")) && !conflicted()) {
23493
+ if (index === 0 && existsSync12(path40.join(marker, "amend")) && !conflicted()) {
23347
23494
  let headName = "";
23348
23495
  try {
23349
23496
  headName = readFileSync9(path40.join(marker, "head-name"), "utf8").trim();
@@ -24638,11 +24785,11 @@ var createStopControl = (root, port) => {
24638
24785
  };
24639
24786
 
24640
24787
  // packages/workit-opencode/src/v2/registry.ts
24641
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
24788
+ import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
24642
24789
  import path45 from "node:path";
24643
24790
 
24644
24791
  // packages/workit-opencode/src/shared/assets.ts
24645
- import { existsSync as existsSync12, realpathSync as realpathSync9 } from "node:fs";
24792
+ import { existsSync as existsSync13, realpathSync as realpathSync9 } from "node:fs";
24646
24793
  import path44 from "node:path";
24647
24794
  import { fileURLToPath as fileURLToPath2 } from "node:url";
24648
24795
  var moduleDir = () => {
@@ -24659,7 +24806,7 @@ var packageRoot2 = () => {
24659
24806
  path44.resolve(dir, "..", ".."),
24660
24807
  path44.resolve(dir, "..", "..", "..")
24661
24808
  ]) {
24662
- if (existsSync12(path44.join(candidate, "package.json")))
24809
+ if (existsSync13(path44.join(candidate, "package.json")))
24663
24810
  return candidate;
24664
24811
  }
24665
24812
  return path44.resolve(dir, "..");
@@ -24667,7 +24814,7 @@ var packageRoot2 = () => {
24667
24814
  var skillsRoot = () => {
24668
24815
  const root = packageRoot2();
24669
24816
  const canonical = path44.resolve(root, "..", "workit-core", "skills");
24670
- if (existsSync12(path44.join(root, "src")) && existsSync12(canonical))
24817
+ if (existsSync13(path44.join(root, "src")) && existsSync13(canonical))
24671
24818
  return canonical;
24672
24819
  return path44.join(root, "assets", "skills");
24673
24820
  };
@@ -24693,7 +24840,7 @@ var parseFrontmatter = (markdown) => {
24693
24840
  };
24694
24841
  var registerSkills = async (ctx) => {
24695
24842
  const skillsDir = skillsRoot();
24696
- if (!existsSync13(skillsDir))
24843
+ if (!existsSync14(skillsDir))
24697
24844
  return new Set;
24698
24845
  const listed = await ctx.skill.list().catch(() => ({ data: [] }));
24699
24846
  const own = (skill) => typeof skill.path === "string" && path45.resolve(skill.path).startsWith(skillsDir + path45.sep);
@@ -24701,7 +24848,7 @@ var registerSkills = async (ctx) => {
24701
24848
  const packaged = new Map;
24702
24849
  for (const id of WORKIT_METHOD_SKILLS) {
24703
24850
  const file = path45.join(skillsDir, id, "SKILL.md");
24704
- if (!userSkills.has(id) && existsSync13(file))
24851
+ if (!userSkills.has(id) && existsSync14(file))
24705
24852
  packaged.set(id, file);
24706
24853
  }
24707
24854
  await ctx.skill.transform((editor) => {
@@ -24747,11 +24894,11 @@ var registerCommands = async (ctx, registeredSkills) => {
24747
24894
  };
24748
24895
 
24749
24896
  // packages/workit-opencode/src/stale-sources.ts
24750
- import { existsSync as existsSync14, readdirSync as readdirSync6 } from "node:fs";
24897
+ import { existsSync as existsSync15, readdirSync as readdirSync6 } from "node:fs";
24751
24898
  import path46 from "node:path";
24752
24899
  import { fileURLToPath as fileURLToPath3 } from "node:url";
24753
24900
  var typescriptFiles = (directory) => {
24754
- if (!existsSync14(directory))
24901
+ if (!existsSync15(directory))
24755
24902
  return [];
24756
24903
  return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
24757
24904
  const file = path46.join(directory, entry.name);
@@ -24765,7 +24912,7 @@ var pluginSourceFiles = [
24765
24912
  path46.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
24766
24913
  ...typescriptFiles(path46.join(packageRoot2(), "..", "workit-core", "src", "core"))
24767
24914
  ])
24768
- ].filter((file) => existsSync14(file)).toSorted();
24915
+ ].filter((file) => existsSync15(file)).toSorted();
24769
24916
 
24770
24917
  // packages/workit-opencode/src/v2/plugin.ts
24771
24918
  var sessionFacts = async (ctx, sessionID) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "8.22.3",
3
+ "version": "8.23.0",
4
4
  "private": false,
5
5
  "description": "Workit OpenCode plugin — native tools, adaptive policy, and sixteen method skills",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "typecheck": "tsc --noEmit"
40
40
  },
41
41
  "devDependencies": {
42
- "@brainervirus/workit-core": "^8.22.3",
42
+ "@brainervirus/workit-core": "^8.23.0",
43
43
  "@opencode/plugin": "2.0.18"
44
44
  },
45
45
  "engines": {