@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
package/dist/workit-hook.js
CHANGED
|
@@ -17953,6 +17953,24 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
|
17953
17953
|
}
|
|
17954
17954
|
return found;
|
|
17955
17955
|
}
|
|
17956
|
+
var WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
|
|
17957
|
+
function skipWorkitGlobals(args) {
|
|
17958
|
+
let cwd = null;
|
|
17959
|
+
let at = 0;
|
|
17960
|
+
for (;at < args.length; at++) {
|
|
17961
|
+
const match = WORKIT_GLOBAL.exec(args[at]);
|
|
17962
|
+
if (match?.[1] === "=") {
|
|
17963
|
+
if (args[at].startsWith("--cwd="))
|
|
17964
|
+
cwd = args[at].slice(6);
|
|
17965
|
+
} else if (match && args[at + 1] !== undefined) {
|
|
17966
|
+
if (args[at] === "--cwd")
|
|
17967
|
+
cwd = args[at + 1];
|
|
17968
|
+
at++;
|
|
17969
|
+
} else if (args[at] !== "--json")
|
|
17970
|
+
break;
|
|
17971
|
+
}
|
|
17972
|
+
return { rest: args.slice(at), cwd };
|
|
17973
|
+
}
|
|
17956
17974
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17957
17975
|
var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
17958
17976
|
var pushDestination = (destination) => {
|
|
@@ -19774,7 +19792,7 @@ var splitDenial = (plan) => ({
|
|
|
19774
19792
|
unblock: plan.unblock
|
|
19775
19793
|
});
|
|
19776
19794
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
19777
|
-
import { realpathSync as realpathSync5 } from "node:fs";
|
|
19795
|
+
import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
19778
19796
|
import os10 from "node:os";
|
|
19779
19797
|
import path27 from "node:path";
|
|
19780
19798
|
|
|
@@ -20003,7 +20021,7 @@ var exempt = (cwd, root, file, plans) => {
|
|
|
20003
20021
|
const segments = posix.split("/");
|
|
20004
20022
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
20005
20023
|
};
|
|
20006
|
-
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
20024
|
+
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
|
|
20007
20025
|
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
20008
20026
|
var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
20009
20027
|
var wrappedScript = (input) => {
|
|
@@ -20015,9 +20033,9 @@ var wrappedScript = (input) => {
|
|
|
20015
20033
|
return null;
|
|
20016
20034
|
if ("script" in tail)
|
|
20017
20035
|
return tail.script;
|
|
20018
|
-
const
|
|
20019
|
-
if (SCRIPT_SHELLS.has((
|
|
20020
|
-
return script
|
|
20036
|
+
const script = shellScript2(tail.argv);
|
|
20037
|
+
if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
|
|
20038
|
+
return script;
|
|
20021
20039
|
return tail.argv.map(quoteWord2).join(" ");
|
|
20022
20040
|
};
|
|
20023
20041
|
var editsOf = (input) => {
|
|
@@ -20096,6 +20114,94 @@ var editsOf = (input) => {
|
|
|
20096
20114
|
return [];
|
|
20097
20115
|
}
|
|
20098
20116
|
};
|
|
20117
|
+
var shellScript2 = (words) => {
|
|
20118
|
+
if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
|
|
20119
|
+
return null;
|
|
20120
|
+
for (let index = 1;index < words.length; index++) {
|
|
20121
|
+
const word = words[index];
|
|
20122
|
+
if (/^-[a-z]*c$/.test(word))
|
|
20123
|
+
return words[index + 1] ?? null;
|
|
20124
|
+
if (/^[-+][oO]$/.test(word))
|
|
20125
|
+
index++;
|
|
20126
|
+
else if (!/^[-+]/.test(word))
|
|
20127
|
+
return null;
|
|
20128
|
+
}
|
|
20129
|
+
return null;
|
|
20130
|
+
};
|
|
20131
|
+
var XARGS_VALUE = /^-[IinLPdsEa]$/;
|
|
20132
|
+
var runsWriter = (words) => {
|
|
20133
|
+
const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
|
|
20134
|
+
const edits = editsOf(filled);
|
|
20135
|
+
return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
|
|
20136
|
+
};
|
|
20137
|
+
var opaqueWrites = (input) => {
|
|
20138
|
+
let words = input;
|
|
20139
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
|
|
20140
|
+
if (words[0] === "xargs") {
|
|
20141
|
+
let at = 1;
|
|
20142
|
+
while (words[at]?.startsWith("-"))
|
|
20143
|
+
at += XARGS_VALUE.test(words[at]) ? 2 : 1;
|
|
20144
|
+
return runsWriter(words.slice(at)) ? ["."] : null;
|
|
20145
|
+
}
|
|
20146
|
+
words = words.slice(1);
|
|
20147
|
+
}
|
|
20148
|
+
const [verb, ...args] = words;
|
|
20149
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20150
|
+
const files = operands(args);
|
|
20151
|
+
const has = (pattern) => args.some((arg) => pattern.test(arg));
|
|
20152
|
+
const listed = (named) => named.length ? named : ["."];
|
|
20153
|
+
if (name === "unlink" || name === "shred")
|
|
20154
|
+
return listed(files);
|
|
20155
|
+
if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
|
|
20156
|
+
return listed(has(/^--reference/) ? files : files.slice(1));
|
|
20157
|
+
if (name === "rsync") {
|
|
20158
|
+
const dest = files.at(-1);
|
|
20159
|
+
const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
|
|
20160
|
+
return dest === undefined ? ["."] : remote ? [] : [dest];
|
|
20161
|
+
}
|
|
20162
|
+
if (name === "find") {
|
|
20163
|
+
const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
|
|
20164
|
+
const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
|
|
20165
|
+
const end = args.findIndex((arg) => /^[-(!]/.test(arg));
|
|
20166
|
+
return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
|
|
20167
|
+
}
|
|
20168
|
+
if (/^g?awk$/.test(name)) {
|
|
20169
|
+
if (!has(/^-i|^--include/))
|
|
20170
|
+
return null;
|
|
20171
|
+
const named = [];
|
|
20172
|
+
let program = !has(/^-f$|^--file/);
|
|
20173
|
+
for (let index = 0;index < args.length; index++)
|
|
20174
|
+
if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
|
|
20175
|
+
index++;
|
|
20176
|
+
else if (args[index].startsWith("-"))
|
|
20177
|
+
continue;
|
|
20178
|
+
else if (program)
|
|
20179
|
+
program = false;
|
|
20180
|
+
else
|
|
20181
|
+
named.push(args[index]);
|
|
20182
|
+
return listed(named);
|
|
20183
|
+
}
|
|
20184
|
+
if (name === "perl")
|
|
20185
|
+
return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
|
|
20186
|
+
if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
|
|
20187
|
+
return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
|
|
20188
|
+
return shellScript2(words) === null ? null : ["."];
|
|
20189
|
+
};
|
|
20190
|
+
var linkSources = (args, at) => {
|
|
20191
|
+
const names = operands([...args]);
|
|
20192
|
+
const into = args.indexOf("-t");
|
|
20193
|
+
const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
|
|
20194
|
+
const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
|
|
20195
|
+
const target = dest === undefined ? null : at(dest);
|
|
20196
|
+
const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
|
|
20197
|
+
const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync7(target) || /[\\/]$/.test(dest ?? "") ? target : path27.dirname(target);
|
|
20198
|
+
return sources.flatMap((source) => {
|
|
20199
|
+
if (symbolic && base !== null && !path27.isAbsolute(homeExpanded(source)))
|
|
20200
|
+
return [path27.resolve(base, source)];
|
|
20201
|
+
const resolved = at(source);
|
|
20202
|
+
return resolved ? [resolved] : [];
|
|
20203
|
+
});
|
|
20204
|
+
};
|
|
20099
20205
|
var shellWrites = (command, depth = 0) => {
|
|
20100
20206
|
const targets = [];
|
|
20101
20207
|
let unknown = false;
|
|
@@ -20205,11 +20311,15 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
20205
20311
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
20206
20312
|
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
20207
20313
|
var POPD2 = new Set(["popd", "pop-location"]);
|
|
20208
|
-
var
|
|
20314
|
+
var UNKNOWN_DIR = path27.resolve(path27.parse(process.cwd()).root, ".workit-unknown-dir");
|
|
20315
|
+
var cdInto = (dir, words, strict = false) => {
|
|
20316
|
+
const lost = strict ? UNKNOWN_DIR : undefined;
|
|
20209
20317
|
const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
20210
20318
|
const target = named === undefined ? undefined : homeExpanded(named);
|
|
20211
|
-
if (
|
|
20212
|
-
return;
|
|
20319
|
+
if (target === "-" || target && /^~|[$`*?]/.test(target))
|
|
20320
|
+
return lost;
|
|
20321
|
+
if (dir === undefined)
|
|
20322
|
+
return lost;
|
|
20213
20323
|
return target === undefined ? os10.homedir() : path27.resolve(dir, target);
|
|
20214
20324
|
};
|
|
20215
20325
|
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
@@ -20217,7 +20327,7 @@ var homeExpanded = (file) => {
|
|
|
20217
20327
|
const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
|
|
20218
20328
|
return match ? (process.env.HOME || os10.homedir()) + file.slice(match[0].length) : file;
|
|
20219
20329
|
};
|
|
20220
|
-
var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
20330
|
+
var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
20221
20331
|
const targets = [];
|
|
20222
20332
|
let dir = cwd;
|
|
20223
20333
|
let stack = [];
|
|
@@ -20241,21 +20351,35 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
|
20241
20351
|
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
20242
20352
|
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20243
20353
|
if (CD2.has(name)) {
|
|
20244
|
-
dir = cdInto(dir, words);
|
|
20354
|
+
dir = cdInto(dir, words, strict);
|
|
20245
20355
|
continue;
|
|
20246
20356
|
}
|
|
20247
20357
|
if (PUSHD2.has(name)) {
|
|
20248
20358
|
stack.push(dir);
|
|
20249
|
-
dir = cdInto(dir, words);
|
|
20359
|
+
dir = cdInto(dir, words, strict);
|
|
20250
20360
|
continue;
|
|
20251
20361
|
}
|
|
20252
20362
|
if (POPD2.has(name)) {
|
|
20253
|
-
dir = stack.length ? stack.pop() : undefined;
|
|
20363
|
+
dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
|
|
20254
20364
|
continue;
|
|
20255
20365
|
}
|
|
20256
|
-
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
20366
|
+
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
20257
20367
|
if (inner !== null && dir !== undefined)
|
|
20258
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
20368
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
20369
|
+
if (strict && dir !== undefined) {
|
|
20370
|
+
const runIn = dir;
|
|
20371
|
+
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
20372
|
+
targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
|
|
20373
|
+
const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
|
|
20374
|
+
for (const file of opaque ?? []) {
|
|
20375
|
+
const resolved = at(file);
|
|
20376
|
+
if (resolved)
|
|
20377
|
+
targets.push(file === "." ? path27.join(resolved, DIR_TARGET) : resolved);
|
|
20378
|
+
}
|
|
20379
|
+
const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
|
|
20380
|
+
if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
|
|
20381
|
+
targets.push(...linkSources(words.slice(ln + 1), at));
|
|
20382
|
+
}
|
|
20259
20383
|
for (const file of segment.redirects)
|
|
20260
20384
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
20261
20385
|
const resolved = at(file);
|
|
@@ -20272,11 +20396,29 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
|
20272
20396
|
if (resolved)
|
|
20273
20397
|
targets.push(resolved);
|
|
20274
20398
|
}
|
|
20275
|
-
if (dir !== undefined
|
|
20276
|
-
|
|
20399
|
+
if (dir !== undefined)
|
|
20400
|
+
for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
|
|
20401
|
+
const { at: runsIn, args } = workitCwd(dir, invocation);
|
|
20402
|
+
if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
|
|
20403
|
+
targets.push(path27.join(runsIn, DIR_TARGET));
|
|
20404
|
+
}
|
|
20277
20405
|
}
|
|
20278
20406
|
return targets;
|
|
20279
20407
|
};
|
|
20408
|
+
var workitCwd = (dir, args) => {
|
|
20409
|
+
const { rest, cwd } = skipWorkitGlobals(args);
|
|
20410
|
+
return { at: cwd === null ? dir : path27.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
20411
|
+
};
|
|
20412
|
+
var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
|
|
20413
|
+
try {
|
|
20414
|
+
return [
|
|
20415
|
+
...segmentTargets(cwd, command, dialect, 0, strict),
|
|
20416
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
20417
|
+
];
|
|
20418
|
+
} catch {
|
|
20419
|
+
return [];
|
|
20420
|
+
}
|
|
20421
|
+
};
|
|
20280
20422
|
var overridden = (command, dialect) => {
|
|
20281
20423
|
const segments = segmentsOf(command, dialect);
|
|
20282
20424
|
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
@@ -20382,16 +20524,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
20382
20524
|
} catch {}
|
|
20383
20525
|
const writes = shellWrites(command);
|
|
20384
20526
|
let targets = [];
|
|
20385
|
-
if (owner)
|
|
20386
|
-
|
|
20387
|
-
targets = [
|
|
20388
|
-
...segmentTargets(cwd, command, dialect),
|
|
20389
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
20390
|
-
];
|
|
20391
|
-
} catch {
|
|
20392
|
-
targets = [];
|
|
20393
|
-
}
|
|
20394
|
-
}
|
|
20527
|
+
if (owner)
|
|
20528
|
+
targets = shellMutationTargets(cwd, command, dialect);
|
|
20395
20529
|
if (owner && targets.length) {
|
|
20396
20530
|
const live = liveGate(owner, targets, deps);
|
|
20397
20531
|
if (live.kind !== "none") {
|
|
@@ -20858,13 +20992,13 @@ ${line}` };
|
|
|
20858
20992
|
return decision;
|
|
20859
20993
|
};
|
|
20860
20994
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20861
|
-
import { existsSync as
|
|
20995
|
+
import { existsSync as existsSync8, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
20862
20996
|
import path28 from "node:path";
|
|
20863
20997
|
var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20864
20998
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
20865
20999
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
20866
21000
|
var existingDirectory = (value) => {
|
|
20867
|
-
if (!nonEmpty2(value) || !path28.isAbsolute(value) || !
|
|
21001
|
+
if (!nonEmpty2(value) || !path28.isAbsolute(value) || !existsSync8(value))
|
|
20868
21002
|
return null;
|
|
20869
21003
|
try {
|
|
20870
21004
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -21067,9 +21201,266 @@ var cwdRewrite = (command) => {
|
|
|
21067
21201
|
return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
|
|
21068
21202
|
};
|
|
21069
21203
|
|
|
21204
|
+
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
21205
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
21206
|
+
import fs23 from "node:fs";
|
|
21207
|
+
import path30 from "node:path";
|
|
21208
|
+
var FANOUT_TOKEN_FILE = "workit-fanout-token";
|
|
21209
|
+
var NONE5 = { kind: "none" };
|
|
21210
|
+
var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
|
|
21211
|
+
var canonical3 = (file) => {
|
|
21212
|
+
let rest = "";
|
|
21213
|
+
for (let dir = path30.resolve(file);; dir = path30.dirname(dir)) {
|
|
21214
|
+
try {
|
|
21215
|
+
return path30.join(fs23.realpathSync.native(dir), rest);
|
|
21216
|
+
} catch {
|
|
21217
|
+
if (path30.dirname(dir) === dir)
|
|
21218
|
+
return path30.resolve(file);
|
|
21219
|
+
rest = rest ? path30.join(path30.basename(dir), rest) : path30.basename(dir);
|
|
21220
|
+
}
|
|
21221
|
+
}
|
|
21222
|
+
};
|
|
21223
|
+
var within = (child, parent) => {
|
|
21224
|
+
const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
|
|
21225
|
+
const relative = path30.relative(fold(parent), fold(child));
|
|
21226
|
+
return relative === "" || !relative.startsWith("..") && !path30.isAbsolute(relative);
|
|
21227
|
+
};
|
|
21228
|
+
var checkoutOf2 = (file) => {
|
|
21229
|
+
for (let dir = canonical3(file);; dir = path30.dirname(dir)) {
|
|
21230
|
+
const dotGit = path30.join(dir, ".git");
|
|
21231
|
+
if (fs23.existsSync(dotGit)) {
|
|
21232
|
+
if (!fs23.statSync(dotGit).isFile())
|
|
21233
|
+
return { root: dir, workit: false };
|
|
21234
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
21235
|
+
return {
|
|
21236
|
+
root: dir,
|
|
21237
|
+
workit: Boolean(admin) && fs23.existsSync(path30.join(path30.resolve(dir, admin), FANOUT_TOKEN_FILE))
|
|
21238
|
+
};
|
|
21239
|
+
}
|
|
21240
|
+
if (path30.dirname(dir) === dir)
|
|
21241
|
+
return null;
|
|
21242
|
+
}
|
|
21243
|
+
};
|
|
21244
|
+
var workitWorktreeOf = (file) => {
|
|
21245
|
+
try {
|
|
21246
|
+
const checkout = checkoutOf2(file);
|
|
21247
|
+
return checkout?.workit ? checkout.root : null;
|
|
21248
|
+
} catch {
|
|
21249
|
+
return null;
|
|
21250
|
+
}
|
|
21251
|
+
};
|
|
21252
|
+
var bindingDir = () => path30.join(resolveStateDir(), "worktree-bind");
|
|
21253
|
+
var bindingFile = (session, agentId) => path30.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
|
|
21254
|
+
var readBinding = (file) => {
|
|
21255
|
+
try {
|
|
21256
|
+
const value = JSON.parse(fs23.readFileSync(file, "utf8"));
|
|
21257
|
+
return typeof value.worktree === "string" ? value : null;
|
|
21258
|
+
} catch {
|
|
21259
|
+
return null;
|
|
21260
|
+
}
|
|
21261
|
+
};
|
|
21262
|
+
var boundWorktree = (input) => {
|
|
21263
|
+
const agentId = input.session.agentId;
|
|
21264
|
+
if (!agentId)
|
|
21265
|
+
return null;
|
|
21266
|
+
const binding = readBinding(bindingFile(input.session.id, agentId));
|
|
21267
|
+
return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
|
|
21268
|
+
};
|
|
21269
|
+
var bind = (input, worktree) => {
|
|
21270
|
+
const agentId = input.session.agentId;
|
|
21271
|
+
const file = bindingFile(input.session.id, agentId);
|
|
21272
|
+
const binding = {
|
|
21273
|
+
worktree,
|
|
21274
|
+
session: input.session.id,
|
|
21275
|
+
agentId,
|
|
21276
|
+
at: new Date().toISOString()
|
|
21277
|
+
};
|
|
21278
|
+
fs23.mkdirSync(path30.dirname(file), { recursive: true });
|
|
21279
|
+
fs23.writeFileSync(file, JSON.stringify(binding));
|
|
21280
|
+
try {
|
|
21281
|
+
if (inWorkitWorkspace(input.cwd))
|
|
21282
|
+
appendHookObserved(input.cwd, {
|
|
21283
|
+
type: "worktree.bound",
|
|
21284
|
+
actor: { host: input.host, session: input.session.id, agentId },
|
|
21285
|
+
agentType: input.session.agentType,
|
|
21286
|
+
worktree
|
|
21287
|
+
});
|
|
21288
|
+
} catch {}
|
|
21289
|
+
};
|
|
21290
|
+
var hostIsolated = (cwd, agentId) => {
|
|
21291
|
+
try {
|
|
21292
|
+
if (agentId === null)
|
|
21293
|
+
return false;
|
|
21294
|
+
const checkout = checkoutOf2(cwd);
|
|
21295
|
+
if (!checkout)
|
|
21296
|
+
return false;
|
|
21297
|
+
const dotGit = path30.join(checkout.root, ".git");
|
|
21298
|
+
if (!fs23.statSync(dotGit).isFile())
|
|
21299
|
+
return false;
|
|
21300
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
21301
|
+
if (!admin)
|
|
21302
|
+
return false;
|
|
21303
|
+
const adminDir = path30.resolve(checkout.root, admin);
|
|
21304
|
+
const back = fs23.readFileSync(path30.join(adminDir, "gitdir"), "utf8").trim();
|
|
21305
|
+
if (path30.relative(canonical3(path30.resolve(adminDir, back)), canonical3(dotGit)) !== "")
|
|
21306
|
+
return false;
|
|
21307
|
+
const common = path30.join(adminDir, "commondir");
|
|
21308
|
+
const gitDir = fs23.existsSync(common) ? path30.resolve(adminDir, fs23.readFileSync(common, "utf8").trim()) : adminDir;
|
|
21309
|
+
const expected = path30.join(path30.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
|
|
21310
|
+
return path30.relative(canonical3(expected), checkout.root) === "";
|
|
21311
|
+
} catch {
|
|
21312
|
+
return false;
|
|
21313
|
+
}
|
|
21314
|
+
};
|
|
21315
|
+
var shown = (target) => path30.basename(target) === ".workit-live-target" ? path30.dirname(target) : target;
|
|
21316
|
+
var unboundDeny = (input, target) => {
|
|
21317
|
+
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.";
|
|
21318
|
+
return {
|
|
21319
|
+
kind: "deny",
|
|
21320
|
+
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}`,
|
|
21321
|
+
unblock
|
|
21322
|
+
};
|
|
21323
|
+
};
|
|
21324
|
+
var outsideDeny = (input, worktree, target) => {
|
|
21325
|
+
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.`;
|
|
21326
|
+
return {
|
|
21327
|
+
kind: "deny",
|
|
21328
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
21329
|
+
unblock
|
|
21330
|
+
};
|
|
21331
|
+
};
|
|
21332
|
+
var unknownDirDeny = (input, worktree) => {
|
|
21333
|
+
const wt = worktree ?? "<worktree>";
|
|
21334
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
21335
|
+
return {
|
|
21336
|
+
kind: "deny",
|
|
21337
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
21338
|
+
unblock
|
|
21339
|
+
};
|
|
21340
|
+
};
|
|
21341
|
+
function confineImplementer(input, targets) {
|
|
21342
|
+
try {
|
|
21343
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
21344
|
+
return NONE5;
|
|
21345
|
+
if (targets.some((target) => within(path30.resolve(target), UNKNOWN_DIR)))
|
|
21346
|
+
return unknownDirDeny(input, boundWorktree(input));
|
|
21347
|
+
const bindings = canonical3(bindingDir());
|
|
21348
|
+
const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
|
|
21349
|
+
if (judged.length === 0)
|
|
21350
|
+
return NONE5;
|
|
21351
|
+
let worktree = boundWorktree(input);
|
|
21352
|
+
if (!worktree) {
|
|
21353
|
+
for (const { real } of judged) {
|
|
21354
|
+
worktree = within(real, bindings) ? null : workitWorktreeOf(real);
|
|
21355
|
+
if (worktree)
|
|
21356
|
+
break;
|
|
21357
|
+
}
|
|
21358
|
+
if (!worktree)
|
|
21359
|
+
return unboundDeny(input, judged[0].named);
|
|
21360
|
+
bind(input, worktree);
|
|
21361
|
+
}
|
|
21362
|
+
const root = canonical3(worktree);
|
|
21363
|
+
const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
|
|
21364
|
+
return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
|
|
21365
|
+
} catch {
|
|
21366
|
+
return NONE5;
|
|
21367
|
+
}
|
|
21368
|
+
}
|
|
21369
|
+
var MUTATING_REMOTE = new Set([
|
|
21370
|
+
"add",
|
|
21371
|
+
"remove",
|
|
21372
|
+
"rm",
|
|
21373
|
+
"rename",
|
|
21374
|
+
"set-url",
|
|
21375
|
+
"set-head",
|
|
21376
|
+
"set-branches",
|
|
21377
|
+
"prune"
|
|
21378
|
+
]);
|
|
21379
|
+
var CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
|
|
21380
|
+
var CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
|
|
21381
|
+
var CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
|
|
21382
|
+
var leadOwnedGit = (args) => {
|
|
21383
|
+
const [sub = "", ...rest] = args;
|
|
21384
|
+
if (rest.some((arg) => arg === "--help" || arg === "-h"))
|
|
21385
|
+
return false;
|
|
21386
|
+
const names = rest.filter((arg) => !arg.startsWith("-"));
|
|
21387
|
+
const flag = (pattern) => rest.some((arg) => pattern.test(arg));
|
|
21388
|
+
switch (sub) {
|
|
21389
|
+
case "worktree":
|
|
21390
|
+
return names[0] !== undefined && names[0] !== "list";
|
|
21391
|
+
case "update-ref":
|
|
21392
|
+
return true;
|
|
21393
|
+
case "symbolic-ref":
|
|
21394
|
+
return names.length > 1 || flag(/^(?:-d|--delete)$/);
|
|
21395
|
+
case "branch":
|
|
21396
|
+
return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
|
|
21397
|
+
case "tag":
|
|
21398
|
+
return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
|
|
21399
|
+
case "push": {
|
|
21400
|
+
if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
|
|
21401
|
+
return true;
|
|
21402
|
+
const refspecs = names.slice(1);
|
|
21403
|
+
return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
|
|
21404
|
+
}
|
|
21405
|
+
case "replace":
|
|
21406
|
+
case "filter-branch":
|
|
21407
|
+
case "gc":
|
|
21408
|
+
case "prune":
|
|
21409
|
+
case "repack":
|
|
21410
|
+
case "sparse-checkout":
|
|
21411
|
+
return true;
|
|
21412
|
+
case "notes":
|
|
21413
|
+
case "reflog":
|
|
21414
|
+
case "submodule":
|
|
21415
|
+
return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
|
|
21416
|
+
case "stash":
|
|
21417
|
+
return names[0] === "drop" || names[0] === "clear";
|
|
21418
|
+
case "remote":
|
|
21419
|
+
return MUTATING_REMOTE.has(names[0] ?? "");
|
|
21420
|
+
case "config": {
|
|
21421
|
+
if (rest.some((arg) => CONFIG_READ.test(arg)))
|
|
21422
|
+
return false;
|
|
21423
|
+
if (rest.some((arg) => CONFIG_WRITE.test(arg)))
|
|
21424
|
+
return true;
|
|
21425
|
+
let operands = 0;
|
|
21426
|
+
for (let index = 0;index < rest.length; index++)
|
|
21427
|
+
if (CONFIG_VALUE.test(rest[index]))
|
|
21428
|
+
index++;
|
|
21429
|
+
else if (!rest[index].startsWith("-"))
|
|
21430
|
+
operands++;
|
|
21431
|
+
return operands > 1;
|
|
21432
|
+
}
|
|
21433
|
+
default:
|
|
21434
|
+
return false;
|
|
21435
|
+
}
|
|
21436
|
+
};
|
|
21437
|
+
var leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
|
|
21438
|
+
if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
|
|
21439
|
+
return false;
|
|
21440
|
+
const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
|
|
21441
|
+
return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
|
|
21442
|
+
});
|
|
21443
|
+
var leadOwnedDeny = (input, command) => {
|
|
21444
|
+
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.";
|
|
21445
|
+
return {
|
|
21446
|
+
kind: "deny",
|
|
21447
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
|
|
21448
|
+
unblock
|
|
21449
|
+
};
|
|
21450
|
+
};
|
|
21451
|
+
function confineImplementerShell(input, command, dialect = "posix") {
|
|
21452
|
+
try {
|
|
21453
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
21454
|
+
return NONE5;
|
|
21455
|
+
if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
|
|
21456
|
+
return leadOwnedDeny(input, command);
|
|
21457
|
+
} catch {}
|
|
21458
|
+
return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
|
|
21459
|
+
}
|
|
21460
|
+
|
|
21070
21461
|
// packages/workit-core/src/hooks/handle.ts
|
|
21071
21462
|
var usable2 = (value) => value === "native" || value === "partial";
|
|
21072
|
-
var
|
|
21463
|
+
var NONE6 = { kind: "none" };
|
|
21073
21464
|
var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
|
|
21074
21465
|
var offered = new Set;
|
|
21075
21466
|
var ROLES = {
|
|
@@ -21082,6 +21473,7 @@ var workitRole = (descriptor, agentType) => {
|
|
|
21082
21473
|
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
21083
21474
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
21084
21475
|
};
|
|
21476
|
+
var implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer";
|
|
21085
21477
|
var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
|
|
21086
21478
|
var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
21087
21479
|
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\`).`;
|
|
@@ -21089,7 +21481,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
21089
21481
|
const session = input.session.id ?? null;
|
|
21090
21482
|
const role = workitRole(descriptor, event.agentType);
|
|
21091
21483
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
21092
|
-
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer"
|
|
21484
|
+
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
|
|
21093
21485
|
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
21094
21486
|
if (role === "implementer" && isolated)
|
|
21095
21487
|
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.`;
|
|
@@ -21097,7 +21489,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
21097
21489
|
return `${isolatedGit(input.cwd)}
|
|
21098
21490
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
21099
21491
|
if (role === "implementer")
|
|
21100
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes
|
|
21492
|
+
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.`;
|
|
21101
21493
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
21102
21494
|
if (role !== "judge" || !event.agentId)
|
|
21103
21495
|
return readOnly;
|
|
@@ -21128,7 +21520,7 @@ function decide(input, deps) {
|
|
|
21128
21520
|
switch (event.kind) {
|
|
21129
21521
|
case "session.start": {
|
|
21130
21522
|
if (!usable2(descriptor.context.sessionStart))
|
|
21131
|
-
return
|
|
21523
|
+
return NONE6;
|
|
21132
21524
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
21133
21525
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
21134
21526
|
if (offer && key !== null)
|
|
@@ -21143,26 +21535,31 @@ function decide(input, deps) {
|
|
|
21143
21535
|
}
|
|
21144
21536
|
case "context.turn": {
|
|
21145
21537
|
if (!usable2(descriptor.context.perTurn))
|
|
21146
|
-
return
|
|
21538
|
+
return NONE6;
|
|
21147
21539
|
const current = turnContextText(input, descriptor);
|
|
21148
21540
|
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
21149
|
-
return withContextLine(text ? { kind: "context", text } :
|
|
21541
|
+
return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
|
|
21150
21542
|
}
|
|
21151
21543
|
case "shell.pre": {
|
|
21152
21544
|
const skillRead = skillReadIn(event.command, event.dialect);
|
|
21153
21545
|
if (skillRead)
|
|
21154
21546
|
recordSkillLoad(input, skillRead, "read");
|
|
21155
21547
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
21156
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) :
|
|
21548
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
|
|
21157
21549
|
if (policy.kind !== "none")
|
|
21158
21550
|
return policy;
|
|
21159
21551
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
21160
21552
|
if (!postTool)
|
|
21161
21553
|
settlePendingCommit(input);
|
|
21162
|
-
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) :
|
|
21554
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
|
|
21163
21555
|
if (raw.kind === "deny")
|
|
21164
21556
|
return raw;
|
|
21165
|
-
|
|
21557
|
+
if (canDeny && implementerAgent(input, descriptor)) {
|
|
21558
|
+
const confined = confineImplementerShell(input, event.command, event.dialect);
|
|
21559
|
+
if (confined.kind === "deny")
|
|
21560
|
+
return confined;
|
|
21561
|
+
}
|
|
21562
|
+
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
|
|
21166
21563
|
if (gate.kind === "deny")
|
|
21167
21564
|
return gate;
|
|
21168
21565
|
const plan = heavyGate(input.cwd, event.command, {
|
|
@@ -21185,10 +21582,10 @@ function decide(input, deps) {
|
|
|
21185
21582
|
command: rewrittenTo,
|
|
21186
21583
|
input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
|
|
21187
21584
|
text
|
|
21188
|
-
} :
|
|
21585
|
+
} : NONE6;
|
|
21189
21586
|
if (descriptor.transport !== "hook-process")
|
|
21190
21587
|
return rewritten(null);
|
|
21191
|
-
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) :
|
|
21588
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
|
|
21192
21589
|
if (!lease)
|
|
21193
21590
|
return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
|
|
21194
21591
|
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
@@ -21197,9 +21594,17 @@ function decide(input, deps) {
|
|
|
21197
21594
|
case "shell.post":
|
|
21198
21595
|
if (usable2(descriptor.events["shell.post"].support))
|
|
21199
21596
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
21200
|
-
return
|
|
21201
|
-
case "write.pre":
|
|
21202
|
-
|
|
21597
|
+
return NONE6;
|
|
21598
|
+
case "write.pre": {
|
|
21599
|
+
if (!usable2(descriptor.events["write.pre"].support))
|
|
21600
|
+
return NONE6;
|
|
21601
|
+
if (implementerAgent(input, descriptor)) {
|
|
21602
|
+
const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
|
|
21603
|
+
if (confined.kind === "deny")
|
|
21604
|
+
return confined;
|
|
21605
|
+
}
|
|
21606
|
+
return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
|
|
21607
|
+
}
|
|
21203
21608
|
case "subagent.start":
|
|
21204
21609
|
if (workitRole(descriptor, event.agentType) === "judge")
|
|
21205
21610
|
trackJudge(input, event.agentId, true);
|
|
@@ -21216,22 +21621,22 @@ function decide(input, deps) {
|
|
|
21216
21621
|
case "tool.pre":
|
|
21217
21622
|
if (event.skill)
|
|
21218
21623
|
recordSkillLoad(input, event.skill, "tool");
|
|
21219
|
-
return
|
|
21624
|
+
return NONE6;
|
|
21220
21625
|
case "stop":
|
|
21221
|
-
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) :
|
|
21626
|
+
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
|
|
21222
21627
|
case "subagent.stop":
|
|
21223
21628
|
trackJudge(input, event.agentId, false);
|
|
21224
|
-
return
|
|
21629
|
+
return NONE6;
|
|
21225
21630
|
case "prompt.submit":
|
|
21226
|
-
return
|
|
21631
|
+
return NONE6;
|
|
21227
21632
|
}
|
|
21228
21633
|
}
|
|
21229
21634
|
var failureDecision = (descriptor, event, error) => {
|
|
21230
21635
|
if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
|
|
21231
|
-
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } :
|
|
21636
|
+
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE6;
|
|
21232
21637
|
if (event === "session.start" || event === "subagent.start")
|
|
21233
21638
|
return { kind: "context", text: `[workit diagnostic: ${error}]` };
|
|
21234
|
-
return
|
|
21639
|
+
return NONE6;
|
|
21235
21640
|
};
|
|
21236
21641
|
var dispatchHook = (adapter, raw, env = process.env) => {
|
|
21237
21642
|
const parsed = adapter.parse(raw, env);
|
|
@@ -21257,7 +21662,7 @@ var dispatchHook = (adapter, raw, env = process.env) => {
|
|
|
21257
21662
|
};
|
|
21258
21663
|
// packages/workit-core/src/hooks/deadline.ts
|
|
21259
21664
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
21260
|
-
import
|
|
21665
|
+
import path31 from "node:path";
|
|
21261
21666
|
var HOOK_SKIP_TEXT = "workit context skipped: host under load";
|
|
21262
21667
|
var HOOK_DEADLINE_MS = 8000;
|
|
21263
21668
|
var HOST_DEADLINES = {
|
|
@@ -21275,7 +21680,7 @@ var hookDeadlineMs = (env, host = "", native) => {
|
|
|
21275
21680
|
const row = HOST_DEADLINES[host];
|
|
21276
21681
|
return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
|
|
21277
21682
|
};
|
|
21278
|
-
var skipsFile = (stateDir) =>
|
|
21683
|
+
var skipsFile = (stateDir) => path31.join(stateDir, HOOK_SKIPS_FILE);
|
|
21279
21684
|
var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
|
|
21280
21685
|
try {
|
|
21281
21686
|
mkdirSync3(stateDir, { recursive: true });
|
|
@@ -21293,7 +21698,7 @@ var QUIET = new Set([
|
|
|
21293
21698
|
]);
|
|
21294
21699
|
var skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT };
|
|
21295
21700
|
var TIMED_OUT = Symbol("timed out");
|
|
21296
|
-
var
|
|
21701
|
+
var within2 = async (work, ms) => {
|
|
21297
21702
|
let timer;
|
|
21298
21703
|
const expired = new Promise((resolve) => {
|
|
21299
21704
|
timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
|
|
@@ -21324,7 +21729,7 @@ var readWithin = async (stdin, budget) => {
|
|
|
21324
21729
|
text += String(chunk);
|
|
21325
21730
|
return text;
|
|
21326
21731
|
})();
|
|
21327
|
-
const text = await
|
|
21732
|
+
const text = await within2(read, budget.startedAt + budget.deadlineMs - budget.now());
|
|
21328
21733
|
return isTimedOut(text) ? null : text;
|
|
21329
21734
|
};
|
|
21330
21735
|
var note = (adapter, raw, env, budget, outcome, stage) => {
|
|
@@ -21353,7 +21758,7 @@ var dispatchWithin = async (adapter, raw, env, budget, dispatch = dispatchHook)
|
|
|
21353
21758
|
if (budget.host !== null && typeof native === "string")
|
|
21354
21759
|
budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
|
|
21355
21760
|
const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
|
|
21356
|
-
const result = remaining() > 0 ? await
|
|
21761
|
+
const result = remaining() > 0 ? await within2(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
|
|
21357
21762
|
if (isTimedOut(result))
|
|
21358
21763
|
return skippedHook(adapter, raw, env, budget, "dispatch");
|
|
21359
21764
|
if (remaining() < 0)
|
|
@@ -21919,8 +22324,8 @@ var codexAdapter = {
|
|
|
21919
22324
|
};
|
|
21920
22325
|
|
|
21921
22326
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
21922
|
-
import
|
|
21923
|
-
import
|
|
22327
|
+
import fs24 from "node:fs";
|
|
22328
|
+
import path32 from "node:path";
|
|
21924
22329
|
var undocumented = { support: "undocumented", native: null };
|
|
21925
22330
|
var none = { support: "none", native: null };
|
|
21926
22331
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -22117,17 +22522,17 @@ var protocolEvent2 = (input) => {
|
|
|
22117
22522
|
};
|
|
22118
22523
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
22119
22524
|
var lastAssistantText = (file) => {
|
|
22120
|
-
if (!file || !
|
|
22525
|
+
if (!file || !path32.isAbsolute(file))
|
|
22121
22526
|
return null;
|
|
22122
22527
|
try {
|
|
22123
|
-
const size =
|
|
22528
|
+
const size = fs24.statSync(file).size;
|
|
22124
22529
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
22125
22530
|
const buffer = Buffer.alloc(size - start);
|
|
22126
|
-
const fd =
|
|
22531
|
+
const fd = fs24.openSync(file, "r");
|
|
22127
22532
|
try {
|
|
22128
|
-
|
|
22533
|
+
fs24.readSync(fd, buffer, 0, buffer.length, start);
|
|
22129
22534
|
} finally {
|
|
22130
|
-
|
|
22535
|
+
fs24.closeSync(fd);
|
|
22131
22536
|
}
|
|
22132
22537
|
const lines = buffer.toString("utf8").split(`
|
|
22133
22538
|
`);
|
|
@@ -22154,7 +22559,7 @@ var lastAssistantText = (file) => {
|
|
|
22154
22559
|
return null;
|
|
22155
22560
|
}
|
|
22156
22561
|
};
|
|
22157
|
-
var
|
|
22562
|
+
var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path32.sep) ? root : `${root}${path32.sep}`);
|
|
22158
22563
|
var hookCwd = (input) => {
|
|
22159
22564
|
const cwd = existingDirectory(input.cwd);
|
|
22160
22565
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
@@ -22163,7 +22568,7 @@ var hookCwd = (input) => {
|
|
|
22163
22568
|
};
|
|
22164
22569
|
var cursorWorkspaceRoot = (input) => {
|
|
22165
22570
|
const cwd = existingDirectory(input.cwd);
|
|
22166
|
-
return cwd && input.workspace_roots.find((root) =>
|
|
22571
|
+
return cwd && input.workspace_roots.find((root) => within3(root, cwd)) || input.workspace_roots[0];
|
|
22167
22572
|
};
|
|
22168
22573
|
var cursorDeny = (reason) => ({
|
|
22169
22574
|
permission: "deny",
|