@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.
- package/.codex-plugin/plugin.json +1 -1
- package/agents/workit-implementer.toml +27 -19
- package/dist/launch-mcp.js +32 -15
- package/dist/{workit-hook-73m7khyw.js → workit-hook-hegafjtc.js} +467 -62
- package/dist/workit-hook.js +3 -1
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +4 -4
- package/skills/workit-fanout/references/brief.md +17 -11
- package/skills/workit-fanout/references/orchestration.md +6 -6
- package/skills/workit-implement/SKILL.md +2 -0
|
@@ -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
|
|
4
|
-
developer_instructions = '''You are a workit implementer
|
|
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
|
|
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.
|
|
13
|
-
|
|
14
|
-
`
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
`workit git commit -m "<conventional message>" -- <paths in scope>`.
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
32
|
-
code, each acceptance item met or not, rulings recorded, and any
|
|
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
|
'''
|
package/dist/launch-mcp.js
CHANGED
|
@@ -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
|
|
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
|
|
27376
|
+
import path24 from "node:path";
|
|
27360
27377
|
var CODEX_AGENT_MARKER = "# Generated by Workit";
|
|
27361
27378
|
var codexHomeOfInstall = (pluginRoot) => {
|
|
27362
|
-
const parts =
|
|
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(
|
|
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 ||
|
|
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(
|
|
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 =
|
|
27411
|
+
const source = path24.join(pluginRoot, "agents");
|
|
27395
27412
|
if (!existsSync9(source))
|
|
27396
27413
|
return [];
|
|
27397
27414
|
const version = pluginVersion(pluginRoot);
|
|
27398
|
-
const target =
|
|
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(
|
|
27402
|
-
const dest =
|
|
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 =
|
|
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 &&
|
|
27461
|
-
if (!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 = () =>
|
|
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
|
|
19688
|
-
if (SCRIPT_SHELLS.has((
|
|
19689
|
-
return script
|
|
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
|
|
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 (
|
|
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
|
|
19945
|
-
|
|
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
|
-
|
|
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
|
|
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) || !
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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 } :
|
|
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) :
|
|
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 }) :
|
|
21223
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
|
|
20832
21224
|
if (raw.kind === "deny")
|
|
20833
21225
|
return raw;
|
|
20834
|
-
|
|
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
|
-
} :
|
|
21254
|
+
} : NONE6;
|
|
20858
21255
|
if (descriptor.transport !== "hook-process")
|
|
20859
21256
|
return rewritten(null);
|
|
20860
|
-
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(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
|
|
20870
|
-
case "write.pre":
|
|
20871
|
-
|
|
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
|
|
21293
|
+
return NONE6;
|
|
20889
21294
|
case "stop":
|
|
20890
|
-
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) :
|
|
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
|
|
21298
|
+
return NONE6;
|
|
20894
21299
|
case "prompt.submit":
|
|
20895
|
-
return
|
|
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 } :
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
21592
|
-
import
|
|
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 || !
|
|
22194
|
+
if (!file || !path29.isAbsolute(file))
|
|
21790
22195
|
return null;
|
|
21791
22196
|
try {
|
|
21792
|
-
const 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 =
|
|
22200
|
+
const fd = fs21.openSync(file, "r");
|
|
21796
22201
|
try {
|
|
21797
|
-
|
|
22202
|
+
fs21.readSync(fd, buffer, 0, buffer.length, start);
|
|
21798
22203
|
} finally {
|
|
21799
|
-
|
|
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
|
|
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) =>
|
|
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",
|
package/dist/workit-hook.js
CHANGED
|
@@ -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-
|
|
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.
|
|
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.
|
|
43
|
-
"@brainervirus/workit-mcp": "^8.
|
|
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
|
|
24
|
-
|
|
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
|
-
|
|
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) |
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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.
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
128
|
-
|
|
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>` (
|
|
39
|
-
|
|
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
|
|
73
|
-
`
|
|
74
|
-
|
|
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;
|
|
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
|