@brainervirus/workit-cursor 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/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +453 -65
- package/dist/mcp-server.js +20 -3
- package/dist/workit-hook.js +467 -62
- 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
|
@@ -17476,6 +17476,23 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
|
17476
17476
|
}
|
|
17477
17477
|
return found;
|
|
17478
17478
|
}
|
|
17479
|
+
function skipWorkitGlobals(args) {
|
|
17480
|
+
let cwd = null;
|
|
17481
|
+
let at = 0;
|
|
17482
|
+
for (;at < args.length; at++) {
|
|
17483
|
+
const match = WORKIT_GLOBAL.exec(args[at]);
|
|
17484
|
+
if (match?.[1] === "=") {
|
|
17485
|
+
if (args[at].startsWith("--cwd="))
|
|
17486
|
+
cwd = args[at].slice(6);
|
|
17487
|
+
} else if (match && args[at + 1] !== undefined) {
|
|
17488
|
+
if (args[at] === "--cwd")
|
|
17489
|
+
cwd = args[at + 1];
|
|
17490
|
+
at++;
|
|
17491
|
+
} else if (args[at] !== "--json")
|
|
17492
|
+
break;
|
|
17493
|
+
}
|
|
17494
|
+
return { rest: args.slice(at), cwd };
|
|
17495
|
+
}
|
|
17479
17496
|
function rawAction(invocation) {
|
|
17480
17497
|
const args = invocation.args;
|
|
17481
17498
|
if (invocation.tool !== "git" && args[0] === "api")
|
|
@@ -17875,7 +17892,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
17875
17892
|
...Object.keys(env).length ? { env } : {},
|
|
17876
17893
|
...stdin === undefined ? {} : { stdin }
|
|
17877
17894
|
};
|
|
17878
|
-
}, RECENT, LAUNCHERS, WORKIT_BIN, WORKIT_PACKAGE, PACKAGE_RUNNERS, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
|
|
17895
|
+
}, RECENT, LAUNCHERS, WORKIT_BIN, WORKIT_PACKAGE, PACKAGE_RUNNERS, WORKIT_GLOBAL, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
|
|
17879
17896
|
if (destination === "HEAD" || destination === "@" || CURRENT_BRANCH.test(destination))
|
|
17880
17897
|
return { name: null };
|
|
17881
17898
|
if (destination.startsWith("refs/heads/"))
|
|
@@ -18569,6 +18586,7 @@ var init_raw_git = __esm(() => {
|
|
|
18569
18586
|
WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
18570
18587
|
WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
|
|
18571
18588
|
PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
|
|
18589
|
+
WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
|
|
18572
18590
|
PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
18573
18591
|
CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
18574
18592
|
VALUE_OPTIONS = {
|
|
@@ -19794,7 +19812,7 @@ var init_live_session = __esm(() => {
|
|
|
19794
19812
|
});
|
|
19795
19813
|
|
|
19796
19814
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
19797
|
-
import { realpathSync as realpathSync5 } from "node:fs";
|
|
19815
|
+
import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
19798
19816
|
import os10 from "node:os";
|
|
19799
19817
|
import path27 from "node:path";
|
|
19800
19818
|
function writeGate(cwd, paths, owner, deps) {
|
|
@@ -19820,16 +19838,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
19820
19838
|
} catch {}
|
|
19821
19839
|
const writes = shellWrites(command);
|
|
19822
19840
|
let targets = [];
|
|
19823
|
-
if (owner)
|
|
19824
|
-
|
|
19825
|
-
targets = [
|
|
19826
|
-
...segmentTargets(cwd, command, dialect),
|
|
19827
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
19828
|
-
];
|
|
19829
|
-
} catch {
|
|
19830
|
-
targets = [];
|
|
19831
|
-
}
|
|
19832
|
-
}
|
|
19841
|
+
if (owner)
|
|
19842
|
+
targets = shellMutationTargets(cwd, command, dialect);
|
|
19833
19843
|
if (owner && targets.length) {
|
|
19834
19844
|
const live = liveGate(owner, targets, deps);
|
|
19835
19845
|
if (live.kind !== "none") {
|
|
@@ -19888,9 +19898,9 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
19888
19898
|
return null;
|
|
19889
19899
|
if ("script" in tail)
|
|
19890
19900
|
return tail.script;
|
|
19891
|
-
const
|
|
19892
|
-
if (SCRIPT_SHELLS.has((
|
|
19893
|
-
return script
|
|
19901
|
+
const script = shellScript2(tail.argv);
|
|
19902
|
+
if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
|
|
19903
|
+
return script;
|
|
19894
19904
|
return tail.argv.map(quoteWord2).join(" ");
|
|
19895
19905
|
}, editsOf = (input) => {
|
|
19896
19906
|
let words = input;
|
|
@@ -19967,6 +19977,89 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
19967
19977
|
default:
|
|
19968
19978
|
return [];
|
|
19969
19979
|
}
|
|
19980
|
+
}, shellScript2 = (words) => {
|
|
19981
|
+
if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
|
|
19982
|
+
return null;
|
|
19983
|
+
for (let index = 1;index < words.length; index++) {
|
|
19984
|
+
const word = words[index];
|
|
19985
|
+
if (/^-[a-z]*c$/.test(word))
|
|
19986
|
+
return words[index + 1] ?? null;
|
|
19987
|
+
if (/^[-+][oO]$/.test(word))
|
|
19988
|
+
index++;
|
|
19989
|
+
else if (!/^[-+]/.test(word))
|
|
19990
|
+
return null;
|
|
19991
|
+
}
|
|
19992
|
+
return null;
|
|
19993
|
+
}, XARGS_VALUE, runsWriter = (words) => {
|
|
19994
|
+
const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
|
|
19995
|
+
const edits = editsOf(filled);
|
|
19996
|
+
return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
|
|
19997
|
+
}, opaqueWrites = (input) => {
|
|
19998
|
+
let words = input;
|
|
19999
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
|
|
20000
|
+
if (words[0] === "xargs") {
|
|
20001
|
+
let at = 1;
|
|
20002
|
+
while (words[at]?.startsWith("-"))
|
|
20003
|
+
at += XARGS_VALUE.test(words[at]) ? 2 : 1;
|
|
20004
|
+
return runsWriter(words.slice(at)) ? ["."] : null;
|
|
20005
|
+
}
|
|
20006
|
+
words = words.slice(1);
|
|
20007
|
+
}
|
|
20008
|
+
const [verb, ...args] = words;
|
|
20009
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20010
|
+
const files = operands(args);
|
|
20011
|
+
const has = (pattern) => args.some((arg) => pattern.test(arg));
|
|
20012
|
+
const listed = (named) => named.length ? named : ["."];
|
|
20013
|
+
if (name === "unlink" || name === "shred")
|
|
20014
|
+
return listed(files);
|
|
20015
|
+
if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
|
|
20016
|
+
return listed(has(/^--reference/) ? files : files.slice(1));
|
|
20017
|
+
if (name === "rsync") {
|
|
20018
|
+
const dest = files.at(-1);
|
|
20019
|
+
const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
|
|
20020
|
+
return dest === undefined ? ["."] : remote ? [] : [dest];
|
|
20021
|
+
}
|
|
20022
|
+
if (name === "find") {
|
|
20023
|
+
const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
|
|
20024
|
+
const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
|
|
20025
|
+
const end = args.findIndex((arg) => /^[-(!]/.test(arg));
|
|
20026
|
+
return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
|
|
20027
|
+
}
|
|
20028
|
+
if (/^g?awk$/.test(name)) {
|
|
20029
|
+
if (!has(/^-i|^--include/))
|
|
20030
|
+
return null;
|
|
20031
|
+
const named = [];
|
|
20032
|
+
let program = !has(/^-f$|^--file/);
|
|
20033
|
+
for (let index = 0;index < args.length; index++)
|
|
20034
|
+
if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
|
|
20035
|
+
index++;
|
|
20036
|
+
else if (args[index].startsWith("-"))
|
|
20037
|
+
continue;
|
|
20038
|
+
else if (program)
|
|
20039
|
+
program = false;
|
|
20040
|
+
else
|
|
20041
|
+
named.push(args[index]);
|
|
20042
|
+
return listed(named);
|
|
20043
|
+
}
|
|
20044
|
+
if (name === "perl")
|
|
20045
|
+
return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
|
|
20046
|
+
if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
|
|
20047
|
+
return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
|
|
20048
|
+
return shellScript2(words) === null ? null : ["."];
|
|
20049
|
+
}, linkSources = (args, at) => {
|
|
20050
|
+
const names = operands([...args]);
|
|
20051
|
+
const into = args.indexOf("-t");
|
|
20052
|
+
const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
|
|
20053
|
+
const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
|
|
20054
|
+
const target = dest === undefined ? null : at(dest);
|
|
20055
|
+
const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
|
|
20056
|
+
const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync7(target) || /[\\/]$/.test(dest ?? "") ? target : path27.dirname(target);
|
|
20057
|
+
return sources.flatMap((source) => {
|
|
20058
|
+
if (symbolic && base !== null && !path27.isAbsolute(homeExpanded(source)))
|
|
20059
|
+
return [path27.resolve(base, source)];
|
|
20060
|
+
const resolved = at(source);
|
|
20061
|
+
return resolved ? [resolved] : [];
|
|
20062
|
+
});
|
|
19970
20063
|
}, shellWrites = (command, depth = 0) => {
|
|
19971
20064
|
const targets = [];
|
|
19972
20065
|
let unknown = false;
|
|
@@ -20044,16 +20137,19 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20044
20137
|
return [];
|
|
20045
20138
|
return [path27.resolve(cwd, invocation.dir ?? ".")];
|
|
20046
20139
|
});
|
|
20047
|
-
}, CD2, PUSHD2, POPD2, cdInto = (dir, words) => {
|
|
20140
|
+
}, CD2, PUSHD2, POPD2, UNKNOWN_DIR, cdInto = (dir, words, strict = false) => {
|
|
20141
|
+
const lost = strict ? UNKNOWN_DIR : undefined;
|
|
20048
20142
|
const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
20049
20143
|
const target = named === undefined ? undefined : homeExpanded(named);
|
|
20050
|
-
if (
|
|
20051
|
-
return;
|
|
20144
|
+
if (target === "-" || target && /^~|[$`*?]/.test(target))
|
|
20145
|
+
return lost;
|
|
20146
|
+
if (dir === undefined)
|
|
20147
|
+
return lost;
|
|
20052
20148
|
return target === undefined ? os10.homedir() : path27.resolve(dir, target);
|
|
20053
20149
|
}, quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`, homeExpanded = (file) => {
|
|
20054
20150
|
const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
|
|
20055
20151
|
return match ? (process.env.HOME || os10.homedir()) + file.slice(match[0].length) : file;
|
|
20056
|
-
}, segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
20152
|
+
}, segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
20057
20153
|
const targets = [];
|
|
20058
20154
|
let dir = cwd;
|
|
20059
20155
|
let stack = [];
|
|
@@ -20077,21 +20173,35 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20077
20173
|
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
20078
20174
|
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20079
20175
|
if (CD2.has(name)) {
|
|
20080
|
-
dir = cdInto(dir, words);
|
|
20176
|
+
dir = cdInto(dir, words, strict);
|
|
20081
20177
|
continue;
|
|
20082
20178
|
}
|
|
20083
20179
|
if (PUSHD2.has(name)) {
|
|
20084
20180
|
stack.push(dir);
|
|
20085
|
-
dir = cdInto(dir, words);
|
|
20181
|
+
dir = cdInto(dir, words, strict);
|
|
20086
20182
|
continue;
|
|
20087
20183
|
}
|
|
20088
20184
|
if (POPD2.has(name)) {
|
|
20089
|
-
dir = stack.length ? stack.pop() : undefined;
|
|
20185
|
+
dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
|
|
20090
20186
|
continue;
|
|
20091
20187
|
}
|
|
20092
|
-
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
20188
|
+
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
20093
20189
|
if (inner !== null && dir !== undefined)
|
|
20094
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
20190
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
20191
|
+
if (strict && dir !== undefined) {
|
|
20192
|
+
const runIn = dir;
|
|
20193
|
+
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
20194
|
+
targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
|
|
20195
|
+
const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
|
|
20196
|
+
for (const file of opaque ?? []) {
|
|
20197
|
+
const resolved = at(file);
|
|
20198
|
+
if (resolved)
|
|
20199
|
+
targets.push(file === "." ? path27.join(resolved, DIR_TARGET) : resolved);
|
|
20200
|
+
}
|
|
20201
|
+
const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
|
|
20202
|
+
if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
|
|
20203
|
+
targets.push(...linkSources(words.slice(ln + 1), at));
|
|
20204
|
+
}
|
|
20095
20205
|
for (const file of segment.redirects)
|
|
20096
20206
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
20097
20207
|
const resolved = at(file);
|
|
@@ -20108,10 +20218,26 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20108
20218
|
if (resolved)
|
|
20109
20219
|
targets.push(resolved);
|
|
20110
20220
|
}
|
|
20111
|
-
if (dir !== undefined
|
|
20112
|
-
|
|
20221
|
+
if (dir !== undefined)
|
|
20222
|
+
for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
|
|
20223
|
+
const { at: runsIn, args } = workitCwd(dir, invocation);
|
|
20224
|
+
if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
|
|
20225
|
+
targets.push(path27.join(runsIn, DIR_TARGET));
|
|
20226
|
+
}
|
|
20113
20227
|
}
|
|
20114
20228
|
return targets;
|
|
20229
|
+
}, workitCwd = (dir, args) => {
|
|
20230
|
+
const { rest, cwd } = skipWorkitGlobals(args);
|
|
20231
|
+
return { at: cwd === null ? dir : path27.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
20232
|
+
}, shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
|
|
20233
|
+
try {
|
|
20234
|
+
return [
|
|
20235
|
+
...segmentTargets(cwd, command, dialect, 0, strict),
|
|
20236
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
20237
|
+
];
|
|
20238
|
+
} catch {
|
|
20239
|
+
return [];
|
|
20240
|
+
}
|
|
20115
20241
|
}, overridden = (command, dialect) => {
|
|
20116
20242
|
const segments = segmentsOf(command, dialect);
|
|
20117
20243
|
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
@@ -20199,8 +20325,9 @@ var init_write_gate = __esm(() => {
|
|
|
20199
20325
|
init_shell_words();
|
|
20200
20326
|
NONE3 = { kind: "none" };
|
|
20201
20327
|
CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
20202
|
-
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
20328
|
+
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
|
|
20203
20329
|
SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
20330
|
+
XARGS_VALUE = /^-[IinLPdsEa]$/;
|
|
20204
20331
|
cache2 = new Map;
|
|
20205
20332
|
MUTATING_GIT = new Set([
|
|
20206
20333
|
"add",
|
|
@@ -20226,6 +20353,7 @@ var init_write_gate = __esm(() => {
|
|
|
20226
20353
|
CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
20227
20354
|
PUSHD2 = new Set(["pushd", "push-location"]);
|
|
20228
20355
|
POPD2 = new Set(["popd", "pop-location"]);
|
|
20356
|
+
UNKNOWN_DIR = path27.resolve(path27.parse(process.cwd()).root, ".workit-unknown-dir");
|
|
20229
20357
|
HOST_LABELS = {
|
|
20230
20358
|
claude_code: "Claude Code",
|
|
20231
20359
|
opencode: "OpenCode",
|
|
@@ -20653,10 +20781,10 @@ var init_skill_nudge = __esm(() => {
|
|
|
20653
20781
|
});
|
|
20654
20782
|
|
|
20655
20783
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20656
|
-
import { existsSync as
|
|
20784
|
+
import { existsSync as existsSync8, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
20657
20785
|
import path28 from "node:path";
|
|
20658
20786
|
var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", optionalText = (value) => typeof value === "string" ? value : null, existingDirectory = (value) => {
|
|
20659
|
-
if (!nonEmpty2(value) || !path28.isAbsolute(value) || !
|
|
20787
|
+
if (!nonEmpty2(value) || !path28.isAbsolute(value) || !existsSync8(value))
|
|
20660
20788
|
return null;
|
|
20661
20789
|
try {
|
|
20662
20790
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -20857,6 +20985,252 @@ var init_cwd_normalize = __esm(() => {
|
|
|
20857
20985
|
FILTERS = new Set(["tail", "head", "grep"]);
|
|
20858
20986
|
});
|
|
20859
20987
|
|
|
20988
|
+
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
20989
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
20990
|
+
import fs23 from "node:fs";
|
|
20991
|
+
import path30 from "node:path";
|
|
20992
|
+
function confineImplementer(input, targets) {
|
|
20993
|
+
try {
|
|
20994
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
20995
|
+
return NONE5;
|
|
20996
|
+
if (targets.some((target) => within(path30.resolve(target), UNKNOWN_DIR)))
|
|
20997
|
+
return unknownDirDeny(input, boundWorktree(input));
|
|
20998
|
+
const bindings = canonical3(bindingDir());
|
|
20999
|
+
const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
|
|
21000
|
+
if (judged.length === 0)
|
|
21001
|
+
return NONE5;
|
|
21002
|
+
let worktree = boundWorktree(input);
|
|
21003
|
+
if (!worktree) {
|
|
21004
|
+
for (const { real } of judged) {
|
|
21005
|
+
worktree = within(real, bindings) ? null : workitWorktreeOf(real);
|
|
21006
|
+
if (worktree)
|
|
21007
|
+
break;
|
|
21008
|
+
}
|
|
21009
|
+
if (!worktree)
|
|
21010
|
+
return unboundDeny(input, judged[0].named);
|
|
21011
|
+
bind(input, worktree);
|
|
21012
|
+
}
|
|
21013
|
+
const root = canonical3(worktree);
|
|
21014
|
+
const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
|
|
21015
|
+
return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
|
|
21016
|
+
} catch {
|
|
21017
|
+
return NONE5;
|
|
21018
|
+
}
|
|
21019
|
+
}
|
|
21020
|
+
function confineImplementerShell(input, command, dialect = "posix") {
|
|
21021
|
+
try {
|
|
21022
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
21023
|
+
return NONE5;
|
|
21024
|
+
if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
|
|
21025
|
+
return leadOwnedDeny(input, command);
|
|
21026
|
+
} catch {}
|
|
21027
|
+
return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
|
|
21028
|
+
}
|
|
21029
|
+
var FANOUT_TOKEN_FILE = "workit-fanout-token", NONE5, CASE_INSENSITIVE2, canonical3 = (file) => {
|
|
21030
|
+
let rest = "";
|
|
21031
|
+
for (let dir = path30.resolve(file);; dir = path30.dirname(dir)) {
|
|
21032
|
+
try {
|
|
21033
|
+
return path30.join(fs23.realpathSync.native(dir), rest);
|
|
21034
|
+
} catch {
|
|
21035
|
+
if (path30.dirname(dir) === dir)
|
|
21036
|
+
return path30.resolve(file);
|
|
21037
|
+
rest = rest ? path30.join(path30.basename(dir), rest) : path30.basename(dir);
|
|
21038
|
+
}
|
|
21039
|
+
}
|
|
21040
|
+
}, within = (child, parent) => {
|
|
21041
|
+
const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
|
|
21042
|
+
const relative = path30.relative(fold(parent), fold(child));
|
|
21043
|
+
return relative === "" || !relative.startsWith("..") && !path30.isAbsolute(relative);
|
|
21044
|
+
}, checkoutOf2 = (file) => {
|
|
21045
|
+
for (let dir = canonical3(file);; dir = path30.dirname(dir)) {
|
|
21046
|
+
const dotGit = path30.join(dir, ".git");
|
|
21047
|
+
if (fs23.existsSync(dotGit)) {
|
|
21048
|
+
if (!fs23.statSync(dotGit).isFile())
|
|
21049
|
+
return { root: dir, workit: false };
|
|
21050
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
21051
|
+
return {
|
|
21052
|
+
root: dir,
|
|
21053
|
+
workit: Boolean(admin) && fs23.existsSync(path30.join(path30.resolve(dir, admin), FANOUT_TOKEN_FILE))
|
|
21054
|
+
};
|
|
21055
|
+
}
|
|
21056
|
+
if (path30.dirname(dir) === dir)
|
|
21057
|
+
return null;
|
|
21058
|
+
}
|
|
21059
|
+
}, workitWorktreeOf = (file) => {
|
|
21060
|
+
try {
|
|
21061
|
+
const checkout = checkoutOf2(file);
|
|
21062
|
+
return checkout?.workit ? checkout.root : null;
|
|
21063
|
+
} catch {
|
|
21064
|
+
return null;
|
|
21065
|
+
}
|
|
21066
|
+
}, bindingDir = () => path30.join(resolveStateDir(), "worktree-bind"), bindingFile = (session, agentId) => path30.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`), readBinding = (file) => {
|
|
21067
|
+
try {
|
|
21068
|
+
const value = JSON.parse(fs23.readFileSync(file, "utf8"));
|
|
21069
|
+
return typeof value.worktree === "string" ? value : null;
|
|
21070
|
+
} catch {
|
|
21071
|
+
return null;
|
|
21072
|
+
}
|
|
21073
|
+
}, boundWorktree = (input) => {
|
|
21074
|
+
const agentId = input.session.agentId;
|
|
21075
|
+
if (!agentId)
|
|
21076
|
+
return null;
|
|
21077
|
+
const binding = readBinding(bindingFile(input.session.id, agentId));
|
|
21078
|
+
return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
|
|
21079
|
+
}, bind = (input, worktree) => {
|
|
21080
|
+
const agentId = input.session.agentId;
|
|
21081
|
+
const file = bindingFile(input.session.id, agentId);
|
|
21082
|
+
const binding = {
|
|
21083
|
+
worktree,
|
|
21084
|
+
session: input.session.id,
|
|
21085
|
+
agentId,
|
|
21086
|
+
at: new Date().toISOString()
|
|
21087
|
+
};
|
|
21088
|
+
fs23.mkdirSync(path30.dirname(file), { recursive: true });
|
|
21089
|
+
fs23.writeFileSync(file, JSON.stringify(binding));
|
|
21090
|
+
try {
|
|
21091
|
+
if (inWorkitWorkspace(input.cwd))
|
|
21092
|
+
appendHookObserved(input.cwd, {
|
|
21093
|
+
type: "worktree.bound",
|
|
21094
|
+
actor: { host: input.host, session: input.session.id, agentId },
|
|
21095
|
+
agentType: input.session.agentType,
|
|
21096
|
+
worktree
|
|
21097
|
+
});
|
|
21098
|
+
} catch {}
|
|
21099
|
+
}, hostIsolated = (cwd, agentId) => {
|
|
21100
|
+
try {
|
|
21101
|
+
if (agentId === null)
|
|
21102
|
+
return false;
|
|
21103
|
+
const checkout = checkoutOf2(cwd);
|
|
21104
|
+
if (!checkout)
|
|
21105
|
+
return false;
|
|
21106
|
+
const dotGit = path30.join(checkout.root, ".git");
|
|
21107
|
+
if (!fs23.statSync(dotGit).isFile())
|
|
21108
|
+
return false;
|
|
21109
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
21110
|
+
if (!admin)
|
|
21111
|
+
return false;
|
|
21112
|
+
const adminDir = path30.resolve(checkout.root, admin);
|
|
21113
|
+
const back = fs23.readFileSync(path30.join(adminDir, "gitdir"), "utf8").trim();
|
|
21114
|
+
if (path30.relative(canonical3(path30.resolve(adminDir, back)), canonical3(dotGit)) !== "")
|
|
21115
|
+
return false;
|
|
21116
|
+
const common = path30.join(adminDir, "commondir");
|
|
21117
|
+
const gitDir = fs23.existsSync(common) ? path30.resolve(adminDir, fs23.readFileSync(common, "utf8").trim()) : adminDir;
|
|
21118
|
+
const expected = path30.join(path30.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
|
|
21119
|
+
return path30.relative(canonical3(expected), checkout.root) === "";
|
|
21120
|
+
} catch {
|
|
21121
|
+
return false;
|
|
21122
|
+
}
|
|
21123
|
+
}, shown = (target) => path30.basename(target) === ".workit-live-target" ? path30.dirname(target) : target, unboundDeny = (input, target) => {
|
|
21124
|
+
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.";
|
|
21125
|
+
return {
|
|
21126
|
+
kind: "deny",
|
|
21127
|
+
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}`,
|
|
21128
|
+
unblock
|
|
21129
|
+
};
|
|
21130
|
+
}, outsideDeny = (input, worktree, target) => {
|
|
21131
|
+
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.`;
|
|
21132
|
+
return {
|
|
21133
|
+
kind: "deny",
|
|
21134
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
21135
|
+
unblock
|
|
21136
|
+
};
|
|
21137
|
+
}, unknownDirDeny = (input, worktree) => {
|
|
21138
|
+
const wt = worktree ?? "<worktree>";
|
|
21139
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
21140
|
+
return {
|
|
21141
|
+
kind: "deny",
|
|
21142
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
21143
|
+
unblock
|
|
21144
|
+
};
|
|
21145
|
+
}, MUTATING_REMOTE, CONFIG_READ, CONFIG_WRITE, CONFIG_VALUE, leadOwnedGit = (args) => {
|
|
21146
|
+
const [sub = "", ...rest] = args;
|
|
21147
|
+
if (rest.some((arg) => arg === "--help" || arg === "-h"))
|
|
21148
|
+
return false;
|
|
21149
|
+
const names = rest.filter((arg) => !arg.startsWith("-"));
|
|
21150
|
+
const flag = (pattern) => rest.some((arg) => pattern.test(arg));
|
|
21151
|
+
switch (sub) {
|
|
21152
|
+
case "worktree":
|
|
21153
|
+
return names[0] !== undefined && names[0] !== "list";
|
|
21154
|
+
case "update-ref":
|
|
21155
|
+
return true;
|
|
21156
|
+
case "symbolic-ref":
|
|
21157
|
+
return names.length > 1 || flag(/^(?:-d|--delete)$/);
|
|
21158
|
+
case "branch":
|
|
21159
|
+
return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
|
|
21160
|
+
case "tag":
|
|
21161
|
+
return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
|
|
21162
|
+
case "push": {
|
|
21163
|
+
if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
|
|
21164
|
+
return true;
|
|
21165
|
+
const refspecs = names.slice(1);
|
|
21166
|
+
return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
|
|
21167
|
+
}
|
|
21168
|
+
case "replace":
|
|
21169
|
+
case "filter-branch":
|
|
21170
|
+
case "gc":
|
|
21171
|
+
case "prune":
|
|
21172
|
+
case "repack":
|
|
21173
|
+
case "sparse-checkout":
|
|
21174
|
+
return true;
|
|
21175
|
+
case "notes":
|
|
21176
|
+
case "reflog":
|
|
21177
|
+
case "submodule":
|
|
21178
|
+
return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
|
|
21179
|
+
case "stash":
|
|
21180
|
+
return names[0] === "drop" || names[0] === "clear";
|
|
21181
|
+
case "remote":
|
|
21182
|
+
return MUTATING_REMOTE.has(names[0] ?? "");
|
|
21183
|
+
case "config": {
|
|
21184
|
+
if (rest.some((arg) => CONFIG_READ.test(arg)))
|
|
21185
|
+
return false;
|
|
21186
|
+
if (rest.some((arg) => CONFIG_WRITE.test(arg)))
|
|
21187
|
+
return true;
|
|
21188
|
+
let operands = 0;
|
|
21189
|
+
for (let index = 0;index < rest.length; index++)
|
|
21190
|
+
if (CONFIG_VALUE.test(rest[index]))
|
|
21191
|
+
index++;
|
|
21192
|
+
else if (!rest[index].startsWith("-"))
|
|
21193
|
+
operands++;
|
|
21194
|
+
return operands > 1;
|
|
21195
|
+
}
|
|
21196
|
+
default:
|
|
21197
|
+
return false;
|
|
21198
|
+
}
|
|
21199
|
+
}, leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
|
|
21200
|
+
if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
|
|
21201
|
+
return false;
|
|
21202
|
+
const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
|
|
21203
|
+
return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
|
|
21204
|
+
}), leadOwnedDeny = (input, command) => {
|
|
21205
|
+
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.";
|
|
21206
|
+
return {
|
|
21207
|
+
kind: "deny",
|
|
21208
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
|
|
21209
|
+
unblock
|
|
21210
|
+
};
|
|
21211
|
+
};
|
|
21212
|
+
var init_worktree_confine = __esm(() => {
|
|
21213
|
+
init_logger();
|
|
21214
|
+
init_ledger();
|
|
21215
|
+
init_raw_git();
|
|
21216
|
+
init_write_gate();
|
|
21217
|
+
NONE5 = { kind: "none" };
|
|
21218
|
+
CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
|
|
21219
|
+
MUTATING_REMOTE = new Set([
|
|
21220
|
+
"add",
|
|
21221
|
+
"remove",
|
|
21222
|
+
"rm",
|
|
21223
|
+
"rename",
|
|
21224
|
+
"set-url",
|
|
21225
|
+
"set-head",
|
|
21226
|
+
"set-branches",
|
|
21227
|
+
"prune"
|
|
21228
|
+
]);
|
|
21229
|
+
CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
|
|
21230
|
+
CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
|
|
21231
|
+
CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
|
|
21232
|
+
});
|
|
21233
|
+
|
|
20860
21234
|
// packages/workit-core/src/hooks/handle.ts
|
|
20861
21235
|
function handleHook(input, deps) {
|
|
20862
21236
|
const decision = decide(input, deps);
|
|
@@ -20874,7 +21248,7 @@ function decide(input, deps) {
|
|
|
20874
21248
|
switch (event.kind) {
|
|
20875
21249
|
case "session.start": {
|
|
20876
21250
|
if (!usable2(descriptor.context.sessionStart))
|
|
20877
|
-
return
|
|
21251
|
+
return NONE6;
|
|
20878
21252
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
20879
21253
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
20880
21254
|
if (offer && key !== null)
|
|
@@ -20889,26 +21263,31 @@ function decide(input, deps) {
|
|
|
20889
21263
|
}
|
|
20890
21264
|
case "context.turn": {
|
|
20891
21265
|
if (!usable2(descriptor.context.perTurn))
|
|
20892
|
-
return
|
|
21266
|
+
return NONE6;
|
|
20893
21267
|
const current = turnContextText(input, descriptor);
|
|
20894
21268
|
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
20895
|
-
return withContextLine(text ? { kind: "context", text } :
|
|
21269
|
+
return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
|
|
20896
21270
|
}
|
|
20897
21271
|
case "shell.pre": {
|
|
20898
21272
|
const skillRead = skillReadIn(event.command, event.dialect);
|
|
20899
21273
|
if (skillRead)
|
|
20900
21274
|
recordSkillLoad(input, skillRead, "read");
|
|
20901
21275
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
20902
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) :
|
|
21276
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
|
|
20903
21277
|
if (policy.kind !== "none")
|
|
20904
21278
|
return policy;
|
|
20905
21279
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
20906
21280
|
if (!postTool)
|
|
20907
21281
|
settlePendingCommit(input);
|
|
20908
|
-
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) :
|
|
21282
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
|
|
20909
21283
|
if (raw.kind === "deny")
|
|
20910
21284
|
return raw;
|
|
20911
|
-
|
|
21285
|
+
if (canDeny && implementerAgent(input, descriptor)) {
|
|
21286
|
+
const confined = confineImplementerShell(input, event.command, event.dialect);
|
|
21287
|
+
if (confined.kind === "deny")
|
|
21288
|
+
return confined;
|
|
21289
|
+
}
|
|
21290
|
+
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
|
|
20912
21291
|
if (gate.kind === "deny")
|
|
20913
21292
|
return gate;
|
|
20914
21293
|
const plan = heavyGate(input.cwd, event.command, {
|
|
@@ -20931,10 +21310,10 @@ function decide(input, deps) {
|
|
|
20931
21310
|
command: rewrittenTo,
|
|
20932
21311
|
input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
|
|
20933
21312
|
text
|
|
20934
|
-
} :
|
|
21313
|
+
} : NONE6;
|
|
20935
21314
|
if (descriptor.transport !== "hook-process")
|
|
20936
21315
|
return rewritten(null);
|
|
20937
|
-
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) :
|
|
21316
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
|
|
20938
21317
|
if (!lease)
|
|
20939
21318
|
return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
|
|
20940
21319
|
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
@@ -20943,9 +21322,17 @@ function decide(input, deps) {
|
|
|
20943
21322
|
case "shell.post":
|
|
20944
21323
|
if (usable2(descriptor.events["shell.post"].support))
|
|
20945
21324
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
20946
|
-
return
|
|
20947
|
-
case "write.pre":
|
|
20948
|
-
|
|
21325
|
+
return NONE6;
|
|
21326
|
+
case "write.pre": {
|
|
21327
|
+
if (!usable2(descriptor.events["write.pre"].support))
|
|
21328
|
+
return NONE6;
|
|
21329
|
+
if (implementerAgent(input, descriptor)) {
|
|
21330
|
+
const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
|
|
21331
|
+
if (confined.kind === "deny")
|
|
21332
|
+
return confined;
|
|
21333
|
+
}
|
|
21334
|
+
return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
|
|
21335
|
+
}
|
|
20949
21336
|
case "subagent.start":
|
|
20950
21337
|
if (workitRole(descriptor, event.agentType) === "judge")
|
|
20951
21338
|
trackJudge(input, event.agentId, true);
|
|
@@ -20962,25 +21349,25 @@ function decide(input, deps) {
|
|
|
20962
21349
|
case "tool.pre":
|
|
20963
21350
|
if (event.skill)
|
|
20964
21351
|
recordSkillLoad(input, event.skill, "tool");
|
|
20965
|
-
return
|
|
21352
|
+
return NONE6;
|
|
20966
21353
|
case "stop":
|
|
20967
|
-
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) :
|
|
21354
|
+
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
|
|
20968
21355
|
case "subagent.stop":
|
|
20969
21356
|
trackJudge(input, event.agentId, false);
|
|
20970
|
-
return
|
|
21357
|
+
return NONE6;
|
|
20971
21358
|
case "prompt.submit":
|
|
20972
|
-
return
|
|
21359
|
+
return NONE6;
|
|
20973
21360
|
}
|
|
20974
21361
|
}
|
|
20975
|
-
var usable2 = (value) => value === "native" || value === "partial",
|
|
21362
|
+
var usable2 = (value) => value === "native" || value === "partial", NONE6, resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn), offered, ROLES, workitRole = (descriptor, agentType) => {
|
|
20976
21363
|
const prefix = descriptor.subagents.agentPrefix;
|
|
20977
21364
|
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
20978
21365
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
20979
|
-
}, subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), CLAUDE_WORKTREE, 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\`).`, subagentStartText = (input, descriptor, event) => {
|
|
21366
|
+
}, implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer", subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), CLAUDE_WORKTREE, 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\`).`, subagentStartText = (input, descriptor, event) => {
|
|
20980
21367
|
const session = input.session.id ?? null;
|
|
20981
21368
|
const role = workitRole(descriptor, event.agentType);
|
|
20982
21369
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
20983
|
-
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer"
|
|
21370
|
+
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
|
|
20984
21371
|
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
20985
21372
|
if (role === "implementer" && isolated)
|
|
20986
21373
|
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.`;
|
|
@@ -20988,7 +21375,7 @@ var usable2 = (value) => value === "native" || value === "partial", NONE5, resen
|
|
|
20988
21375
|
return `${isolatedGit(input.cwd)}
|
|
20989
21376
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
20990
21377
|
if (role === "implementer")
|
|
20991
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes
|
|
21378
|
+
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.`;
|
|
20992
21379
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
20993
21380
|
if (role !== "judge" || !event.agentId)
|
|
20994
21381
|
return readOnly;
|
|
@@ -21003,10 +21390,10 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
21003
21390
|
return `${readOnly} Its own Workit session is ${own}, so the ledger tells its verdicts apart from the author's. Record the verdict with exactly this command (pick the result${pick}, name the candidate branch if it differs, say what you ran): \`${line}\`.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
|
|
21004
21391
|
}, PRE_TOOL, failureDecision = (descriptor, event, error) => {
|
|
21005
21392
|
if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
|
|
21006
|
-
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } :
|
|
21393
|
+
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE6;
|
|
21007
21394
|
if (event === "session.start" || event === "subagent.start")
|
|
21008
21395
|
return { kind: "context", text: `[workit diagnostic: ${error}]` };
|
|
21009
|
-
return
|
|
21396
|
+
return NONE6;
|
|
21010
21397
|
}, dispatchHook = (adapter, raw, env = process.env) => {
|
|
21011
21398
|
const parsed = adapter.parse(raw, env);
|
|
21012
21399
|
if (!parsed.ok)
|
|
@@ -21044,7 +21431,8 @@ var init_handle = __esm(() => {
|
|
|
21044
21431
|
init_stop();
|
|
21045
21432
|
init_live_session();
|
|
21046
21433
|
init_write_gate();
|
|
21047
|
-
|
|
21434
|
+
init_worktree_confine();
|
|
21435
|
+
NONE6 = { kind: "none" };
|
|
21048
21436
|
offered = new Set;
|
|
21049
21437
|
ROLES = {
|
|
21050
21438
|
implementer: "implementer",
|
|
@@ -21057,20 +21445,20 @@ var init_handle = __esm(() => {
|
|
|
21057
21445
|
|
|
21058
21446
|
// packages/workit-core/src/hooks/deadline.ts
|
|
21059
21447
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
21060
|
-
import
|
|
21448
|
+
import path31 from "node:path";
|
|
21061
21449
|
var HOOK_SKIP_TEXT = "workit context skipped: host under load", HOOK_DEADLINE_MS = 8000, HOST_DEADLINES, HOOK_SKIPS_FILE = "hook-skips.jsonl", override = (env) => {
|
|
21062
21450
|
const value = Number(env.WORKIT_HOOK_DEADLINE_MS);
|
|
21063
21451
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
21064
21452
|
}, hookDeadlineMs = (env, host = "", native) => {
|
|
21065
21453
|
const row = HOST_DEADLINES[host];
|
|
21066
21454
|
return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
|
|
21067
|
-
}, skipsFile = (stateDir) =>
|
|
21455
|
+
}, skipsFile = (stateDir) => path31.join(stateDir, HOOK_SKIPS_FILE), recordHookSkip = (skip, stateDir = resolveStateDir()) => {
|
|
21068
21456
|
try {
|
|
21069
21457
|
mkdirSync3(stateDir, { recursive: true });
|
|
21070
21458
|
appendFileSync(skipsFile(stateDir), `${JSON.stringify(skip)}
|
|
21071
21459
|
`);
|
|
21072
21460
|
} catch {}
|
|
21073
|
-
}, QUIET, skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT }, TIMED_OUT,
|
|
21461
|
+
}, QUIET, skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT }, TIMED_OUT, within2 = async (work, ms) => {
|
|
21074
21462
|
let timer;
|
|
21075
21463
|
const expired = new Promise((resolve) => {
|
|
21076
21464
|
timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
|
|
@@ -21097,7 +21485,7 @@ var HOOK_SKIP_TEXT = "workit context skipped: host under load", HOOK_DEADLINE_MS
|
|
|
21097
21485
|
text += String(chunk);
|
|
21098
21486
|
return text;
|
|
21099
21487
|
})();
|
|
21100
|
-
const text = await
|
|
21488
|
+
const text = await within2(read, budget.startedAt + budget.deadlineMs - budget.now());
|
|
21101
21489
|
return isTimedOut(text) ? null : text;
|
|
21102
21490
|
}, note = (adapter, raw, env, budget, outcome, stage) => {
|
|
21103
21491
|
const parsed = raw === undefined ? null : adapter.parse(raw, env);
|
|
@@ -21123,7 +21511,7 @@ var HOOK_SKIP_TEXT = "workit context skipped: host under load", HOOK_DEADLINE_MS
|
|
|
21123
21511
|
if (budget.host !== null && typeof native === "string")
|
|
21124
21512
|
budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
|
|
21125
21513
|
const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
|
|
21126
|
-
const result = remaining() > 0 ? await
|
|
21514
|
+
const result = remaining() > 0 ? await within2(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
|
|
21127
21515
|
if (isTimedOut(result))
|
|
21128
21516
|
return skippedHook(adapter, raw, env, budget, "dispatch");
|
|
21129
21517
|
if (remaining() < 0)
|
|
@@ -21708,8 +22096,8 @@ var init_codex = __esm(() => {
|
|
|
21708
22096
|
});
|
|
21709
22097
|
|
|
21710
22098
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
21711
|
-
import
|
|
21712
|
-
import
|
|
22099
|
+
import fs24 from "node:fs";
|
|
22100
|
+
import path32 from "node:path";
|
|
21713
22101
|
var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
|
|
21714
22102
|
if (!isRecord6(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
21715
22103
|
return { ok: false, error: "hook_event_name is required" };
|
|
@@ -21790,17 +22178,17 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
21790
22178
|
};
|
|
21791
22179
|
}
|
|
21792
22180
|
}, TRANSCRIPT_TAIL_BYTES, lastAssistantText = (file) => {
|
|
21793
|
-
if (!file || !
|
|
22181
|
+
if (!file || !path32.isAbsolute(file))
|
|
21794
22182
|
return null;
|
|
21795
22183
|
try {
|
|
21796
|
-
const size =
|
|
22184
|
+
const size = fs24.statSync(file).size;
|
|
21797
22185
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
21798
22186
|
const buffer = Buffer.alloc(size - start);
|
|
21799
|
-
const fd =
|
|
22187
|
+
const fd = fs24.openSync(file, "r");
|
|
21800
22188
|
try {
|
|
21801
|
-
|
|
22189
|
+
fs24.readSync(fd, buffer, 0, buffer.length, start);
|
|
21802
22190
|
} finally {
|
|
21803
|
-
|
|
22191
|
+
fs24.closeSync(fd);
|
|
21804
22192
|
}
|
|
21805
22193
|
const lines = buffer.toString("utf8").split(`
|
|
21806
22194
|
`);
|
|
@@ -21826,14 +22214,14 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
21826
22214
|
} catch {
|
|
21827
22215
|
return null;
|
|
21828
22216
|
}
|
|
21829
|
-
},
|
|
22217
|
+
}, within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path32.sep) ? root : `${root}${path32.sep}`), hookCwd = (input) => {
|
|
21830
22218
|
const cwd = existingDirectory(input.cwd);
|
|
21831
22219
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
21832
22220
|
return cwd;
|
|
21833
22221
|
return cursorWorkspaceRoot(input);
|
|
21834
22222
|
}, cursorWorkspaceRoot = (input) => {
|
|
21835
22223
|
const cwd = existingDirectory(input.cwd);
|
|
21836
|
-
return cwd && input.workspace_roots.find((root) =>
|
|
22224
|
+
return cwd && input.workspace_roots.find((root) => within3(root, cwd)) || input.workspace_roots[0];
|
|
21837
22225
|
}, cursorDeny = (reason) => ({
|
|
21838
22226
|
permission: "deny",
|
|
21839
22227
|
user_message: "Workit blocked this action",
|