@brainervirus/workit-codex 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workit",
3
- "version": "8.22.3",
3
+ "version": "8.23.0",
4
4
  "description": "Adaptive engineering workflow rails for Codex",
5
5
  "author": {
6
6
  "name": "BrainerVirus"
@@ -1,36 +1,44 @@
1
1
  # Generated by Workit from packages/workit-claude-code/agents/implementer.md; edits are overwritten on the next plugin start.
2
2
  name = "workit-implementer"
3
- description = "Implements one scoped workit brief in an isolated git worktree and reports what changed and how it was checked. Use to delegate a well-defined slice (goal, scope, acceptance, verify, forbidden, report) so it can run in parallel with other work."
4
- developer_instructions = '''You are a workit implementer working in your own git worktree.
3
+ description = "Implements one scoped workit brief in the git worktree its lead made with `workit fanout worktree create`, and reports what changed and how it was checked. Use to delegate a well-defined slice (goal, scope, acceptance, verify, forbidden, report, worktree) so it can run in parallel with other work."
4
+ developer_instructions = '''You are a workit implementer. You work only in the git worktree your brief
5
+ names (its WORKTREE line), which the lead made with
6
+ `workit fanout worktree create <slice>` before spawning you; never in the
7
+ lead's checkout.
5
8
 
6
9
  Refuse to start without a complete brief. It must state: goal, scope (files
7
10
  or areas you may touch), acceptance (observable outcomes), verify (exact
8
11
  commands), timebox, forbidden (what you must not touch or do), report (what to
9
- hand back), standing orders and mode (`new` or `resume`). If a field is
10
- missing, report which and stop.
12
+ hand back), standing orders, mode (`new` or `resume`) and the worktree path.
13
+ If a field is missing, report which and stop.
11
14
 
12
- 1. Your worktree starts on a branch name you did not choose, which may break
13
- the repository's branch policy. First command, `MODE: new`:
14
- `workit git branch <branch> --base <base>` with the brief's branch and base.
15
- `MODE: resume` (you replace a stopped worker): `git switch <branch>` and
16
- continue from its head; never `workit git branch`, the branch exists. A hook denies
17
- protected or non-compliant names; follow the unblock it prints.
18
- 2. Stay inside the scope. Anything outside it is a follow-up in the report,
15
+ 1. Run each git and check command against the worktree, one verb per call:
16
+ `workit --cwd <worktree> git commit …`, `workit --cwd <worktree> check <name>`.
17
+ Never `cd <worktree> && …` chains, `$VAR` paths or compound commands. Edit
18
+ files by their path under the worktree. Workit's hooks deny an edit or a
19
+ mutating git command outside it.
20
+ 2. The worktree is already on the brief's branch. If it is not: `MODE: new`,
21
+ `workit --cwd <worktree> git branch <branch> --base <base>`; `MODE: resume`
22
+ (you replace a stopped worker), `git -C <worktree> switch <branch>` and
23
+ continue from its head. A hook denies protected or non-compliant names;
24
+ follow the unblock it prints.
25
+ 3. Stay inside the scope. Anything outside it is a follow-up in the report,
19
26
  not a diff. Follow the workit-implement skill (`$workit-implement`).
20
- 3. Decide ambiguities yourself and record them:
27
+ 4. Decide ambiguities yourself and record them:
21
28
  `workit ledger ruling "<what>" --why "<why>" --cost-if-wrong "<cost>"`.
22
29
  Stop only for an irreversible or security-sensitive action, or a side effect
23
30
  outside your worktree.
24
- 4. Commit in small steps with
25
- `workit git commit -m "<conventional message>" -- <paths in scope>`.
26
- 5. Run every verify command as `workit check …` and keep the real exit codes.
27
- 6. Never record a verdict on your own work and never pass `--self`: a separate
31
+ 5. Commit in small steps with
32
+ `workit --cwd <worktree> git commit -m "<conventional message>" -- <paths in scope>`.
33
+ 6. Run every verify command as `workit --cwd <worktree> check …` and keep the
34
+ real exit codes.
35
+ 7. Never record a verdict on your own work and never pass `--self`: a separate
28
36
  verifier judges it. Never push, open a PR, rebase or merge unless the brief
29
37
  says so.
30
38
 
31
- Report: branch and head SHA, files changed, each verify command with its exit
32
- code, each acceptance item met or not, rulings recorded, and any deviation from
33
- the brief.
39
+ Report: worktree, branch and head SHA, files changed, each verify command with
40
+ its exit code, each acceptance item met or not, rulings recorded, and any
41
+ deviation from the brief.
34
42
 
35
43
  Codex does not isolate subagents: the lead creates your worktree with `workit fanout worktree create <slice>` and names it in the brief. Work only there, never in the lead's checkout.
36
44
  '''
@@ -6622,7 +6622,7 @@ var require_dist = __commonJS(function(exports, module) {
6622
6622
 
6623
6623
  // packages/workit-codex/scripts/launch-mcp.ts
6624
6624
  import { existsSync as existsSync10, realpathSync as realpathSync7, statSync as statSync3 } from "node:fs";
6625
- import path24 from "node:path";
6625
+ import path25 from "node:path";
6626
6626
  import { fileURLToPath as fileURLToPath3 } from "node:url";
6627
6627
 
6628
6628
  // packages/workit-core/src/core.ts
@@ -27108,6 +27108,9 @@ var EXEC = new Set(["exec", "x", "dlx"]);
27108
27108
 
27109
27109
  // packages/workit-core/src/core/heavy-words.ts
27110
27110
  var HEAVY_WORDS = new Set(("npm npx pnpm pnpx yarn bun bunx deno corepack make gmake docker podman nerdctl " + "python python3 py uv poetry pdm pipenv hatch rye bundle rake nx " + "jest vitest mocha ava tap karma jasmine nyc c8 tsc vue-tsc svelte-check eslint stylelint " + "tslint webpack webpack-cli rollup parcel tsup vite next nuxt nuxi astro ng react-scripts " + "playwright cypress wdio testcafe turbo lerna sonar-scanner sonarless sonarless-mcp go " + "golangci-lint gotestsum staticcheck cargo mvn mvnw gradle gradlew ant sbt bazel bazelisk " + "buck2 dotnet msbuild pytest py.test tox nox mypy pyright pylint coverage maturin pip pip3 " + "rspec rails phpunit pest phpstan psalm composer mix swift xcodebuild flutter dart pod cmake " + "ninja meson ctest scons docker-compose buildah earthly").split(" "));
27111
+ // packages/workit-core/src/hooks/write-gate.ts
27112
+ import path23 from "node:path";
27113
+
27111
27114
  // packages/workit-core/src/core/live-session.ts
27112
27115
  import { spawnSync as spawnSync15 } from "node:child_process";
27113
27116
  var LIVE_SESSION_TTL_MS = 15 * 60000;
@@ -27120,7 +27123,7 @@ var PIDS = new Map;
27120
27123
 
27121
27124
  // packages/workit-core/src/hooks/write-gate.ts
27122
27125
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
27123
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
27126
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
27124
27127
  var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
27125
27128
  var cache2 = new Map;
27126
27129
  var MUTATING_GIT = new Set([
@@ -27147,6 +27150,7 @@ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
27147
27150
  var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
27148
27151
  var PUSHD2 = new Set(["pushd", "push-location"]);
27149
27152
  var POPD2 = new Set(["popd", "pop-location"]);
27153
+ var UNKNOWN_DIR = path23.resolve(path23.parse(process.cwd()).root, ".workit-unknown-dir");
27150
27154
  // packages/workit-core/src/core/skill-manifests.ts
27151
27155
  if (false) {}
27152
27156
 
@@ -27191,6 +27195,19 @@ var sinceStart = new Map;
27191
27195
  // packages/workit-core/src/hooks/cwd-normalize.ts
27192
27196
  var FILTERS = new Set(["tail", "head", "grep"]);
27193
27197
 
27198
+ // packages/workit-core/src/hooks/worktree-confine.ts
27199
+ var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
27200
+ var MUTATING_REMOTE = new Set([
27201
+ "add",
27202
+ "remove",
27203
+ "rm",
27204
+ "rename",
27205
+ "set-url",
27206
+ "set-head",
27207
+ "set-branches",
27208
+ "prune"
27209
+ ]);
27210
+
27194
27211
  // packages/workit-core/src/hooks/handle.ts
27195
27212
  var offered = new Set;
27196
27213
  var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
@@ -27356,14 +27373,14 @@ import {
27356
27373
  writeFileSync as writeFileSync3
27357
27374
  } from "node:fs";
27358
27375
  import os5 from "node:os";
27359
- import path23 from "node:path";
27376
+ import path24 from "node:path";
27360
27377
  var CODEX_AGENT_MARKER = "# Generated by Workit";
27361
27378
  var codexHomeOfInstall = (pluginRoot) => {
27362
- const parts = path23.resolve(pluginRoot).split(path23.sep);
27379
+ const parts = path24.resolve(pluginRoot).split(path24.sep);
27363
27380
  const cache = parts.length - 4;
27364
- return cache >= 2 && parts[cache] === "cache" && parts[cache - 1] === "plugins" ? parts.slice(0, cache - 1).join(path23.sep) || path23.sep : null;
27381
+ return cache >= 2 && parts[cache] === "cache" && parts[cache - 1] === "plugins" ? parts.slice(0, cache - 1).join(path24.sep) || path24.sep : null;
27365
27382
  };
27366
- var codexHomeFor = (pluginRoot, env) => codexHomeOfInstall(pluginRoot) ?? (env.CODEX_HOME || path23.join(env.HOME || os5.homedir(), ".codex"));
27383
+ var codexHomeFor = (pluginRoot, env) => codexHomeOfInstall(pluginRoot) ?? (env.CODEX_HOME || path24.join(env.HOME || os5.homedir(), ".codex"));
27367
27384
  var MARKER_VERSION = /^# Generated by Workit (\d+\.\d+\.\d+)\b/;
27368
27385
  var agentMarkerVersion = (text) => MARKER_VERSION.exec(text)?.[1] ?? null;
27369
27386
  var stampCodexAgent = (content, version) => version && content.startsWith(CODEX_AGENT_MARKER) && !agentMarkerVersion(content) ? `${CODEX_AGENT_MARKER} ${version}${content.slice(CODEX_AGENT_MARKER.length)}` : content;
@@ -27377,7 +27394,7 @@ var versionAtLeast = (version, min) => {
27377
27394
  };
27378
27395
  var pluginVersion = (pluginRoot) => {
27379
27396
  try {
27380
- const version = JSON.parse(readFileSync7(path23.join(pluginRoot, "package.json"), "utf8")).version;
27397
+ const version = JSON.parse(readFileSync7(path24.join(pluginRoot, "package.json"), "utf8")).version;
27381
27398
  return typeof version === "string" && /^\d+\.\d+\.\d+/.test(version) ? version.match(/^\d+\.\d+\.\d+/)[0] : null;
27382
27399
  } catch {
27383
27400
  return null;
@@ -27391,15 +27408,15 @@ var lstatOrNull = (file) => {
27391
27408
  }
27392
27409
  };
27393
27410
  var installCodexAgents = (pluginRoot, codexHome) => {
27394
- const source = path23.join(pluginRoot, "agents");
27411
+ const source = path24.join(pluginRoot, "agents");
27395
27412
  if (!existsSync9(source))
27396
27413
  return [];
27397
27414
  const version = pluginVersion(pluginRoot);
27398
- const target = path23.join(codexHome, "agents");
27415
+ const target = path24.join(codexHome, "agents");
27399
27416
  const results = [];
27400
27417
  for (const file of readdirSync5(source).filter((name) => name.endsWith(".toml")).toSorted()) {
27401
- const content = stampCodexAgent(readFileSync7(path23.join(source, file), "utf8"), version);
27402
- const dest = path23.join(target, file);
27418
+ const content = stampCodexAgent(readFileSync7(path24.join(source, file), "utf8"), version);
27419
+ const dest = path24.join(target, file);
27403
27420
  const stat = lstatOrNull(dest);
27404
27421
  if (stat?.isSymbolicLink()) {
27405
27422
  results.push({ file: dest, action: "skipped-symlink" });
@@ -27435,7 +27452,7 @@ var installCodexAgents = (pluginRoot, codexHome) => {
27435
27452
  results.push({ file: dest, action: "kept-newer-version" });
27436
27453
  continue;
27437
27454
  }
27438
- const temp = path23.join(target, `.${file}.${process.pid}.${Date.now()}.tmp`);
27455
+ const temp = path24.join(target, `.${file}.${process.pid}.${Date.now()}.tmp`);
27439
27456
  writeFileSync3(temp, content, { flag: "wx" });
27440
27457
  try {
27441
27458
  renameSync2(temp, dest);
@@ -27457,8 +27474,8 @@ var codexQualification = (host) => host === "codex_cli" ? { surface: host, cli:
27457
27474
  var resolveCodexWorkspaceRoot = (pluginRoot, env) => {
27458
27475
  const explicit = env.WORKFLOW_WORKSPACE_ROOT;
27459
27476
  const inherited = env.PWD;
27460
- const candidate = explicit ?? (inherited && path24.resolve(inherited) !== path24.resolve(pluginRoot) ? inherited : undefined);
27461
- if (!candidate || !path24.isAbsolute(candidate) || !existsSync10(candidate))
27477
+ const candidate = explicit ?? (inherited && path25.resolve(inherited) !== path25.resolve(pluginRoot) ? inherited : undefined);
27478
+ if (!candidate || !path25.isAbsolute(candidate) || !existsSync10(candidate))
27462
27479
  return null;
27463
27480
  try {
27464
27481
  if (!statSync3(candidate).isDirectory())
@@ -27497,7 +27514,7 @@ var codexContextProvider = (host = detectCodexSurface(process.env), root) => ({
27497
27514
  };
27498
27515
  }
27499
27516
  });
27500
- var pluginRoot = () => path24.resolve(path24.dirname(fileURLToPath3(import.meta.url)), "..");
27517
+ var pluginRoot = () => path25.resolve(path25.dirname(fileURLToPath3(import.meta.url)), "..");
27501
27518
  var syncCodexAgents = (root) => {
27502
27519
  const codexHome = codexHomeOfInstall(root);
27503
27520
  if (!codexHome)
@@ -17683,6 +17683,24 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
17683
17683
  }
17684
17684
  return found;
17685
17685
  }
17686
+ var WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
17687
+ function skipWorkitGlobals(args) {
17688
+ let cwd = null;
17689
+ let at = 0;
17690
+ for (;at < args.length; at++) {
17691
+ const match = WORKIT_GLOBAL.exec(args[at]);
17692
+ if (match?.[1] === "=") {
17693
+ if (args[at].startsWith("--cwd="))
17694
+ cwd = args[at].slice(6);
17695
+ } else if (match && args[at + 1] !== undefined) {
17696
+ if (args[at] === "--cwd")
17697
+ cwd = args[at + 1];
17698
+ at++;
17699
+ } else if (args[at] !== "--json")
17700
+ break;
17701
+ }
17702
+ return { rest: args.slice(at), cwd };
17703
+ }
17686
17704
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
17687
17705
  var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
17688
17706
  var pushDestination = (destination) => {
@@ -19443,7 +19461,7 @@ var splitDenial = (plan) => ({
19443
19461
  unblock: plan.unblock
19444
19462
  });
19445
19463
  // packages/workit-core/src/hooks/write-gate.ts
19446
- import { realpathSync as realpathSync5 } from "node:fs";
19464
+ import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
19447
19465
  import os9 from "node:os";
19448
19466
  import path24 from "node:path";
19449
19467
 
@@ -19672,7 +19690,7 @@ var exempt = (cwd, root, file, plans) => {
19672
19690
  const segments = posix.split("/");
19673
19691
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
19674
19692
  };
19675
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
19693
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
19676
19694
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
19677
19695
  var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
19678
19696
  var wrappedScript = (input) => {
@@ -19684,9 +19702,9 @@ var wrappedScript = (input) => {
19684
19702
  return null;
19685
19703
  if ("script" in tail)
19686
19704
  return tail.script;
19687
- const [shell, flag, script] = tail.argv;
19688
- if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
19689
- return script ?? null;
19705
+ const script = shellScript2(tail.argv);
19706
+ if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
19707
+ return script;
19690
19708
  return tail.argv.map(quoteWord2).join(" ");
19691
19709
  };
19692
19710
  var editsOf = (input) => {
@@ -19765,6 +19783,94 @@ var editsOf = (input) => {
19765
19783
  return [];
19766
19784
  }
19767
19785
  };
19786
+ var shellScript2 = (words) => {
19787
+ if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
19788
+ return null;
19789
+ for (let index = 1;index < words.length; index++) {
19790
+ const word = words[index];
19791
+ if (/^-[a-z]*c$/.test(word))
19792
+ return words[index + 1] ?? null;
19793
+ if (/^[-+][oO]$/.test(word))
19794
+ index++;
19795
+ else if (!/^[-+]/.test(word))
19796
+ return null;
19797
+ }
19798
+ return null;
19799
+ };
19800
+ var XARGS_VALUE = /^-[IinLPdsEa]$/;
19801
+ var runsWriter = (words) => {
19802
+ const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
19803
+ const edits = editsOf(filled);
19804
+ return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
19805
+ };
19806
+ var opaqueWrites = (input) => {
19807
+ let words = input;
19808
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
19809
+ if (words[0] === "xargs") {
19810
+ let at = 1;
19811
+ while (words[at]?.startsWith("-"))
19812
+ at += XARGS_VALUE.test(words[at]) ? 2 : 1;
19813
+ return runsWriter(words.slice(at)) ? ["."] : null;
19814
+ }
19815
+ words = words.slice(1);
19816
+ }
19817
+ const [verb, ...args] = words;
19818
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
19819
+ const files = operands(args);
19820
+ const has = (pattern) => args.some((arg) => pattern.test(arg));
19821
+ const listed = (named) => named.length ? named : ["."];
19822
+ if (name === "unlink" || name === "shred")
19823
+ return listed(files);
19824
+ if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
19825
+ return listed(has(/^--reference/) ? files : files.slice(1));
19826
+ if (name === "rsync") {
19827
+ const dest = files.at(-1);
19828
+ const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
19829
+ return dest === undefined ? ["."] : remote ? [] : [dest];
19830
+ }
19831
+ if (name === "find") {
19832
+ const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
19833
+ const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
19834
+ const end = args.findIndex((arg) => /^[-(!]/.test(arg));
19835
+ return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
19836
+ }
19837
+ if (/^g?awk$/.test(name)) {
19838
+ if (!has(/^-i|^--include/))
19839
+ return null;
19840
+ const named = [];
19841
+ let program = !has(/^-f$|^--file/);
19842
+ for (let index = 0;index < args.length; index++)
19843
+ if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
19844
+ index++;
19845
+ else if (args[index].startsWith("-"))
19846
+ continue;
19847
+ else if (program)
19848
+ program = false;
19849
+ else
19850
+ named.push(args[index]);
19851
+ return listed(named);
19852
+ }
19853
+ if (name === "perl")
19854
+ return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
19855
+ if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
19856
+ return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
19857
+ return shellScript2(words) === null ? null : ["."];
19858
+ };
19859
+ var linkSources = (args, at) => {
19860
+ const names = operands([...args]);
19861
+ const into = args.indexOf("-t");
19862
+ const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
19863
+ const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
19864
+ const target = dest === undefined ? null : at(dest);
19865
+ const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
19866
+ const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync7(target) || /[\\/]$/.test(dest ?? "") ? target : path24.dirname(target);
19867
+ return sources.flatMap((source) => {
19868
+ if (symbolic && base !== null && !path24.isAbsolute(homeExpanded(source)))
19869
+ return [path24.resolve(base, source)];
19870
+ const resolved = at(source);
19871
+ return resolved ? [resolved] : [];
19872
+ });
19873
+ };
19768
19874
  var shellWrites = (command, depth = 0) => {
19769
19875
  const targets = [];
19770
19876
  let unknown = false;
@@ -19874,11 +19980,15 @@ var gitMutationDirs = (cwd, command, dialect) => {
19874
19980
  var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
19875
19981
  var PUSHD2 = new Set(["pushd", "push-location"]);
19876
19982
  var POPD2 = new Set(["popd", "pop-location"]);
19877
- var cdInto = (dir, words) => {
19983
+ var UNKNOWN_DIR = path24.resolve(path24.parse(process.cwd()).root, ".workit-unknown-dir");
19984
+ var cdInto = (dir, words, strict = false) => {
19985
+ const lost = strict ? UNKNOWN_DIR : undefined;
19878
19986
  const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
19879
19987
  const target = named === undefined ? undefined : homeExpanded(named);
19880
- if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
19881
- return;
19988
+ if (target === "-" || target && /^~|[$`*?]/.test(target))
19989
+ return lost;
19990
+ if (dir === undefined)
19991
+ return lost;
19882
19992
  return target === undefined ? os9.homedir() : path24.resolve(dir, target);
19883
19993
  };
19884
19994
  var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
@@ -19886,7 +19996,7 @@ var homeExpanded = (file) => {
19886
19996
  const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
19887
19997
  return match ? (process.env.HOME || os9.homedir()) + file.slice(match[0].length) : file;
19888
19998
  };
19889
- var segmentTargets = (cwd, command, dialect, depth = 0) => {
19999
+ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
19890
20000
  const targets = [];
19891
20001
  let dir = cwd;
19892
20002
  let stack = [];
@@ -19910,21 +20020,35 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
19910
20020
  const words = lead < 0 ? [] : segment.words.slice(lead);
19911
20021
  const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
19912
20022
  if (CD2.has(name)) {
19913
- dir = cdInto(dir, words);
20023
+ dir = cdInto(dir, words, strict);
19914
20024
  continue;
19915
20025
  }
19916
20026
  if (PUSHD2.has(name)) {
19917
20027
  stack.push(dir);
19918
- dir = cdInto(dir, words);
20028
+ dir = cdInto(dir, words, strict);
19919
20029
  continue;
19920
20030
  }
19921
20031
  if (POPD2.has(name)) {
19922
- dir = stack.length ? stack.pop() : undefined;
20032
+ dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
19923
20033
  continue;
19924
20034
  }
19925
- const inner = depth < 4 ? wrappedScript(words) : null;
20035
+ const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
19926
20036
  if (inner !== null && dir !== undefined)
19927
- targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
20037
+ targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
20038
+ if (strict && dir !== undefined) {
20039
+ const runIn = dir;
20040
+ for (const sub of depth < 4 ? segment.subs ?? [] : [])
20041
+ targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
20042
+ const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
20043
+ for (const file of opaque ?? []) {
20044
+ const resolved = at(file);
20045
+ if (resolved)
20046
+ targets.push(file === "." ? path24.join(resolved, DIR_TARGET) : resolved);
20047
+ }
20048
+ const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
20049
+ if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
20050
+ targets.push(...linkSources(words.slice(ln + 1), at));
20051
+ }
19928
20052
  for (const file of segment.redirects)
19929
20053
  if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
19930
20054
  const resolved = at(file);
@@ -19941,11 +20065,29 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
19941
20065
  if (resolved)
19942
20066
  targets.push(resolved);
19943
20067
  }
19944
- 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), "")))
19945
- targets.push(path24.join(dir, DIR_TARGET));
20068
+ if (dir !== undefined)
20069
+ for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
20070
+ const { at: runsIn, args } = workitCwd(dir, invocation);
20071
+ if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
20072
+ targets.push(path24.join(runsIn, DIR_TARGET));
20073
+ }
19946
20074
  }
19947
20075
  return targets;
19948
20076
  };
20077
+ var workitCwd = (dir, args) => {
20078
+ const { rest, cwd } = skipWorkitGlobals(args);
20079
+ return { at: cwd === null ? dir : path24.resolve(dir, homeExpanded(cwd)), args: rest };
20080
+ };
20081
+ var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
20082
+ try {
20083
+ return [
20084
+ ...segmentTargets(cwd, command, dialect, 0, strict),
20085
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path24.join(dir, DIR_TARGET))
20086
+ ];
20087
+ } catch {
20088
+ return [];
20089
+ }
20090
+ };
19949
20091
  var overridden = (command, dialect) => {
19950
20092
  const segments = segmentsOf(command, dialect);
19951
20093
  if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
@@ -20051,16 +20193,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20051
20193
  } catch {}
20052
20194
  const writes = shellWrites(command);
20053
20195
  let targets = [];
20054
- if (owner) {
20055
- try {
20056
- targets = [
20057
- ...segmentTargets(cwd, command, dialect),
20058
- ...gitMutationDirs(cwd, command, dialect).map((dir) => path24.join(dir, DIR_TARGET))
20059
- ];
20060
- } catch {
20061
- targets = [];
20062
- }
20063
- }
20196
+ if (owner)
20197
+ targets = shellMutationTargets(cwd, command, dialect);
20064
20198
  if (owner && targets.length) {
20065
20199
  const live = liveGate(owner, targets, deps);
20066
20200
  if (live.kind !== "none") {
@@ -20527,13 +20661,13 @@ ${line}` };
20527
20661
  return decision;
20528
20662
  };
20529
20663
  // packages/workit-core/src/hooks/hosts/fields.ts
20530
- import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20664
+ import { existsSync as existsSync8, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20531
20665
  import path25 from "node:path";
20532
20666
  var isRecord5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
20533
20667
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
20534
20668
  var optionalText = (value) => typeof value === "string" ? value : null;
20535
20669
  var existingDirectory = (value) => {
20536
- if (!nonEmpty2(value) || !path25.isAbsolute(value) || !existsSync7(value))
20670
+ if (!nonEmpty2(value) || !path25.isAbsolute(value) || !existsSync8(value))
20537
20671
  return null;
20538
20672
  try {
20539
20673
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -20736,9 +20870,266 @@ var cwdRewrite = (command) => {
20736
20870
  return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
20737
20871
  };
20738
20872
 
20873
+ // packages/workit-core/src/hooks/worktree-confine.ts
20874
+ import { createHash as createHash16 } from "node:crypto";
20875
+ import fs20 from "node:fs";
20876
+ import path27 from "node:path";
20877
+ var FANOUT_TOKEN_FILE = "workit-fanout-token";
20878
+ var NONE5 = { kind: "none" };
20879
+ var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
20880
+ var canonical3 = (file) => {
20881
+ let rest = "";
20882
+ for (let dir = path27.resolve(file);; dir = path27.dirname(dir)) {
20883
+ try {
20884
+ return path27.join(fs20.realpathSync.native(dir), rest);
20885
+ } catch {
20886
+ if (path27.dirname(dir) === dir)
20887
+ return path27.resolve(file);
20888
+ rest = rest ? path27.join(path27.basename(dir), rest) : path27.basename(dir);
20889
+ }
20890
+ }
20891
+ };
20892
+ var within = (child, parent) => {
20893
+ const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
20894
+ const relative = path27.relative(fold(parent), fold(child));
20895
+ return relative === "" || !relative.startsWith("..") && !path27.isAbsolute(relative);
20896
+ };
20897
+ var checkoutOf2 = (file) => {
20898
+ for (let dir = canonical3(file);; dir = path27.dirname(dir)) {
20899
+ const dotGit = path27.join(dir, ".git");
20900
+ if (fs20.existsSync(dotGit)) {
20901
+ if (!fs20.statSync(dotGit).isFile())
20902
+ return { root: dir, workit: false };
20903
+ const admin = /^gitdir:\s*(.+)$/mu.exec(fs20.readFileSync(dotGit, "utf8"))?.[1]?.trim();
20904
+ return {
20905
+ root: dir,
20906
+ workit: Boolean(admin) && fs20.existsSync(path27.join(path27.resolve(dir, admin), FANOUT_TOKEN_FILE))
20907
+ };
20908
+ }
20909
+ if (path27.dirname(dir) === dir)
20910
+ return null;
20911
+ }
20912
+ };
20913
+ var workitWorktreeOf = (file) => {
20914
+ try {
20915
+ const checkout = checkoutOf2(file);
20916
+ return checkout?.workit ? checkout.root : null;
20917
+ } catch {
20918
+ return null;
20919
+ }
20920
+ };
20921
+ var bindingDir = () => path27.join(resolveStateDir(), "worktree-bind");
20922
+ var bindingFile = (session, agentId) => path27.join(bindingDir(), `${createHash16("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
20923
+ var readBinding = (file) => {
20924
+ try {
20925
+ const value = JSON.parse(fs20.readFileSync(file, "utf8"));
20926
+ return typeof value.worktree === "string" ? value : null;
20927
+ } catch {
20928
+ return null;
20929
+ }
20930
+ };
20931
+ var boundWorktree = (input) => {
20932
+ const agentId = input.session.agentId;
20933
+ if (!agentId)
20934
+ return null;
20935
+ const binding = readBinding(bindingFile(input.session.id, agentId));
20936
+ return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
20937
+ };
20938
+ var bind = (input, worktree) => {
20939
+ const agentId = input.session.agentId;
20940
+ const file = bindingFile(input.session.id, agentId);
20941
+ const binding = {
20942
+ worktree,
20943
+ session: input.session.id,
20944
+ agentId,
20945
+ at: new Date().toISOString()
20946
+ };
20947
+ fs20.mkdirSync(path27.dirname(file), { recursive: true });
20948
+ fs20.writeFileSync(file, JSON.stringify(binding));
20949
+ try {
20950
+ if (inWorkitWorkspace(input.cwd))
20951
+ appendHookObserved(input.cwd, {
20952
+ type: "worktree.bound",
20953
+ actor: { host: input.host, session: input.session.id, agentId },
20954
+ agentType: input.session.agentType,
20955
+ worktree
20956
+ });
20957
+ } catch {}
20958
+ };
20959
+ var hostIsolated = (cwd, agentId) => {
20960
+ try {
20961
+ if (agentId === null)
20962
+ return false;
20963
+ const checkout = checkoutOf2(cwd);
20964
+ if (!checkout)
20965
+ return false;
20966
+ const dotGit = path27.join(checkout.root, ".git");
20967
+ if (!fs20.statSync(dotGit).isFile())
20968
+ return false;
20969
+ const admin = /^gitdir:\s*(.+)$/mu.exec(fs20.readFileSync(dotGit, "utf8"))?.[1]?.trim();
20970
+ if (!admin)
20971
+ return false;
20972
+ const adminDir = path27.resolve(checkout.root, admin);
20973
+ const back = fs20.readFileSync(path27.join(adminDir, "gitdir"), "utf8").trim();
20974
+ if (path27.relative(canonical3(path27.resolve(adminDir, back)), canonical3(dotGit)) !== "")
20975
+ return false;
20976
+ const common = path27.join(adminDir, "commondir");
20977
+ const gitDir = fs20.existsSync(common) ? path27.resolve(adminDir, fs20.readFileSync(common, "utf8").trim()) : adminDir;
20978
+ const expected = path27.join(path27.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
20979
+ return path27.relative(canonical3(expected), checkout.root) === "";
20980
+ } catch {
20981
+ return false;
20982
+ }
20983
+ };
20984
+ var shown = (target) => path27.basename(target) === ".workit-live-target" ? path27.dirname(target) : target;
20985
+ var unboundDeny = (input, target) => {
20986
+ const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree> git commit -m … -- <paths>`, `workit --cwd <worktree> check <name>`). If your brief names no worktree, stop and report that to the lead.";
20987
+ return {
20988
+ kind: "deny",
20989
+ reason: `workit worktree confinement: implementer ${input.session.agentId} is not working in a Workit worktree yet, and ${shown(target)} is outside every worktree \`workit fanout worktree create\` made, so it would change the lead's checkout. ${unblock}`,
20990
+ unblock
20991
+ };
20992
+ };
20993
+ var outsideDeny = (input, worktree, target) => {
20994
+ const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree} git commit -m … -- <paths>\`, \`workit --cwd ${worktree} check <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
20995
+ return {
20996
+ kind: "deny",
20997
+ reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
20998
+ unblock
20999
+ };
21000
+ };
21001
+ var unknownDirDeny = (input, worktree) => {
21002
+ const wt = worktree ?? "<worktree>";
21003
+ const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
21004
+ return {
21005
+ kind: "deny",
21006
+ reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
21007
+ unblock
21008
+ };
21009
+ };
21010
+ function confineImplementer(input, targets) {
21011
+ try {
21012
+ if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
21013
+ return NONE5;
21014
+ if (targets.some((target) => within(path27.resolve(target), UNKNOWN_DIR)))
21015
+ return unknownDirDeny(input, boundWorktree(input));
21016
+ const bindings = canonical3(bindingDir());
21017
+ const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
21018
+ if (judged.length === 0)
21019
+ return NONE5;
21020
+ let worktree = boundWorktree(input);
21021
+ if (!worktree) {
21022
+ for (const { real } of judged) {
21023
+ worktree = within(real, bindings) ? null : workitWorktreeOf(real);
21024
+ if (worktree)
21025
+ break;
21026
+ }
21027
+ if (!worktree)
21028
+ return unboundDeny(input, judged[0].named);
21029
+ bind(input, worktree);
21030
+ }
21031
+ const root = canonical3(worktree);
21032
+ const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
21033
+ return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
21034
+ } catch {
21035
+ return NONE5;
21036
+ }
21037
+ }
21038
+ var MUTATING_REMOTE = new Set([
21039
+ "add",
21040
+ "remove",
21041
+ "rm",
21042
+ "rename",
21043
+ "set-url",
21044
+ "set-head",
21045
+ "set-branches",
21046
+ "prune"
21047
+ ]);
21048
+ var CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
21049
+ var CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
21050
+ var CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
21051
+ var leadOwnedGit = (args) => {
21052
+ const [sub = "", ...rest] = args;
21053
+ if (rest.some((arg) => arg === "--help" || arg === "-h"))
21054
+ return false;
21055
+ const names = rest.filter((arg) => !arg.startsWith("-"));
21056
+ const flag = (pattern) => rest.some((arg) => pattern.test(arg));
21057
+ switch (sub) {
21058
+ case "worktree":
21059
+ return names[0] !== undefined && names[0] !== "list";
21060
+ case "update-ref":
21061
+ return true;
21062
+ case "symbolic-ref":
21063
+ return names.length > 1 || flag(/^(?:-d|--delete)$/);
21064
+ case "branch":
21065
+ return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
21066
+ case "tag":
21067
+ return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
21068
+ case "push": {
21069
+ if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
21070
+ return true;
21071
+ const refspecs = names.slice(1);
21072
+ return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
21073
+ }
21074
+ case "replace":
21075
+ case "filter-branch":
21076
+ case "gc":
21077
+ case "prune":
21078
+ case "repack":
21079
+ case "sparse-checkout":
21080
+ return true;
21081
+ case "notes":
21082
+ case "reflog":
21083
+ case "submodule":
21084
+ return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
21085
+ case "stash":
21086
+ return names[0] === "drop" || names[0] === "clear";
21087
+ case "remote":
21088
+ return MUTATING_REMOTE.has(names[0] ?? "");
21089
+ case "config": {
21090
+ if (rest.some((arg) => CONFIG_READ.test(arg)))
21091
+ return false;
21092
+ if (rest.some((arg) => CONFIG_WRITE.test(arg)))
21093
+ return true;
21094
+ let operands = 0;
21095
+ for (let index = 0;index < rest.length; index++)
21096
+ if (CONFIG_VALUE.test(rest[index]))
21097
+ index++;
21098
+ else if (!rest[index].startsWith("-"))
21099
+ operands++;
21100
+ return operands > 1;
21101
+ }
21102
+ default:
21103
+ return false;
21104
+ }
21105
+ };
21106
+ var leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
21107
+ if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
21108
+ return false;
21109
+ const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
21110
+ return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
21111
+ });
21112
+ var leadOwnedDeny = (input, command) => {
21113
+ const unblock = "Worktrees, branches other than your own, refs, tags, remotes, git config, the shared stash and the fan-out plan belong to the lead: report what you need changed as a follow-up instead.";
21114
+ return {
21115
+ kind: "deny",
21116
+ reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
21117
+ unblock
21118
+ };
21119
+ };
21120
+ function confineImplementerShell(input, command, dialect = "posix") {
21121
+ try {
21122
+ if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
21123
+ return NONE5;
21124
+ if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
21125
+ return leadOwnedDeny(input, command);
21126
+ } catch {}
21127
+ return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
21128
+ }
21129
+
20739
21130
  // packages/workit-core/src/hooks/handle.ts
20740
21131
  var usable2 = (value) => value === "native" || value === "partial";
20741
- var NONE5 = { kind: "none" };
21132
+ var NONE6 = { kind: "none" };
20742
21133
  var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
20743
21134
  var offered = new Set;
20744
21135
  var ROLES = {
@@ -20751,6 +21142,7 @@ var workitRole = (descriptor, agentType) => {
20751
21142
  const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
20752
21143
  return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
20753
21144
  };
21145
+ var implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer";
20754
21146
  var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
20755
21147
  var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
20756
21148
  var isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`;
@@ -20758,7 +21150,7 @@ var subagentStartText = (input, descriptor, event) => {
20758
21150
  const session = input.session.id ?? null;
20759
21151
  const role = workitRole(descriptor, event.agentType);
20760
21152
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
20761
- const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
21153
+ const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
20762
21154
  const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
20763
21155
  if (role === "implementer" && isolated)
20764
21156
  return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${isolatedGit(input.cwd)} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
@@ -20766,7 +21158,7 @@ var subagentStartText = (input, descriptor, event) => {
20766
21158
  return `${isolatedGit(input.cwd)}
20767
21159
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
20768
21160
  if (role === "implementer")
20769
- 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.`;
21161
+ return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree> git commit -m "<message>" -- <paths>\`, \`workit --cwd <worktree> check <name>\`. The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit --cwd <worktree> git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
20770
21162
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
20771
21163
  if (role !== "judge" || !event.agentId)
20772
21164
  return readOnly;
@@ -20797,7 +21189,7 @@ function decide(input, deps) {
20797
21189
  switch (event.kind) {
20798
21190
  case "session.start": {
20799
21191
  if (!usable2(descriptor.context.sessionStart))
20800
- return NONE5;
21192
+ return NONE6;
20801
21193
  const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
20802
21194
  const offer = event.source === "startup" && (key === null || !offered.has(key));
20803
21195
  if (offer && key !== null)
@@ -20812,26 +21204,31 @@ function decide(input, deps) {
20812
21204
  }
20813
21205
  case "context.turn": {
20814
21206
  if (!usable2(descriptor.context.perTurn))
20815
- return NONE5;
21207
+ return NONE6;
20816
21208
  const current = turnContextText(input, descriptor);
20817
21209
  const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
20818
- return withContextLine(text ? { kind: "context", text } : NONE5, promptNudge(input, event.prompt));
21210
+ return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
20819
21211
  }
20820
21212
  case "shell.pre": {
20821
21213
  const skillRead = skillReadIn(event.command, event.dialect);
20822
21214
  if (skillRead)
20823
21215
  recordSkillLoad(input, skillRead, "read");
20824
21216
  const canDeny = usable2(descriptor.shellPolicy.deny);
20825
- const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
21217
+ const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
20826
21218
  if (policy.kind !== "none")
20827
21219
  return policy;
20828
21220
  const postTool = usable2(descriptor.events["shell.post"].support);
20829
21221
  if (!postTool)
20830
21222
  settlePendingCommit(input);
20831
- const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
21223
+ const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
20832
21224
  if (raw.kind === "deny")
20833
21225
  return raw;
20834
- const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
21226
+ if (canDeny && implementerAgent(input, descriptor)) {
21227
+ const confined = confineImplementerShell(input, event.command, event.dialect);
21228
+ if (confined.kind === "deny")
21229
+ return confined;
21230
+ }
21231
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
20835
21232
  if (gate.kind === "deny")
20836
21233
  return gate;
20837
21234
  const plan = heavyGate(input.cwd, event.command, {
@@ -20854,10 +21251,10 @@ function decide(input, deps) {
20854
21251
  command: rewrittenTo,
20855
21252
  input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
20856
21253
  text
20857
- } : NONE5;
21254
+ } : NONE6;
20858
21255
  if (descriptor.transport !== "hook-process")
20859
21256
  return rewritten(null);
20860
- const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
21257
+ const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
20861
21258
  if (!lease)
20862
21259
  return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
20863
21260
  return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
@@ -20866,9 +21263,17 @@ function decide(input, deps) {
20866
21263
  case "shell.post":
20867
21264
  if (usable2(descriptor.events["shell.post"].support))
20868
21265
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
20869
- return NONE5;
20870
- case "write.pre":
20871
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
21266
+ return NONE6;
21267
+ case "write.pre": {
21268
+ if (!usable2(descriptor.events["write.pre"].support))
21269
+ return NONE6;
21270
+ if (implementerAgent(input, descriptor)) {
21271
+ const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
21272
+ if (confined.kind === "deny")
21273
+ return confined;
21274
+ }
21275
+ return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
21276
+ }
20872
21277
  case "subagent.start":
20873
21278
  if (workitRole(descriptor, event.agentType) === "judge")
20874
21279
  trackJudge(input, event.agentId, true);
@@ -20885,22 +21290,22 @@ function decide(input, deps) {
20885
21290
  case "tool.pre":
20886
21291
  if (event.skill)
20887
21292
  recordSkillLoad(input, event.skill, "tool");
20888
- return NONE5;
21293
+ return NONE6;
20889
21294
  case "stop":
20890
- return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE5;
21295
+ return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
20891
21296
  case "subagent.stop":
20892
21297
  trackJudge(input, event.agentId, false);
20893
- return NONE5;
21298
+ return NONE6;
20894
21299
  case "prompt.submit":
20895
- return NONE5;
21300
+ return NONE6;
20896
21301
  }
20897
21302
  }
20898
21303
  var failureDecision = (descriptor, event, error) => {
20899
21304
  if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
20900
- return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE5;
21305
+ return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE6;
20901
21306
  if (event === "session.start" || event === "subagent.start")
20902
21307
  return { kind: "context", text: `[workit diagnostic: ${error}]` };
20903
- return NONE5;
21308
+ return NONE6;
20904
21309
  };
20905
21310
  var dispatchHook = (adapter, raw, env = process.env) => {
20906
21311
  const parsed = adapter.parse(raw, env);
@@ -20926,7 +21331,7 @@ var dispatchHook = (adapter, raw, env = process.env) => {
20926
21331
  };
20927
21332
  // packages/workit-core/src/hooks/deadline.ts
20928
21333
  import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
20929
- import path27 from "node:path";
21334
+ import path28 from "node:path";
20930
21335
  var HOOK_SKIP_TEXT = "workit context skipped: host under load";
20931
21336
  var HOOK_DEADLINE_MS = 8000;
20932
21337
  var HOST_DEADLINES = {
@@ -20944,7 +21349,7 @@ var hookDeadlineMs = (env, host = "", native) => {
20944
21349
  const row = HOST_DEADLINES[host];
20945
21350
  return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
20946
21351
  };
20947
- var skipsFile = (stateDir) => path27.join(stateDir, HOOK_SKIPS_FILE);
21352
+ var skipsFile = (stateDir) => path28.join(stateDir, HOOK_SKIPS_FILE);
20948
21353
  var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
20949
21354
  try {
20950
21355
  mkdirSync3(stateDir, { recursive: true });
@@ -20962,7 +21367,7 @@ var QUIET = new Set([
20962
21367
  ]);
20963
21368
  var skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT };
20964
21369
  var TIMED_OUT = Symbol("timed out");
20965
- var within = async (work, ms) => {
21370
+ var within2 = async (work, ms) => {
20966
21371
  let timer;
20967
21372
  const expired = new Promise((resolve) => {
20968
21373
  timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
@@ -20993,7 +21398,7 @@ var readWithin = async (stdin, budget) => {
20993
21398
  text += String(chunk);
20994
21399
  return text;
20995
21400
  })();
20996
- const text = await within(read, budget.startedAt + budget.deadlineMs - budget.now());
21401
+ const text = await within2(read, budget.startedAt + budget.deadlineMs - budget.now());
20997
21402
  return isTimedOut(text) ? null : text;
20998
21403
  };
20999
21404
  var note = (adapter, raw, env, budget, outcome, stage) => {
@@ -21022,7 +21427,7 @@ var dispatchWithin = async (adapter, raw, env, budget, dispatch = dispatchHook)
21022
21427
  if (budget.host !== null && typeof native === "string")
21023
21428
  budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
21024
21429
  const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
21025
- const result = remaining() > 0 ? await within(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
21430
+ const result = remaining() > 0 ? await within2(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
21026
21431
  if (isTimedOut(result))
21027
21432
  return skippedHook(adapter, raw, env, budget, "dispatch");
21028
21433
  if (remaining() < 0)
@@ -21588,8 +21993,8 @@ var codexAdapter = {
21588
21993
  };
21589
21994
 
21590
21995
  // packages/workit-core/src/hooks/hosts/cursor.ts
21591
- import fs20 from "node:fs";
21592
- import path28 from "node:path";
21996
+ import fs21 from "node:fs";
21997
+ import path29 from "node:path";
21593
21998
  var undocumented = { support: "undocumented", native: null };
21594
21999
  var none = { support: "none", native: null };
21595
22000
  var CURSOR_DESCRIPTOR = {
@@ -21786,17 +22191,17 @@ var protocolEvent2 = (input) => {
21786
22191
  };
21787
22192
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
21788
22193
  var lastAssistantText = (file) => {
21789
- if (!file || !path28.isAbsolute(file))
22194
+ if (!file || !path29.isAbsolute(file))
21790
22195
  return null;
21791
22196
  try {
21792
- const size = fs20.statSync(file).size;
22197
+ const size = fs21.statSync(file).size;
21793
22198
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21794
22199
  const buffer = Buffer.alloc(size - start);
21795
- const fd = fs20.openSync(file, "r");
22200
+ const fd = fs21.openSync(file, "r");
21796
22201
  try {
21797
- fs20.readSync(fd, buffer, 0, buffer.length, start);
22202
+ fs21.readSync(fd, buffer, 0, buffer.length, start);
21798
22203
  } finally {
21799
- fs20.closeSync(fd);
22204
+ fs21.closeSync(fd);
21800
22205
  }
21801
22206
  const lines = buffer.toString("utf8").split(`
21802
22207
  `);
@@ -21823,7 +22228,7 @@ var lastAssistantText = (file) => {
21823
22228
  return null;
21824
22229
  }
21825
22230
  };
21826
- var within2 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path28.sep) ? root : `${root}${path28.sep}`);
22231
+ var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path29.sep) ? root : `${root}${path29.sep}`);
21827
22232
  var hookCwd = (input) => {
21828
22233
  const cwd = existingDirectory(input.cwd);
21829
22234
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -21832,7 +22237,7 @@ var hookCwd = (input) => {
21832
22237
  };
21833
22238
  var cursorWorkspaceRoot = (input) => {
21834
22239
  const cwd = existingDirectory(input.cwd);
21835
- return cwd && input.workspace_roots.find((root) => within2(root, cwd)) || input.workspace_roots[0];
22240
+ return cwd && input.workspace_roots.find((root) => within3(root, cwd)) || input.workspace_roots[0];
21836
22241
  };
21837
22242
  var cursorDeny = (reason) => ({
21838
22243
  permission: "deny",
@@ -28,6 +28,8 @@ var quietEvent = () => {
28
28
  const tool = value.tool_name.toLowerCase();
29
29
  if (event === "PreToolUse" && WRITE_TOOLS.has(tool.replace(/[_-]/g, "")))
30
30
  return null;
31
+ if (event === "PreToolUse" && value.agent_type === "workit-implementer")
32
+ return null;
31
33
  const shell = SHELL_TOOLS.has(tool);
32
34
  const command = value.tool_input?.command;
33
35
  const text = Array.isArray(command) ? command.join(" ") : String(command ?? "");
@@ -47,6 +49,6 @@ if (quiet)
47
49
  process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
48
50
  `);
49
51
  else {
50
- const { runCodexHook } = await import("./workit-hook-73m7khyw.js");
52
+ const { runCodexHook } = await import("./workit-hook-hegafjtc.js");
51
53
  await runCodexHook([payload]);
52
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-codex",
3
- "version": "8.22.3",
3
+ "version": "8.23.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
  "devDependencies": {
42
- "@brainervirus/workit-core": "^8.22.3",
43
- "@brainervirus/workit-mcp": "^8.22.3"
42
+ "@brainervirus/workit-core": "^8.23.0",
43
+ "@brainervirus/workit-mcp": "^8.23.0"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">=24"
@@ -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 (read the `workit-fanout` skill's SKILL.md and follow it).
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