@brainervirus/workit-cursor 8.16.0 → 8.17.1
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 +1149 -104
- package/dist/mcp-server.js +141 -2
- package/dist/workit-hook.js +1170 -101
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +7 -7
- package/skills/workit-fanout/references/brief.md +3 -1
- package/skills/workit-implement/SKILL.md +8 -1
- package/skills/workit-review/SKILL.md +2 -1
- package/skills/workit-verify-app/SKILL.md +2 -0
|
@@ -9711,6 +9711,7 @@ var init_workspaces = __esm(() => {
|
|
|
9711
9711
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
9712
9712
|
defaultEndpoint: unknown().optional(),
|
|
9713
9713
|
verification: _enum(["self", "independent"]).optional(),
|
|
9714
|
+
delegation: unknown().optional(),
|
|
9714
9715
|
trailers: boolean2().optional(),
|
|
9715
9716
|
liveSessionGuard: boolean2().optional(),
|
|
9716
9717
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
@@ -15032,6 +15033,16 @@ var init_workers = __esm(() => {
|
|
|
15032
15033
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
15033
15034
|
import fs12 from "node:fs";
|
|
15034
15035
|
import path16 from "node:path";
|
|
15036
|
+
function repoHeavyCommands(root) {
|
|
15037
|
+
try {
|
|
15038
|
+
const value = JSON.parse(fs12.readFileSync(checksConfigFile(root), "utf8"));
|
|
15039
|
+
const resources = isRecord3(value) ? value.resources : undefined;
|
|
15040
|
+
const list = isRecord3(resources) ? resources.heavyCommands : undefined;
|
|
15041
|
+
return Array.isArray(list) ? list.filter((item) => typeof item === "string") : [];
|
|
15042
|
+
} catch {
|
|
15043
|
+
return [];
|
|
15044
|
+
}
|
|
15045
|
+
}
|
|
15035
15046
|
function splitCommand(command) {
|
|
15036
15047
|
const argv = [];
|
|
15037
15048
|
let current = "";
|
|
@@ -15112,7 +15123,7 @@ function detectedChecks(root) {
|
|
|
15112
15123
|
}
|
|
15113
15124
|
function loadCheckConfig(cwd) {
|
|
15114
15125
|
const root = checkRoot(cwd);
|
|
15115
|
-
const file =
|
|
15126
|
+
const file = checksConfigFile(root);
|
|
15116
15127
|
const none = (error = null) => ({
|
|
15117
15128
|
source: error ? CHECKS_FILE : "none",
|
|
15118
15129
|
root,
|
|
@@ -15196,7 +15207,7 @@ function workitAvailable(env = process.env) {
|
|
|
15196
15207
|
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
15197
15208
|
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
15198
15209
|
}
|
|
15199
|
-
var SCRIPT_CHECKS, CHECKS_FILE = "workit.checks.json", isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git4 = (cwd, args) => {
|
|
15210
|
+
var SCRIPT_CHECKS, CHECKS_FILE = "workit.checks.json", checksConfigFile = (root) => path16.join(root, CHECKS_FILE), isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git4 = (cwd, args) => {
|
|
15200
15211
|
const run = spawnSync9("git", args, {
|
|
15201
15212
|
cwd,
|
|
15202
15213
|
encoding: "utf8",
|
|
@@ -18749,7 +18760,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
18749
18760
|
RECENT.delete(RECENT.keys().next().value);
|
|
18750
18761
|
return found;
|
|
18751
18762
|
}
|
|
18752
|
-
function
|
|
18763
|
+
function commandLines(command, dialect = "posix") {
|
|
18764
|
+
const lines = [];
|
|
18765
|
+
try {
|
|
18766
|
+
walk2(command, dialect, 0, lines);
|
|
18767
|
+
} catch (error) {
|
|
18768
|
+
throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
|
|
18769
|
+
}
|
|
18770
|
+
return lines;
|
|
18771
|
+
}
|
|
18772
|
+
function walk2(command, dialect, depth, seen) {
|
|
18753
18773
|
const found = [];
|
|
18754
18774
|
let dir = null;
|
|
18755
18775
|
const groups = [];
|
|
@@ -18760,7 +18780,8 @@ function walk2(command, dialect, depth) {
|
|
|
18760
18780
|
const nested = (script, inner) => {
|
|
18761
18781
|
if (!script || depth >= MAX_DEPTH)
|
|
18762
18782
|
return;
|
|
18763
|
-
|
|
18783
|
+
const inside = seen ? walk2(script, inner, depth + 1, seen) : rawInvocations(script, inner, depth + 1);
|
|
18784
|
+
for (const invocation of inside)
|
|
18764
18785
|
found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
|
|
18765
18786
|
};
|
|
18766
18787
|
const visit = (input, stdin, hops) => {
|
|
@@ -18772,11 +18793,17 @@ function walk2(command, dialect, depth) {
|
|
|
18772
18793
|
const name = commandName(words[0]);
|
|
18773
18794
|
if (!name)
|
|
18774
18795
|
return;
|
|
18796
|
+
seen?.push(words);
|
|
18797
|
+
if (name === "-c" || name === "--command")
|
|
18798
|
+
return nested(words[1], "posix");
|
|
18775
18799
|
const runs = (lines) => {
|
|
18776
18800
|
if (hops < MAX_DEPTH)
|
|
18777
18801
|
for (const next of lines)
|
|
18778
18802
|
visit(next, undefined, hops + 1);
|
|
18779
18803
|
};
|
|
18804
|
+
const wrapped = seen ? null : workitCheckTail(words);
|
|
18805
|
+
if (wrapped)
|
|
18806
|
+
return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
|
|
18780
18807
|
if (CD.has(name)) {
|
|
18781
18808
|
const target = cdTarget(words);
|
|
18782
18809
|
dir = target === null ? os5.homedir() : joinDir(dir, target);
|
|
@@ -18853,6 +18880,49 @@ function walk2(command, dialect, depth) {
|
|
|
18853
18880
|
}
|
|
18854
18881
|
return found;
|
|
18855
18882
|
}
|
|
18883
|
+
function workitCheckTail(words) {
|
|
18884
|
+
let at = 0;
|
|
18885
|
+
if (PACKAGE_RUNNERS.has(commandName(words[0]))) {
|
|
18886
|
+
at = words.findIndex((word, index) => index > 0 && !word.startsWith("-"));
|
|
18887
|
+
if (at < 0 || !WORKIT_PACKAGE.test(words[at]))
|
|
18888
|
+
return null;
|
|
18889
|
+
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
18890
|
+
return null;
|
|
18891
|
+
const args = words.slice(at + 1);
|
|
18892
|
+
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
18893
|
+
return null;
|
|
18894
|
+
const split = args.indexOf("--");
|
|
18895
|
+
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
18896
|
+
if (!tail.length)
|
|
18897
|
+
return null;
|
|
18898
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
18899
|
+
}
|
|
18900
|
+
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
18901
|
+
let segments;
|
|
18902
|
+
try {
|
|
18903
|
+
segments = segmentsOf(command, dialect);
|
|
18904
|
+
} catch {
|
|
18905
|
+
return null;
|
|
18906
|
+
}
|
|
18907
|
+
if (segments.length !== 1)
|
|
18908
|
+
return null;
|
|
18909
|
+
const [segment] = segments;
|
|
18910
|
+
if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
|
|
18911
|
+
return null;
|
|
18912
|
+
const { words } = stripPrefixes(segment.words);
|
|
18913
|
+
const name = commandName(words[0]);
|
|
18914
|
+
if (!name || depth >= MAX_DEPTH)
|
|
18915
|
+
return null;
|
|
18916
|
+
if (SHELLS.has(name)) {
|
|
18917
|
+
const script = shellScript(words, undefined);
|
|
18918
|
+
return script ? simpleCommand(script, "posix", depth + 1) : null;
|
|
18919
|
+
}
|
|
18920
|
+
if (name === "-c" || name === "--command")
|
|
18921
|
+
return words[1] ? simpleCommand(words[1], "posix", depth + 1) : null;
|
|
18922
|
+
if (EVALS.has(name) || POWERSHELLS.has(name) || ["xargs", "find", "parallel", "script", "cmd", "source", "."].includes(name))
|
|
18923
|
+
return null;
|
|
18924
|
+
return words;
|
|
18925
|
+
}
|
|
18856
18926
|
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
18857
18927
|
const found = [];
|
|
18858
18928
|
for (const { words } of segmentsOf(command, dialect)) {
|
|
@@ -19269,7 +19339,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
19269
19339
|
...Object.keys(env).length ? { env } : {},
|
|
19270
19340
|
...stdin === undefined ? {} : { stdin }
|
|
19271
19341
|
};
|
|
19272
|
-
}, RECENT, LAUNCHERS, WORKIT_BIN, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
|
|
19342
|
+
}, RECENT, LAUNCHERS, WORKIT_BIN, WORKIT_PACKAGE, PACKAGE_RUNNERS, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
|
|
19273
19343
|
if (destination === "HEAD" || destination === "@" || CURRENT_BRANCH.test(destination))
|
|
19274
19344
|
return { name: null };
|
|
19275
19345
|
if (destination.startsWith("refs/heads/"))
|
|
@@ -19961,6 +20031,8 @@ var init_raw_git = __esm(() => {
|
|
|
19961
20031
|
"command"
|
|
19962
20032
|
]);
|
|
19963
20033
|
WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
20034
|
+
WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
|
|
20035
|
+
PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
|
|
19964
20036
|
PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
19965
20037
|
CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
19966
20038
|
VALUE_OPTIONS = {
|
|
@@ -20200,12 +20272,808 @@ var init_policy = __esm(() => {
|
|
|
20200
20272
|
init_shell_words();
|
|
20201
20273
|
});
|
|
20202
20274
|
|
|
20203
|
-
// packages/workit-core/src/core/
|
|
20204
|
-
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
20205
|
-
import { createHash as createHash15 } from "node:crypto";
|
|
20275
|
+
// packages/workit-core/src/core/heavy-rules.ts
|
|
20206
20276
|
import fs17 from "node:fs";
|
|
20207
20277
|
import os6 from "node:os";
|
|
20208
20278
|
import path22 from "node:path";
|
|
20279
|
+
function parseJsonc(text) {
|
|
20280
|
+
let out = "";
|
|
20281
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
20282
|
+
const char = text[index];
|
|
20283
|
+
if (char === '"') {
|
|
20284
|
+
const end = /"(?:[^"\\]|\\.)*"/y;
|
|
20285
|
+
end.lastIndex = index;
|
|
20286
|
+
const match = end.exec(text);
|
|
20287
|
+
if (!match)
|
|
20288
|
+
throw new Error("unterminated string");
|
|
20289
|
+
out += match[0];
|
|
20290
|
+
index += match[0].length - 1;
|
|
20291
|
+
} else if (char === "/" && text[index + 1] === "/") {
|
|
20292
|
+
while (index < text.length && text[index] !== `
|
|
20293
|
+
`)
|
|
20294
|
+
index += 1;
|
|
20295
|
+
out += `
|
|
20296
|
+
`;
|
|
20297
|
+
} else if (char === "/" && text[index + 1] === "*") {
|
|
20298
|
+
const close = text.indexOf("*/", index + 2);
|
|
20299
|
+
if (close < 0)
|
|
20300
|
+
throw new Error("unterminated comment");
|
|
20301
|
+
index = close + 1;
|
|
20302
|
+
} else
|
|
20303
|
+
out += char;
|
|
20304
|
+
}
|
|
20305
|
+
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
20306
|
+
}
|
|
20307
|
+
function gitTop(cwd) {
|
|
20308
|
+
for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
|
|
20309
|
+
if (fs17.existsSync(path22.join(dir, ".git")))
|
|
20310
|
+
return dir;
|
|
20311
|
+
if (path22.dirname(dir) === dir)
|
|
20312
|
+
return null;
|
|
20313
|
+
}
|
|
20314
|
+
}
|
|
20315
|
+
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
20316
|
+
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
20317
|
+
const user = env.CLAUDE_CONFIG_DIR || path22.join(homeOf(env), ".claude");
|
|
20318
|
+
return [
|
|
20319
|
+
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
20320
|
+
path22.join(user, "settings.json"),
|
|
20321
|
+
path22.join(project, ".claude", "settings.json"),
|
|
20322
|
+
path22.join(project, ".claude", "settings.local.json")
|
|
20323
|
+
];
|
|
20324
|
+
}
|
|
20325
|
+
function claudeRules(cwd, env = process.env) {
|
|
20326
|
+
const rules = EMPTY();
|
|
20327
|
+
try {
|
|
20328
|
+
for (const file of claudeSettingsFiles(cwd, env)) {
|
|
20329
|
+
const text = readText2(file);
|
|
20330
|
+
if (text === null)
|
|
20331
|
+
continue;
|
|
20332
|
+
const value = JSON.parse(text);
|
|
20333
|
+
if (!isRecord4(value))
|
|
20334
|
+
return "unreadable";
|
|
20335
|
+
const permissions = value.permissions;
|
|
20336
|
+
if (permissions === undefined)
|
|
20337
|
+
continue;
|
|
20338
|
+
if (!isRecord4(permissions))
|
|
20339
|
+
return "unreadable";
|
|
20340
|
+
for (const [key, bucket] of [
|
|
20341
|
+
["deny", rules.deny],
|
|
20342
|
+
["ask", rules.deny],
|
|
20343
|
+
["allow", rules.allow]
|
|
20344
|
+
]) {
|
|
20345
|
+
const list = permissions[key];
|
|
20346
|
+
if (list === undefined)
|
|
20347
|
+
continue;
|
|
20348
|
+
if (!Array.isArray(list))
|
|
20349
|
+
return "unreadable";
|
|
20350
|
+
for (const entry of list) {
|
|
20351
|
+
if (typeof entry !== "string")
|
|
20352
|
+
return "unreadable";
|
|
20353
|
+
const pattern = bashPattern(entry);
|
|
20354
|
+
if (pattern !== null)
|
|
20355
|
+
bucket.push(pattern);
|
|
20356
|
+
}
|
|
20357
|
+
}
|
|
20358
|
+
}
|
|
20359
|
+
} catch {
|
|
20360
|
+
return "unreadable";
|
|
20361
|
+
}
|
|
20362
|
+
return rules;
|
|
20363
|
+
}
|
|
20364
|
+
function opencodeRules(cwd, env = process.env) {
|
|
20365
|
+
const rules = EMPTY();
|
|
20366
|
+
try {
|
|
20367
|
+
const config = env.XDG_CONFIG_HOME || path22.join(homeOf(env), ".config");
|
|
20368
|
+
const files = [
|
|
20369
|
+
...OPENCODE_NAMES.map((name) => path22.join(config, "opencode", name)),
|
|
20370
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path22.join(dir, name), path22.join(dir, ".opencode", name)])),
|
|
20371
|
+
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
20372
|
+
];
|
|
20373
|
+
for (const file of files) {
|
|
20374
|
+
const text = readText2(file);
|
|
20375
|
+
if (text !== null && !opencodeConfig(parseJsonc(text), rules))
|
|
20376
|
+
return "unreadable";
|
|
20377
|
+
}
|
|
20378
|
+
if (env.OPENCODE_CONFIG_CONTENT && !opencodeConfig(parseJsonc(env.OPENCODE_CONFIG_CONTENT), rules))
|
|
20379
|
+
return "unreadable";
|
|
20380
|
+
} catch {
|
|
20381
|
+
return "unreadable";
|
|
20382
|
+
}
|
|
20383
|
+
return rules;
|
|
20384
|
+
}
|
|
20385
|
+
function codexRules(cwd, env = process.env) {
|
|
20386
|
+
const rules = EMPTY();
|
|
20387
|
+
try {
|
|
20388
|
+
const home = env.CODEX_HOME || path22.join(homeOf(env), ".codex");
|
|
20389
|
+
const top = gitTop(cwd) ?? cwd;
|
|
20390
|
+
for (const dir of [path22.join(home, "rules"), path22.join(top, ".codex", "rules")]) {
|
|
20391
|
+
let files;
|
|
20392
|
+
try {
|
|
20393
|
+
files = fs17.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
20394
|
+
} catch (error) {
|
|
20395
|
+
if (error.code === "ENOENT")
|
|
20396
|
+
continue;
|
|
20397
|
+
return "unreadable";
|
|
20398
|
+
}
|
|
20399
|
+
for (const name of files) {
|
|
20400
|
+
const text = readText2(path22.join(dir, name)) ?? "";
|
|
20401
|
+
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
20402
|
+
const rule = codexRule(part);
|
|
20403
|
+
if (!rule)
|
|
20404
|
+
return "unreadable";
|
|
20405
|
+
(rule.decision === "allow" ? rules.allow : rules.deny).push(rule.pattern);
|
|
20406
|
+
}
|
|
20407
|
+
}
|
|
20408
|
+
}
|
|
20409
|
+
} catch {
|
|
20410
|
+
return "unreadable";
|
|
20411
|
+
}
|
|
20412
|
+
return rules;
|
|
20413
|
+
}
|
|
20414
|
+
function cursorRules(cwd, env = process.env) {
|
|
20415
|
+
const rules = EMPTY();
|
|
20416
|
+
try {
|
|
20417
|
+
const top = gitTop(cwd) ?? cwd;
|
|
20418
|
+
for (const file of [
|
|
20419
|
+
path22.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
20420
|
+
path22.join(top, ".cursor", "cli.json")
|
|
20421
|
+
]) {
|
|
20422
|
+
const text = readText2(file);
|
|
20423
|
+
if (text === null)
|
|
20424
|
+
continue;
|
|
20425
|
+
const value = JSON.parse(text);
|
|
20426
|
+
if (!isRecord4(value))
|
|
20427
|
+
return "unreadable";
|
|
20428
|
+
if (value.permissions === undefined)
|
|
20429
|
+
continue;
|
|
20430
|
+
if (!isRecord4(value.permissions))
|
|
20431
|
+
return "unreadable";
|
|
20432
|
+
for (const [key, bucket] of [
|
|
20433
|
+
["deny", rules.deny],
|
|
20434
|
+
["allow", rules.allow]
|
|
20435
|
+
]) {
|
|
20436
|
+
const list = value.permissions[key];
|
|
20437
|
+
if (list === undefined)
|
|
20438
|
+
continue;
|
|
20439
|
+
if (!Array.isArray(list))
|
|
20440
|
+
return "unreadable";
|
|
20441
|
+
for (const entry of list) {
|
|
20442
|
+
if (typeof entry !== "string")
|
|
20443
|
+
return "unreadable";
|
|
20444
|
+
const match = /^Shell(?:\((.*)\))?$/su.exec(entry.trim());
|
|
20445
|
+
if (match)
|
|
20446
|
+
bucket.push(match[1] ?? "");
|
|
20447
|
+
}
|
|
20448
|
+
}
|
|
20449
|
+
}
|
|
20450
|
+
} catch {
|
|
20451
|
+
return "unreadable";
|
|
20452
|
+
}
|
|
20453
|
+
return rules;
|
|
20454
|
+
}
|
|
20455
|
+
function names(pattern, words) {
|
|
20456
|
+
const token = firstToken(pattern);
|
|
20457
|
+
if (!token || token === "*" || token.includes("*"))
|
|
20458
|
+
return true;
|
|
20459
|
+
const wanted = baseName(token);
|
|
20460
|
+
return words.some((word) => baseName(word) === wanted);
|
|
20461
|
+
}
|
|
20462
|
+
function wrapAllowed(read, words, command, options = {}) {
|
|
20463
|
+
if (read === "unreadable")
|
|
20464
|
+
return { ok: false, why: "named" };
|
|
20465
|
+
if (read.deny.some((pattern) => names(pattern, words)))
|
|
20466
|
+
return { ok: false, why: "named" };
|
|
20467
|
+
if (!options.bypass && read.allow.some(coversWrapper) && !read.allow.some((pattern) => coversCommand(pattern, command)))
|
|
20468
|
+
return { ok: false, why: "allowlisted" };
|
|
20469
|
+
return { ok: true };
|
|
20470
|
+
}
|
|
20471
|
+
var EMPTY = () => ({ deny: [], allow: [] }), readText2 = (file) => {
|
|
20472
|
+
try {
|
|
20473
|
+
return fs17.readFileSync(file, "utf8");
|
|
20474
|
+
} catch (error) {
|
|
20475
|
+
if (error.code === "ENOENT")
|
|
20476
|
+
return null;
|
|
20477
|
+
throw error;
|
|
20478
|
+
}
|
|
20479
|
+
}, isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), projectDirs = (cwd) => {
|
|
20480
|
+
const top = gitTop(cwd);
|
|
20481
|
+
const dirs = [];
|
|
20482
|
+
for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
|
|
20483
|
+
dirs.push(dir);
|
|
20484
|
+
if (!top || dir === top || path22.dirname(dir) === dir)
|
|
20485
|
+
return dirs;
|
|
20486
|
+
}
|
|
20487
|
+
}, homeOf = (env) => env.HOME || os6.homedir(), CLAUDE_MANAGED, bashPattern = (entry) => {
|
|
20488
|
+
const match = /^Bash(?:\((.*)\))?$/su.exec(entry.trim());
|
|
20489
|
+
return match ? match[1] ?? "" : null;
|
|
20490
|
+
}, OPENCODE_NAMES, opencodePermission = (permission, rules) => {
|
|
20491
|
+
if (permission === undefined)
|
|
20492
|
+
return true;
|
|
20493
|
+
if (typeof permission === "string") {
|
|
20494
|
+
(permission === "allow" ? rules.allow : rules.deny).push("");
|
|
20495
|
+
return ["allow", "ask", "deny"].includes(permission);
|
|
20496
|
+
}
|
|
20497
|
+
if (!isRecord4(permission))
|
|
20498
|
+
return false;
|
|
20499
|
+
for (const key of ["bash", "shell"]) {
|
|
20500
|
+
const value = permission[key];
|
|
20501
|
+
if (value === undefined)
|
|
20502
|
+
continue;
|
|
20503
|
+
if (typeof value === "string") {
|
|
20504
|
+
if (!["allow", "ask", "deny"].includes(value))
|
|
20505
|
+
return false;
|
|
20506
|
+
(value === "allow" ? rules.allow : rules.deny).push("");
|
|
20507
|
+
continue;
|
|
20508
|
+
}
|
|
20509
|
+
if (!isRecord4(value))
|
|
20510
|
+
return false;
|
|
20511
|
+
for (const [pattern, action] of Object.entries(value)) {
|
|
20512
|
+
if (action !== "allow" && action !== "ask" && action !== "deny")
|
|
20513
|
+
return false;
|
|
20514
|
+
(action === "allow" ? rules.allow : rules.deny).push(pattern);
|
|
20515
|
+
}
|
|
20516
|
+
}
|
|
20517
|
+
return true;
|
|
20518
|
+
}, opencodeConfig = (value, rules) => {
|
|
20519
|
+
if (!isRecord4(value))
|
|
20520
|
+
return false;
|
|
20521
|
+
if (!opencodePermission(value.permission, rules))
|
|
20522
|
+
return false;
|
|
20523
|
+
if (value.agent === undefined)
|
|
20524
|
+
return true;
|
|
20525
|
+
if (!isRecord4(value.agent))
|
|
20526
|
+
return false;
|
|
20527
|
+
return Object.values(value.agent).every((agent) => isRecord4(agent) && opencodePermission(agent.permission, rules));
|
|
20528
|
+
}, codexRule = (body) => {
|
|
20529
|
+
const open = /\bpattern\s*=\s*\[/u.exec(body);
|
|
20530
|
+
if (!open)
|
|
20531
|
+
return null;
|
|
20532
|
+
const words = [];
|
|
20533
|
+
let index = open.index + open[0].length;
|
|
20534
|
+
for (;; ) {
|
|
20535
|
+
while (/[\s,]/u.test(body[index] ?? ""))
|
|
20536
|
+
index += 1;
|
|
20537
|
+
if (body[index] === "]")
|
|
20538
|
+
break;
|
|
20539
|
+
if (body[index] !== '"')
|
|
20540
|
+
return null;
|
|
20541
|
+
const literal = /"((?:[^"\\]|\\.)*)"/y;
|
|
20542
|
+
literal.lastIndex = index;
|
|
20543
|
+
const match = literal.exec(body);
|
|
20544
|
+
if (!match)
|
|
20545
|
+
return null;
|
|
20546
|
+
words.push(JSON.parse(`"${match[1]}"`));
|
|
20547
|
+
index = literal.lastIndex;
|
|
20548
|
+
}
|
|
20549
|
+
const decision = /\bdecision\s*=\s*"(\w+)"/u.exec(body)?.[1];
|
|
20550
|
+
if (!words.length || !decision || !["allow", "prompt", "forbidden"].includes(decision))
|
|
20551
|
+
return null;
|
|
20552
|
+
return { pattern: words.join(" "), decision };
|
|
20553
|
+
}, baseName = (word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, ""), firstToken = (pattern) => pattern.trim().split(/[:\s]/u)[0] ?? "", literalPrefix = (pattern) => pattern.trim().replace(/(?::\*| \*|\*)$/u, ""), coversWrapper = (pattern) => {
|
|
20554
|
+
const token = firstToken(pattern);
|
|
20555
|
+
if (!token || token.includes("*"))
|
|
20556
|
+
return true;
|
|
20557
|
+
const words = literalPrefix(pattern).split(/\s+/u);
|
|
20558
|
+
if (baseName(words[0]) !== "workit")
|
|
20559
|
+
return false;
|
|
20560
|
+
return ["check", "--heavy"].slice(0, words.length - 1).every((word, at) => {
|
|
20561
|
+
const given = words[at + 1];
|
|
20562
|
+
return given === word || word.startsWith(given);
|
|
20563
|
+
});
|
|
20564
|
+
}, coversCommand = (pattern, command) => {
|
|
20565
|
+
const prefix = literalPrefix(pattern);
|
|
20566
|
+
if (!prefix)
|
|
20567
|
+
return true;
|
|
20568
|
+
return !prefix.includes("*") && command.trim().startsWith(prefix);
|
|
20569
|
+
};
|
|
20570
|
+
var init_heavy_rules = __esm(() => {
|
|
20571
|
+
CLAUDE_MANAGED = {
|
|
20572
|
+
linux: "/etc/claude-code/managed-settings.json",
|
|
20573
|
+
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
20574
|
+
win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
|
|
20575
|
+
};
|
|
20576
|
+
OPENCODE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
20577
|
+
});
|
|
20578
|
+
|
|
20579
|
+
// packages/workit-core/src/core/heavy.ts
|
|
20580
|
+
import fs18 from "node:fs";
|
|
20581
|
+
import os7 from "node:os";
|
|
20582
|
+
import path23 from "node:path";
|
|
20583
|
+
function stateDir(env, platform = process.platform) {
|
|
20584
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
20585
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
20586
|
+
if (env.XDG_STATE_HOME)
|
|
20587
|
+
return path23.join(env.XDG_STATE_HOME, "workit");
|
|
20588
|
+
const home = env.HOME || os7.homedir();
|
|
20589
|
+
if (platform === "darwin")
|
|
20590
|
+
return path23.join(home, "Library", "Application Support", "workit");
|
|
20591
|
+
if (platform === "win32")
|
|
20592
|
+
return path23.join(env.LOCALAPPDATA ?? path23.join(home, "AppData", "Local"), "workit");
|
|
20593
|
+
return path23.join(home, ".local", "state", "workit");
|
|
20594
|
+
}
|
|
20595
|
+
function positional2(words, from, values = PM_VALUES) {
|
|
20596
|
+
let index = from;
|
|
20597
|
+
while (index < words.length && words[index].startsWith("-")) {
|
|
20598
|
+
if (words[index] === "--")
|
|
20599
|
+
return index + 1;
|
|
20600
|
+
index += values.has(words[index]) ? 2 : 1;
|
|
20601
|
+
}
|
|
20602
|
+
return index;
|
|
20603
|
+
}
|
|
20604
|
+
function builtIn(words, depth = 0) {
|
|
20605
|
+
if (depth > 6 || !words.length)
|
|
20606
|
+
return null;
|
|
20607
|
+
const name = baseName2(words[0]);
|
|
20608
|
+
const label = () => words.slice(0, 3).join(" ");
|
|
20609
|
+
const inner = (from) => builtIn(words.slice(from), depth + 1);
|
|
20610
|
+
if (WORKIT.test(name))
|
|
20611
|
+
return null;
|
|
20612
|
+
if (INFO_FLAGS.has(words[1] ?? "") && words.length === 2)
|
|
20613
|
+
return null;
|
|
20614
|
+
if (words.some((word) => word === "--watch" || word.startsWith("--watch=") || word === "--watchAll"))
|
|
20615
|
+
return null;
|
|
20616
|
+
if (name === "npx" || name === "bunx" || name === "pnpx")
|
|
20617
|
+
return inner(positional2(words, 1));
|
|
20618
|
+
if (name === "corepack")
|
|
20619
|
+
return inner(1);
|
|
20620
|
+
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" || name === "deno") {
|
|
20621
|
+
const at = positional2(words, 1);
|
|
20622
|
+
const command = words[at];
|
|
20623
|
+
if (command === undefined)
|
|
20624
|
+
return name === "yarn" ? "yarn (install)" : null;
|
|
20625
|
+
if (name === "deno") {
|
|
20626
|
+
if (["test", "bench", "check", "compile", "lint"].includes(command))
|
|
20627
|
+
return label();
|
|
20628
|
+
if (command === "task" && HEAVY_SCRIPT.test(words[at + 1] ?? ""))
|
|
20629
|
+
return label();
|
|
20630
|
+
return null;
|
|
20631
|
+
}
|
|
20632
|
+
if (NPM_INSTALL.has(command) || NPM_TEST.has(command))
|
|
20633
|
+
return label();
|
|
20634
|
+
if (name === "bun" && command === "build")
|
|
20635
|
+
return label();
|
|
20636
|
+
if (EXEC.has(command))
|
|
20637
|
+
return inner(positional2(words, at + 1));
|
|
20638
|
+
if (name === "yarn" && command === "workspace")
|
|
20639
|
+
return inner(at + 2);
|
|
20640
|
+
if (name === "yarn" && command === "workspaces")
|
|
20641
|
+
return words.slice(at + 1).some((word) => HEAVY_SCRIPT.test(word)) ? label() : null;
|
|
20642
|
+
if (NPM_RUN.has(command)) {
|
|
20643
|
+
const script = words[positional2(words, at + 1)];
|
|
20644
|
+
if (script === undefined || WATCH_SCRIPT.test(script))
|
|
20645
|
+
return null;
|
|
20646
|
+
if (HEAVY_SCRIPT.test(script))
|
|
20647
|
+
return label();
|
|
20648
|
+
return name === "bun" ? inner(positional2(words, at + 1)) : null;
|
|
20649
|
+
}
|
|
20650
|
+
if (name === "npm")
|
|
20651
|
+
return null;
|
|
20652
|
+
if (WATCH_SCRIPT.test(command))
|
|
20653
|
+
return null;
|
|
20654
|
+
if (HEAVY_SCRIPT.test(command) && !/\.[cm]?[jt]sx?$/u.test(command))
|
|
20655
|
+
return label();
|
|
20656
|
+
return inner(at);
|
|
20657
|
+
}
|
|
20658
|
+
if (PYTHONS.test(name)) {
|
|
20659
|
+
const at = words.indexOf("-m");
|
|
20660
|
+
return at > 0 && words[at + 1] ? inner(at + 1) : null;
|
|
20661
|
+
}
|
|
20662
|
+
if (["uv", "poetry", "pdm", "pipenv", "hatch", "rye"].includes(name)) {
|
|
20663
|
+
const at = positional2(words, 1);
|
|
20664
|
+
const command = words[at];
|
|
20665
|
+
if (command === "run")
|
|
20666
|
+
return inner(positional2(words, at + 1));
|
|
20667
|
+
if (command === "pip")
|
|
20668
|
+
return inner(at);
|
|
20669
|
+
return ["sync", "install", "build", "test", "add"].includes(command ?? "") ? label() : null;
|
|
20670
|
+
}
|
|
20671
|
+
if (name === "bundle") {
|
|
20672
|
+
const command = words[positional2(words, 1)];
|
|
20673
|
+
if (command === "exec")
|
|
20674
|
+
return inner(positional2(words, 2));
|
|
20675
|
+
return command === "install" || command === undefined ? label() : null;
|
|
20676
|
+
}
|
|
20677
|
+
if (name === "make" || name === "gmake") {
|
|
20678
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
20679
|
+
if (targets.some((target) => WATCH_SCRIPT.test(target)))
|
|
20680
|
+
return null;
|
|
20681
|
+
return !targets.length || targets.some((target) => HEAVY_SCRIPT.test(target)) ? label() : null;
|
|
20682
|
+
}
|
|
20683
|
+
if (name === "rake") {
|
|
20684
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
20685
|
+
return targets.some((target) => HEAVY_SCRIPT.test(target) || /^spec(?:$|:)/u.test(target)) ? label() : null;
|
|
20686
|
+
}
|
|
20687
|
+
if (name === "nx") {
|
|
20688
|
+
const at = positional2(words, 1);
|
|
20689
|
+
const command = words[at] ?? "";
|
|
20690
|
+
if (["run-many", "affected"].includes(command))
|
|
20691
|
+
return words.some((word) => /^(?:-t|--targets?)(?:=|$)/u.test(word)) && !words.some((word) => WATCH_SCRIPT.test(word.replace(/^--?targets?=/u, ""))) ? label() : null;
|
|
20692
|
+
if (command === "run") {
|
|
20693
|
+
const target = (words[at + 1] ?? "").split(":")[1] ?? "";
|
|
20694
|
+
return HEAVY_SCRIPT.test(target) && !WATCH_SCRIPT.test(target) ? label() : null;
|
|
20695
|
+
}
|
|
20696
|
+
return HEAVY_SCRIPT.test(command) && !WATCH_SCRIPT.test(command) ? label() : null;
|
|
20697
|
+
}
|
|
20698
|
+
if (name === "docker" || name === "podman" || name === "nerdctl") {
|
|
20699
|
+
const at = positional2(words, 1, new Set(["-H", "--host", "--context", "-c", "--config"]));
|
|
20700
|
+
const command = words[at];
|
|
20701
|
+
if (command === "build")
|
|
20702
|
+
return label();
|
|
20703
|
+
if (command === "buildx" || command === "image" || command === "builder")
|
|
20704
|
+
return words[positional2(words, at + 1)] === "build" ? label() : null;
|
|
20705
|
+
if (command === "compose") {
|
|
20706
|
+
const at2 = positional2(words, at + 1, new Set(["-f", "--file", "-p"]));
|
|
20707
|
+
const action = words[at2];
|
|
20708
|
+
if (action === "build")
|
|
20709
|
+
return label();
|
|
20710
|
+
return action === "up" && words.slice(at2 + 1).includes("--build") ? label() : null;
|
|
20711
|
+
}
|
|
20712
|
+
return null;
|
|
20713
|
+
}
|
|
20714
|
+
const rule = TOOLS[name];
|
|
20715
|
+
if (!rule)
|
|
20716
|
+
return null;
|
|
20717
|
+
if (words.slice(1).some((word) => WATCH_FLAG.test(word) || word === "watch" || word === "dev"))
|
|
20718
|
+
return null;
|
|
20719
|
+
if (rule === "any")
|
|
20720
|
+
return label();
|
|
20721
|
+
const command = words.slice(1).find((word) => !word.startsWith("-") && !word.startsWith("+") || rule.has(word));
|
|
20722
|
+
return command !== undefined && rule.has(command) ? label() : null;
|
|
20723
|
+
}
|
|
20724
|
+
function parsePattern(pattern) {
|
|
20725
|
+
const words = pattern.trim().split(/\s+/u).filter(Boolean);
|
|
20726
|
+
return words.length ? words.map(globWord) : null;
|
|
20727
|
+
}
|
|
20728
|
+
function machinePatterns(env) {
|
|
20729
|
+
const value = readJson(heavyCommandsFile(env));
|
|
20730
|
+
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
20731
|
+
}
|
|
20732
|
+
function repoPatterns(cwd) {
|
|
20733
|
+
return repoHeavyCommands(gitTop(cwd) ?? path23.resolve(cwd));
|
|
20734
|
+
}
|
|
20735
|
+
function heavyLines(lines, options) {
|
|
20736
|
+
const found = [];
|
|
20737
|
+
let patterns = null;
|
|
20738
|
+
for (const line of lines) {
|
|
20739
|
+
const reason = builtIn(line);
|
|
20740
|
+
if (reason) {
|
|
20741
|
+
found.push({ line, source: "catalog", reason });
|
|
20742
|
+
continue;
|
|
20743
|
+
}
|
|
20744
|
+
patterns ??= (options.patterns ?? [
|
|
20745
|
+
...machinePatterns(options.env ?? process.env).map((pattern) => ({
|
|
20746
|
+
pattern,
|
|
20747
|
+
from: "machine"
|
|
20748
|
+
})),
|
|
20749
|
+
...repoPatterns(options.cwd).map((pattern) => ({ pattern, from: "repo" }))
|
|
20750
|
+
]).flatMap((entry) => {
|
|
20751
|
+
const matchers = parsePattern(entry.pattern);
|
|
20752
|
+
return matchers ? [{ ...entry, matchers }] : [];
|
|
20753
|
+
});
|
|
20754
|
+
const hit = patterns.find((entry) => matches(entry.matchers, line));
|
|
20755
|
+
if (hit)
|
|
20756
|
+
found.push({
|
|
20757
|
+
line,
|
|
20758
|
+
source: { pattern: hit.pattern, from: hit.from },
|
|
20759
|
+
reason: `${hit.from} pattern "${hit.pattern}"`
|
|
20760
|
+
});
|
|
20761
|
+
}
|
|
20762
|
+
return found;
|
|
20763
|
+
}
|
|
20764
|
+
function heavyPlan(command, options) {
|
|
20765
|
+
const dialect = options.dialect ?? "posix";
|
|
20766
|
+
if (dialect !== "posix" || !gitTop(options.cwd))
|
|
20767
|
+
return { kind: "none" };
|
|
20768
|
+
const lines = linesOf(command, dialect);
|
|
20769
|
+
if (!lines)
|
|
20770
|
+
return { kind: "none" };
|
|
20771
|
+
const heavy = heavyLines(lines, options);
|
|
20772
|
+
if (!heavy.length)
|
|
20773
|
+
return { kind: "none" };
|
|
20774
|
+
const simple = simpleCommand(command, dialect);
|
|
20775
|
+
const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
|
|
20776
|
+
const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
|
|
20777
|
+
words: match.line,
|
|
20778
|
+
text: match.line.join(" ")
|
|
20779
|
+
}));
|
|
20780
|
+
if (options.rules !== null)
|
|
20781
|
+
for (const { words, text } of judged) {
|
|
20782
|
+
const verdict = wrapAllowed(options.rules, words, text, { bypass: options.bypass });
|
|
20783
|
+
if (!verdict.ok)
|
|
20784
|
+
return {
|
|
20785
|
+
kind: "passthrough",
|
|
20786
|
+
note: `workit: \`${command.trim()}\` is heavy but runs outside the heavy-check lease because a host permission rule names it (or \`workit\` is allowlisted); the host decides.`
|
|
20787
|
+
};
|
|
20788
|
+
}
|
|
20789
|
+
if (single) {
|
|
20790
|
+
const rewrite = leaseRewrite(command);
|
|
20791
|
+
return rewrite ? { kind: "rewrite", command: rewrite, reason: single.reason } : { kind: "none" };
|
|
20792
|
+
}
|
|
20793
|
+
const parts = heavy.map((match) => leaseCommand(match.line));
|
|
20794
|
+
return {
|
|
20795
|
+
kind: "split",
|
|
20796
|
+
reason: `heavy_command: \`${command.trim()}\` chains heavy work (${heavy.map((match) => match.reason).join("; ")}) with other commands, and a chain cannot run under the machine-wide heavy-check lease. Run each heavy part alone as \`workit check --heavy -- <segment>\` (here: ${parts.map((part) => `\`${part}\``).join("; ")}); run the other parts as their own commands.`,
|
|
20797
|
+
unblock: parts[0] ?? null
|
|
20798
|
+
};
|
|
20799
|
+
}
|
|
20800
|
+
function workitOnPath2(env = process.env) {
|
|
20801
|
+
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
20802
|
+
for (const dir of (env.PATH ?? "").split(path23.delimiter))
|
|
20803
|
+
for (const name of names) {
|
|
20804
|
+
if (!dir)
|
|
20805
|
+
continue;
|
|
20806
|
+
try {
|
|
20807
|
+
fs18.accessSync(path23.join(dir, name), fs18.constants.X_OK);
|
|
20808
|
+
return true;
|
|
20809
|
+
} catch {}
|
|
20810
|
+
}
|
|
20811
|
+
return false;
|
|
20812
|
+
}
|
|
20813
|
+
function leaseRewrite(command) {
|
|
20814
|
+
const text = command.trim();
|
|
20815
|
+
const prefix = ASSIGNMENTS.exec(text)?.[1] ?? "";
|
|
20816
|
+
const rest = text.slice(prefix.length).trim();
|
|
20817
|
+
if (!rest)
|
|
20818
|
+
return null;
|
|
20819
|
+
return `${prefix}workit check --heavy -- ${rest}`;
|
|
20820
|
+
}
|
|
20821
|
+
var HEAVY_COMMANDS_FILE = "heavy-commands.json", heavyCommandsFile = (env) => path23.join(stateDir(env), HEAVY_COMMANDS_FILE), HEAVY_SCRIPT, INFO_FLAGS, WATCH_FLAG, WATCH_SCRIPT, sub = (...names) => new Set(names), TOOLS, PM_VALUES, baseName2 = (word) => (word?.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/u, ""), WORKIT, PYTHONS, NPM_INSTALL, NPM_RUN, NPM_TEST, EXEC, globWord = (word) => new RegExp(`^${word.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*")}$`, "u"), matches = (matchers, words) => matchers.length <= words.length && matchers.every((matcher, index) => index === 0 ? matcher.test(words[0]) || matcher.test(baseName2(words[0])) : matcher.test(words[index])), readJson = (file) => {
|
|
20822
|
+
try {
|
|
20823
|
+
return JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
20824
|
+
} catch {
|
|
20825
|
+
return;
|
|
20826
|
+
}
|
|
20827
|
+
}, isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), stringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [], linesOf = (command, dialect) => {
|
|
20828
|
+
try {
|
|
20829
|
+
return commandLines(command, dialect);
|
|
20830
|
+
} catch {
|
|
20831
|
+
return null;
|
|
20832
|
+
}
|
|
20833
|
+
}, quoteWord = (word) => /^[\w@%+=:,./-]+$/u.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`, leaseCommand = (words) => `workit check --heavy -- ${words.map(quoteWord).join(" ")}`, ASSIGNMENTS;
|
|
20834
|
+
var init_heavy = __esm(() => {
|
|
20835
|
+
init_raw_git();
|
|
20836
|
+
init_check_config();
|
|
20837
|
+
init_heavy_rules();
|
|
20838
|
+
HEAVY_SCRIPT = /^(?:test|tests|t|tst|unit|integration|e2e|coverage|cov|lint|eslint|typecheck|type-check|types|tsc|check|verify|validate|build|compile|bundle|ci|sonar|cypress|playwright|jest|vitest|mocha|bench|benchmark|all)(?:$|[:_.-])/iu;
|
|
20839
|
+
INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
20840
|
+
WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
20841
|
+
WATCH_SCRIPT = /(?:^|[:_.-])(?:watch|dev|serve|start|storybook)(?:$|[:_.-])/iu;
|
|
20842
|
+
TOOLS = {
|
|
20843
|
+
jest: "any",
|
|
20844
|
+
vitest: "any",
|
|
20845
|
+
mocha: "any",
|
|
20846
|
+
ava: "any",
|
|
20847
|
+
tap: "any",
|
|
20848
|
+
karma: "any",
|
|
20849
|
+
jasmine: "any",
|
|
20850
|
+
nyc: "any",
|
|
20851
|
+
c8: "any",
|
|
20852
|
+
tsc: "any",
|
|
20853
|
+
"vue-tsc": "any",
|
|
20854
|
+
"svelte-check": "any",
|
|
20855
|
+
eslint: "any",
|
|
20856
|
+
stylelint: "any",
|
|
20857
|
+
tslint: "any",
|
|
20858
|
+
webpack: "any",
|
|
20859
|
+
"webpack-cli": "any",
|
|
20860
|
+
rollup: "any",
|
|
20861
|
+
parcel: "any",
|
|
20862
|
+
tsup: "any",
|
|
20863
|
+
vite: sub("build", "optimize"),
|
|
20864
|
+
next: sub("build", "lint"),
|
|
20865
|
+
nuxt: sub("build", "generate"),
|
|
20866
|
+
nuxi: sub("build", "generate"),
|
|
20867
|
+
astro: sub("build", "check"),
|
|
20868
|
+
ng: sub("build", "b", "test", "t", "e2e", "lint"),
|
|
20869
|
+
"react-scripts": sub("build", "test"),
|
|
20870
|
+
playwright: sub("test", "install"),
|
|
20871
|
+
cypress: sub("run"),
|
|
20872
|
+
wdio: "any",
|
|
20873
|
+
testcafe: "any",
|
|
20874
|
+
turbo: "any",
|
|
20875
|
+
lerna: sub("run", "exec", "bootstrap"),
|
|
20876
|
+
"sonar-scanner": "any",
|
|
20877
|
+
sonarless: sub("scan"),
|
|
20878
|
+
"sonarless-mcp": sub("scan"),
|
|
20879
|
+
go: sub("test", "build", "vet", "install", "generate"),
|
|
20880
|
+
"golangci-lint": sub("run"),
|
|
20881
|
+
gotestsum: "any",
|
|
20882
|
+
staticcheck: "any",
|
|
20883
|
+
cargo: sub("build", "b", "test", "t", "check", "c", "clippy", "bench", "nextest", "doc", "install", "tarpaulin", "llvm-cov"),
|
|
20884
|
+
mvn: "any",
|
|
20885
|
+
mvnw: "any",
|
|
20886
|
+
gradle: "any",
|
|
20887
|
+
gradlew: "any",
|
|
20888
|
+
ant: "any",
|
|
20889
|
+
sbt: "any",
|
|
20890
|
+
bazel: sub("build", "test", "run", "coverage"),
|
|
20891
|
+
bazelisk: sub("build", "test", "run", "coverage"),
|
|
20892
|
+
buck2: sub("build", "test", "run"),
|
|
20893
|
+
dotnet: sub("build", "test", "publish", "restore", "pack", "msbuild"),
|
|
20894
|
+
msbuild: "any",
|
|
20895
|
+
pytest: "any",
|
|
20896
|
+
"py.test": "any",
|
|
20897
|
+
tox: "any",
|
|
20898
|
+
nox: "any",
|
|
20899
|
+
mypy: "any",
|
|
20900
|
+
pyright: "any",
|
|
20901
|
+
pylint: "any",
|
|
20902
|
+
coverage: sub("run"),
|
|
20903
|
+
maturin: sub("build", "develop"),
|
|
20904
|
+
pip: sub("install", "wheel"),
|
|
20905
|
+
pip3: sub("install", "wheel"),
|
|
20906
|
+
rspec: "any",
|
|
20907
|
+
rails: sub("test", "assets:precompile"),
|
|
20908
|
+
phpunit: "any",
|
|
20909
|
+
pest: "any",
|
|
20910
|
+
phpstan: "any",
|
|
20911
|
+
psalm: "any",
|
|
20912
|
+
composer: sub("install", "update", "require", "test"),
|
|
20913
|
+
mix: sub("test", "compile", "deps.get", "dialyzer", "credo"),
|
|
20914
|
+
swift: sub("build", "test"),
|
|
20915
|
+
xcodebuild: "any",
|
|
20916
|
+
flutter: sub("build", "test"),
|
|
20917
|
+
dart: sub("test", "compile"),
|
|
20918
|
+
pod: sub("install"),
|
|
20919
|
+
cmake: sub("--build"),
|
|
20920
|
+
ninja: "any",
|
|
20921
|
+
meson: sub("compile", "test"),
|
|
20922
|
+
ctest: "any",
|
|
20923
|
+
scons: "any",
|
|
20924
|
+
"docker-compose": sub("build"),
|
|
20925
|
+
buildah: sub("bud", "build"),
|
|
20926
|
+
earthly: "any"
|
|
20927
|
+
};
|
|
20928
|
+
PM_VALUES = new Set([
|
|
20929
|
+
"--prefix",
|
|
20930
|
+
"-w",
|
|
20931
|
+
"--workspace",
|
|
20932
|
+
"-C",
|
|
20933
|
+
"--dir",
|
|
20934
|
+
"--filter",
|
|
20935
|
+
"-F",
|
|
20936
|
+
"--cwd",
|
|
20937
|
+
"-p",
|
|
20938
|
+
"--package",
|
|
20939
|
+
"--cache",
|
|
20940
|
+
"--registry"
|
|
20941
|
+
]);
|
|
20942
|
+
WORKIT = /^(?:workit|@brainervirus\/workit-cli(?:@.*)?)$/u;
|
|
20943
|
+
PYTHONS = /^(?:python[\d.]*|py)$/u;
|
|
20944
|
+
NPM_INSTALL = new Set([
|
|
20945
|
+
"ci",
|
|
20946
|
+
"clean-install",
|
|
20947
|
+
"cit",
|
|
20948
|
+
"install-test",
|
|
20949
|
+
"it",
|
|
20950
|
+
"install",
|
|
20951
|
+
"i",
|
|
20952
|
+
"in",
|
|
20953
|
+
"add",
|
|
20954
|
+
"rebuild",
|
|
20955
|
+
"update",
|
|
20956
|
+
"up"
|
|
20957
|
+
]);
|
|
20958
|
+
NPM_RUN = new Set(["run", "run-script", "rum", "urn"]);
|
|
20959
|
+
NPM_TEST = new Set(["test", "t", "tst"]);
|
|
20960
|
+
EXEC = new Set(["exec", "x", "dlx"]);
|
|
20961
|
+
ASSIGNMENTS = /^\s*((?:[A-Za-z_]\w*=(?:'[^']*'|"(?:[^"\\]|\\.)*"|[^\s'"])*\s+)*)/u;
|
|
20962
|
+
});
|
|
20963
|
+
|
|
20964
|
+
// packages/workit-core/src/core/heavy-words.ts
|
|
20965
|
+
import fs19 from "node:fs";
|
|
20966
|
+
import os8 from "node:os";
|
|
20967
|
+
import path24 from "node:path";
|
|
20968
|
+
function maybeHeavy(command, cwd, env = process.env) {
|
|
20969
|
+
try {
|
|
20970
|
+
const words = wordsOf(command);
|
|
20971
|
+
if (words.some((word) => HEAVY_WORDS.has(word) || /^python[\d.]*$/u.test(word)))
|
|
20972
|
+
return true;
|
|
20973
|
+
const extra = patternWords(cwd, env);
|
|
20974
|
+
return extra === null || words.some((word) => extra.has(word));
|
|
20975
|
+
} catch {
|
|
20976
|
+
return true;
|
|
20977
|
+
}
|
|
20978
|
+
}
|
|
20979
|
+
var HEAVY_WORDS, wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean), stateDir2 = (env) => {
|
|
20980
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
20981
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
20982
|
+
if (env.XDG_STATE_HOME)
|
|
20983
|
+
return path24.join(env.XDG_STATE_HOME, "workit");
|
|
20984
|
+
const home = env.HOME || os8.homedir();
|
|
20985
|
+
if (process.platform === "darwin")
|
|
20986
|
+
return path24.join(home, "Library", "Application Support", "workit");
|
|
20987
|
+
if (process.platform === "win32")
|
|
20988
|
+
return path24.join(env.LOCALAPPDATA ?? path24.join(home, "AppData", "Local"), "workit");
|
|
20989
|
+
return path24.join(home, ".local", "state", "workit");
|
|
20990
|
+
}, readJson2 = (file) => {
|
|
20991
|
+
try {
|
|
20992
|
+
return JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
20993
|
+
} catch {
|
|
20994
|
+
return;
|
|
20995
|
+
}
|
|
20996
|
+
}, patternWords = (cwd, env) => {
|
|
20997
|
+
const patterns = [];
|
|
20998
|
+
const machine = readJson2(path24.join(stateDir2(env), "heavy-commands.json"));
|
|
20999
|
+
if (Array.isArray(machine?.patterns))
|
|
21000
|
+
patterns.push(...machine.patterns);
|
|
21001
|
+
for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
|
|
21002
|
+
if (fs19.existsSync(path24.join(dir, ".git"))) {
|
|
21003
|
+
patterns.push(...repoHeavyCommands(dir));
|
|
21004
|
+
break;
|
|
21005
|
+
}
|
|
21006
|
+
if (path24.dirname(dir) === dir)
|
|
21007
|
+
break;
|
|
21008
|
+
}
|
|
21009
|
+
const words = new Set;
|
|
21010
|
+
for (const pattern of patterns) {
|
|
21011
|
+
if (typeof pattern !== "string")
|
|
21012
|
+
continue;
|
|
21013
|
+
const first = wordsOf(pattern)[0];
|
|
21014
|
+
if (!first || first.includes("*"))
|
|
21015
|
+
return null;
|
|
21016
|
+
words.add(first);
|
|
21017
|
+
}
|
|
21018
|
+
return words;
|
|
21019
|
+
};
|
|
21020
|
+
var init_heavy_words = __esm(() => {
|
|
21021
|
+
init_check_config();
|
|
21022
|
+
HEAVY_WORDS = new Set(("npm npx pnpm pnpx yarn bun bunx deno corepack make gmake docker podman nerdctl " + "python python3 py uv poetry pdm pipenv hatch rye bundle rake nx " + "jest vitest mocha ava tap karma jasmine nyc c8 tsc vue-tsc svelte-check eslint stylelint " + "tslint webpack webpack-cli rollup parcel tsup vite next nuxt nuxi astro ng react-scripts " + "playwright cypress wdio testcafe turbo lerna sonar-scanner sonarless sonarless-mcp go " + "golangci-lint gotestsum staticcheck cargo mvn mvnw gradle gradlew ant sbt bazel bazelisk " + "buck2 dotnet msbuild pytest py.test tox nox mypy pyright pylint coverage maturin pip pip3 " + "rspec rails phpunit pest phpstan psalm composer mix swift xcodebuild flutter dart pod cmake " + "ninja meson ctest scons docker-compose buildah earthly").split(" "));
|
|
21023
|
+
});
|
|
21024
|
+
|
|
21025
|
+
// packages/workit-core/src/hooks/heavy-gate.ts
|
|
21026
|
+
function heavyGate(cwd, command, options) {
|
|
21027
|
+
const env = options.env ?? process.env;
|
|
21028
|
+
try {
|
|
21029
|
+
if (env.WORKIT_HEAVY_GATE === "0" || !maybeHeavy(command, cwd, env) || !workitOnPath2(env))
|
|
21030
|
+
return { kind: "none" };
|
|
21031
|
+
return heavyPlan(command, {
|
|
21032
|
+
cwd,
|
|
21033
|
+
env,
|
|
21034
|
+
dialect: options.dialect ?? "posix",
|
|
21035
|
+
rules: rulesFor(options.host, cwd, env),
|
|
21036
|
+
bypass: options.permissionMode === "bypassPermissions"
|
|
21037
|
+
});
|
|
21038
|
+
} catch {
|
|
21039
|
+
return { kind: "none" };
|
|
21040
|
+
}
|
|
21041
|
+
}
|
|
21042
|
+
var rulesFor = (host, cwd, env) => {
|
|
21043
|
+
switch (host) {
|
|
21044
|
+
case "claude_code":
|
|
21045
|
+
return claudeRules(cwd, env);
|
|
21046
|
+
case "opencode":
|
|
21047
|
+
return opencodeRules(cwd, env);
|
|
21048
|
+
case "codex_cli":
|
|
21049
|
+
case "codex_desktop":
|
|
21050
|
+
return codexRules(cwd, env);
|
|
21051
|
+
case "cursor":
|
|
21052
|
+
return cursorRules(cwd, env);
|
|
21053
|
+
case "pi":
|
|
21054
|
+
return null;
|
|
21055
|
+
}
|
|
21056
|
+
}, rewriteNote = (plan) => `workit: this heavy command (${plan.reason}) runs under the machine-wide heavy-check lease as \`${plan.command}\`: it waits while another heavy check holds the lease (\`workit lock status\` shows who). Run heavy commands that way yourself; \`workit help lock\` explains the lease.`, heavyDenial = (original, plan) => ({
|
|
21057
|
+
kind: "deny",
|
|
21058
|
+
reason: `heavy_command: \`${original.trim()}\` is heavy (${plan.reason}) and must run under the machine-wide heavy-check lease, so parallel agents cannot exhaust this machine's memory and CPU. Run exactly: ${plan.command}`,
|
|
21059
|
+
unblock: plan.command
|
|
21060
|
+
}), splitDenial = (plan) => ({
|
|
21061
|
+
kind: "deny",
|
|
21062
|
+
reason: plan.reason,
|
|
21063
|
+
unblock: plan.unblock
|
|
21064
|
+
});
|
|
21065
|
+
var init_heavy_gate = __esm(() => {
|
|
21066
|
+
init_heavy_rules();
|
|
21067
|
+
init_heavy();
|
|
21068
|
+
init_heavy_words();
|
|
21069
|
+
});
|
|
21070
|
+
|
|
21071
|
+
// packages/workit-core/src/core/live-session.ts
|
|
21072
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
21073
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
21074
|
+
import fs20 from "node:fs";
|
|
21075
|
+
import os9 from "node:os";
|
|
21076
|
+
import path25 from "node:path";
|
|
20209
21077
|
var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session, familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, ""), processAlive = (pid) => {
|
|
20210
21078
|
try {
|
|
20211
21079
|
process.kill(pid, 0);
|
|
@@ -20216,9 +21084,9 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
20216
21084
|
}, processInfo = (pid) => {
|
|
20217
21085
|
try {
|
|
20218
21086
|
if (liveProbe.platform === "linux") {
|
|
20219
|
-
const stat =
|
|
21087
|
+
const stat = fs20.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
20220
21088
|
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
20221
|
-
const command =
|
|
21089
|
+
const command = fs20.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
20222
21090
|
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
20223
21091
|
}
|
|
20224
21092
|
if (liveProbe.platform === "win32")
|
|
@@ -20263,32 +21131,32 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
20263
21131
|
pid: () => hostPid(transport)
|
|
20264
21132
|
};
|
|
20265
21133
|
}, checkoutOf = (target) => {
|
|
20266
|
-
let dir =
|
|
20267
|
-
while (!
|
|
20268
|
-
dir =
|
|
20269
|
-
if (
|
|
20270
|
-
dir =
|
|
20271
|
-
const parts = dir.split(
|
|
21134
|
+
let dir = path25.resolve(target);
|
|
21135
|
+
while (!fs20.existsSync(dir) && path25.dirname(dir) !== dir)
|
|
21136
|
+
dir = path25.dirname(dir);
|
|
21137
|
+
if (fs20.existsSync(dir) && !fs20.statSync(dir).isDirectory())
|
|
21138
|
+
dir = path25.dirname(dir);
|
|
21139
|
+
const parts = dir.split(path25.sep);
|
|
20272
21140
|
const dotGit = parts.lastIndexOf(".git");
|
|
20273
21141
|
if (dotGit > 0)
|
|
20274
|
-
dir = parts.slice(0, dotGit).join(
|
|
21142
|
+
dir = parts.slice(0, dotGit).join(path25.sep) || path25.sep;
|
|
20275
21143
|
const location = resolveStore(dir);
|
|
20276
21144
|
if (location instanceof Error || !location.shared || !location.top)
|
|
20277
21145
|
return null;
|
|
20278
21146
|
let top = location.top;
|
|
20279
21147
|
try {
|
|
20280
|
-
top =
|
|
21148
|
+
top = fs20.realpathSync.native(top);
|
|
20281
21149
|
} catch {}
|
|
20282
21150
|
return top;
|
|
20283
|
-
}, markerFile2 = (top) =>
|
|
20284
|
-
let key =
|
|
21151
|
+
}, markerFile2 = (top) => path25.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`), checkoutKey = (top, platform = process.platform) => {
|
|
21152
|
+
let key = path25.resolve(top);
|
|
20285
21153
|
try {
|
|
20286
|
-
key =
|
|
21154
|
+
key = fs20.realpathSync.native(key);
|
|
20287
21155
|
} catch {}
|
|
20288
21156
|
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
20289
21157
|
}, readMarker = (file) => {
|
|
20290
21158
|
try {
|
|
20291
|
-
const value = JSON.parse(
|
|
21159
|
+
const value = JSON.parse(fs20.readFileSync(file, "utf8"));
|
|
20292
21160
|
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
20293
21161
|
return null;
|
|
20294
21162
|
return {
|
|
@@ -20309,7 +21177,7 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
20309
21177
|
const age = ageOf2(marker, deps.now ?? new Date);
|
|
20310
21178
|
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
20311
21179
|
return false;
|
|
20312
|
-
if (marker.machine !== (deps.machine ??
|
|
21180
|
+
if (marker.machine !== (deps.machine ?? os9.hostname()) || marker.pid === null)
|
|
20313
21181
|
return true;
|
|
20314
21182
|
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
20315
21183
|
return false;
|
|
@@ -20335,7 +21203,7 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
20335
21203
|
const marker = readMarker(file);
|
|
20336
21204
|
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
20337
21205
|
return null;
|
|
20338
|
-
if (sameOwner(marker, owner, deps.machine ??
|
|
21206
|
+
if (sameOwner(marker, owner, deps.machine ?? os9.hostname()))
|
|
20339
21207
|
return null;
|
|
20340
21208
|
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
20341
21209
|
} catch {
|
|
@@ -20348,7 +21216,7 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
20348
21216
|
return;
|
|
20349
21217
|
const file = markerFile2(checkout);
|
|
20350
21218
|
const now = deps.now ?? new Date;
|
|
20351
|
-
const machine = deps.machine ??
|
|
21219
|
+
const machine = deps.machine ?? os9.hostname();
|
|
20352
21220
|
const current = readMarker(file);
|
|
20353
21221
|
if (current) {
|
|
20354
21222
|
const age = ageOf2(current, now);
|
|
@@ -20369,10 +21237,10 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
20369
21237
|
checkout,
|
|
20370
21238
|
heartbeatAt: now.toISOString()
|
|
20371
21239
|
};
|
|
20372
|
-
|
|
21240
|
+
fs20.mkdirSync(path25.dirname(file), { recursive: true });
|
|
20373
21241
|
const temp = `${file}.${process.pid}.tmp`;
|
|
20374
|
-
|
|
20375
|
-
|
|
21242
|
+
fs20.writeFileSync(temp, JSON.stringify(marker));
|
|
21243
|
+
fs20.renameSync(temp, file);
|
|
20376
21244
|
} catch {}
|
|
20377
21245
|
};
|
|
20378
21246
|
var init_live_session = __esm(() => {
|
|
@@ -20391,8 +21259,8 @@ var init_live_session = __esm(() => {
|
|
|
20391
21259
|
|
|
20392
21260
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
20393
21261
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
20394
|
-
import
|
|
20395
|
-
import
|
|
21262
|
+
import os10 from "node:os";
|
|
21263
|
+
import path26 from "node:path";
|
|
20396
21264
|
function writeGate(cwd, paths, owner, deps) {
|
|
20397
21265
|
if (!owner)
|
|
20398
21266
|
return beforeWriteGate(cwd, paths);
|
|
@@ -20412,7 +21280,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
20412
21280
|
try {
|
|
20413
21281
|
targets = [
|
|
20414
21282
|
...segmentTargets(cwd, command, dialect),
|
|
20415
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
21283
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path26.join(dir, DIR_TARGET))
|
|
20416
21284
|
];
|
|
20417
21285
|
} catch {
|
|
20418
21286
|
targets = [];
|
|
@@ -20450,24 +21318,37 @@ function beforeWriteGate(cwd, paths) {
|
|
|
20450
21318
|
}
|
|
20451
21319
|
var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
20452
21320
|
let rest = "";
|
|
20453
|
-
for (let dir =
|
|
21321
|
+
for (let dir = path26.resolve(file);; dir = path26.dirname(dir)) {
|
|
20454
21322
|
try {
|
|
20455
|
-
return
|
|
21323
|
+
return path26.join(realpathSync5(dir), rest);
|
|
20456
21324
|
} catch {
|
|
20457
|
-
if (
|
|
20458
|
-
return
|
|
20459
|
-
rest = rest ?
|
|
21325
|
+
if (path26.dirname(dir) === dir)
|
|
21326
|
+
return path26.resolve(file);
|
|
21327
|
+
rest = rest ? path26.join(path26.basename(dir), rest) : path26.basename(dir);
|
|
20460
21328
|
}
|
|
20461
21329
|
}
|
|
20462
21330
|
}, exempt = (cwd, root, file, plans) => {
|
|
20463
21331
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
20464
|
-
const relative =
|
|
20465
|
-
if (relative.startsWith("..") ||
|
|
21332
|
+
const relative = path26.relative(fold(canonical2(root)), fold(canonical2(path26.resolve(cwd, file))));
|
|
21333
|
+
if (relative.startsWith("..") || path26.isAbsolute(relative))
|
|
20466
21334
|
return true;
|
|
20467
|
-
const posix = relative.split(
|
|
21335
|
+
const posix = relative.split(path26.sep).join("/");
|
|
20468
21336
|
const segments = posix.split("/");
|
|
20469
21337
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
20470
|
-
}, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")),
|
|
21338
|
+
}, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), SCRIPT_SHELLS, wrappedScript = (input) => {
|
|
21339
|
+
let words = input;
|
|
21340
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
21341
|
+
words = words.slice(1);
|
|
21342
|
+
const tail = workitCheckTail(words);
|
|
21343
|
+
if (!tail)
|
|
21344
|
+
return null;
|
|
21345
|
+
if ("script" in tail)
|
|
21346
|
+
return tail.script;
|
|
21347
|
+
const [shell, flag, script] = tail.argv;
|
|
21348
|
+
if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
|
|
21349
|
+
return script ?? null;
|
|
21350
|
+
return tail.argv.map(quoteWord2).join(" ");
|
|
21351
|
+
}, editsOf = (input) => {
|
|
20471
21352
|
let words = input;
|
|
20472
21353
|
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
20473
21354
|
words = words.slice(1);
|
|
@@ -20542,10 +21423,18 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20542
21423
|
default:
|
|
20543
21424
|
return [];
|
|
20544
21425
|
}
|
|
20545
|
-
}, shellWrites = (command) => {
|
|
21426
|
+
}, shellWrites = (command, depth = 0) => {
|
|
20546
21427
|
const targets = [];
|
|
20547
21428
|
let unknown = false;
|
|
20548
21429
|
for (const segment of segmentsOf(command)) {
|
|
21430
|
+
const inner = depth < 4 ? wrappedScript(segment.words) : null;
|
|
21431
|
+
if (inner !== null) {
|
|
21432
|
+
const nested = shellWrites(inner, depth + 1);
|
|
21433
|
+
if (nested.writes && nested.targets === null)
|
|
21434
|
+
unknown = true;
|
|
21435
|
+
else if (nested.targets)
|
|
21436
|
+
targets.push(...nested.targets);
|
|
21437
|
+
}
|
|
20549
21438
|
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
20550
21439
|
const edits = editsOf(segment.words);
|
|
20551
21440
|
if (edits === null)
|
|
@@ -20556,7 +21445,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20556
21445
|
if (!unknown && targets.length === 0)
|
|
20557
21446
|
return { writes: false, targets: null };
|
|
20558
21447
|
return { writes: true, targets: unknown ? null : targets };
|
|
20559
|
-
}, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
21448
|
+
}, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path26.resolve(root, file)) ?? "-").join("\x00"), gateFor = (cwd) => {
|
|
20560
21449
|
const store = new TaskStore(cwd);
|
|
20561
21450
|
const listed = store.listTaskIndex();
|
|
20562
21451
|
if (!listed.ok)
|
|
@@ -20609,19 +21498,19 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20609
21498
|
return [];
|
|
20610
21499
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
20611
21500
|
return [];
|
|
20612
|
-
return [
|
|
21501
|
+
return [path26.resolve(cwd, invocation.dir ?? ".")];
|
|
20613
21502
|
});
|
|
20614
21503
|
}, CD2, PUSHD2, POPD2, cdInto = (dir, words) => {
|
|
20615
21504
|
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
20616
21505
|
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
20617
21506
|
return;
|
|
20618
|
-
return target === undefined ?
|
|
20619
|
-
},
|
|
21507
|
+
return target === undefined ? os10.homedir() : path26.resolve(dir, target);
|
|
21508
|
+
}, quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`, segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
20620
21509
|
const targets = [];
|
|
20621
21510
|
let dir = cwd;
|
|
20622
21511
|
let stack = [];
|
|
20623
21512
|
const groups = [];
|
|
20624
|
-
const at = (file) =>
|
|
21513
|
+
const at = (file) => path26.isAbsolute(file) ? file : dir === undefined ? null : path26.resolve(dir, file);
|
|
20625
21514
|
for (const segment of segmentsOf(command, dialect)) {
|
|
20626
21515
|
if (segment.group === "open") {
|
|
20627
21516
|
groups.push({ dir, stack: [...stack] });
|
|
@@ -20649,6 +21538,9 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20649
21538
|
dir = stack.length ? stack.pop() : undefined;
|
|
20650
21539
|
continue;
|
|
20651
21540
|
}
|
|
21541
|
+
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
21542
|
+
if (inner !== null && dir !== undefined)
|
|
21543
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
20652
21544
|
for (const file of segment.redirects)
|
|
20653
21545
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
20654
21546
|
const resolved = at(file);
|
|
@@ -20658,15 +21550,15 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20658
21550
|
const edits = editsOf(segment.words);
|
|
20659
21551
|
if (edits === null) {
|
|
20660
21552
|
if (dir !== undefined)
|
|
20661
|
-
targets.push(
|
|
21553
|
+
targets.push(path26.join(dir, DIR_TARGET));
|
|
20662
21554
|
} else
|
|
20663
21555
|
for (const file of edits) {
|
|
20664
21556
|
const resolved = at(file);
|
|
20665
21557
|
if (resolved)
|
|
20666
21558
|
targets.push(resolved);
|
|
20667
21559
|
}
|
|
20668
|
-
if (dir !== undefined && workitInvocations(segment.words.map(
|
|
20669
|
-
targets.push(
|
|
21560
|
+
if (dir !== undefined && workitInvocations(segment.words.map(quoteWord2).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
|
|
21561
|
+
targets.push(path26.join(dir, DIR_TARGET));
|
|
20670
21562
|
}
|
|
20671
21563
|
return targets;
|
|
20672
21564
|
}, overridden = (command, dialect) => {
|
|
@@ -20689,7 +21581,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20689
21581
|
}, guarded = (targets) => {
|
|
20690
21582
|
const seen = new Set;
|
|
20691
21583
|
return targets.filter((target) => {
|
|
20692
|
-
const dir =
|
|
21584
|
+
const dir = path26.dirname(target);
|
|
20693
21585
|
if (seen.has(dir))
|
|
20694
21586
|
return false;
|
|
20695
21587
|
seen.add(dir);
|
|
@@ -20707,7 +21599,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20707
21599
|
}, claimAll = (owner, targets, deps) => {
|
|
20708
21600
|
for (const target of guarded(targets))
|
|
20709
21601
|
claimLiveSession(target, owner, deps);
|
|
20710
|
-
}, liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
21602
|
+
}, liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path26.resolve(cwd, file)) : [path26.join(cwd, DIR_TARGET)], noteOverride = (cwd, owner, command) => {
|
|
20711
21603
|
try {
|
|
20712
21604
|
if (!inWorkitWorkspace(cwd))
|
|
20713
21605
|
return;
|
|
@@ -20731,6 +21623,7 @@ var init_write_gate = __esm(() => {
|
|
|
20731
21623
|
NONE3 = { kind: "none" };
|
|
20732
21624
|
CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
20733
21625
|
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
21626
|
+
SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
20734
21627
|
cache2 = new Map;
|
|
20735
21628
|
MUTATING_GIT = new Set([
|
|
20736
21629
|
"add",
|
|
@@ -21143,9 +22036,9 @@ var init_skill_nudge = __esm(() => {
|
|
|
21143
22036
|
|
|
21144
22037
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
21145
22038
|
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
21146
|
-
import
|
|
21147
|
-
var
|
|
21148
|
-
if (!nonEmpty2(value) || !
|
|
22039
|
+
import path27 from "node:path";
|
|
22040
|
+
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) => {
|
|
22041
|
+
if (!nonEmpty2(value) || !path27.isAbsolute(value) || !existsSync7(value))
|
|
21149
22042
|
return null;
|
|
21150
22043
|
try {
|
|
21151
22044
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -21171,7 +22064,7 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
|
|
|
21171
22064
|
const joined = argv.map(quoteArg).join(" ");
|
|
21172
22065
|
return nonEmpty2(joined) ? joined : null;
|
|
21173
22066
|
}, WRITE_TOOLS, isWriteTool = (name) => typeof name === "string" && WRITE_TOOLS.has(name.toLowerCase().replace(/[_-]/g, "")), PATH_KEYS, PATCH_HEADER, writePaths = (input) => {
|
|
21174
|
-
if (!
|
|
22067
|
+
if (!isRecord6(input))
|
|
21175
22068
|
return [];
|
|
21176
22069
|
const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
|
|
21177
22070
|
for (const value of Object.values(input))
|
|
@@ -21253,10 +22146,32 @@ function handleHook(input, deps) {
|
|
|
21253
22146
|
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
|
|
21254
22147
|
if (gate.kind === "deny")
|
|
21255
22148
|
return gate;
|
|
22149
|
+
const plan = heavyGate(input.cwd, event.command, {
|
|
22150
|
+
host: input.host,
|
|
22151
|
+
dialect: event.dialect,
|
|
22152
|
+
permissionMode: input.permissionMode
|
|
22153
|
+
});
|
|
22154
|
+
if (plan.kind === "split" && canDeny)
|
|
22155
|
+
return splitDenial(plan);
|
|
22156
|
+
const rewrites = usable2(descriptor.shellPolicy.rewrite);
|
|
22157
|
+
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
22158
|
+
return heavyDenial(event.command, plan);
|
|
22159
|
+
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
22160
|
+
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
21256
22161
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
22162
|
+
const rewritten = (text) => lease ? {
|
|
22163
|
+
kind: "rewrite",
|
|
22164
|
+
command: lease.command,
|
|
22165
|
+
input: event.toolInput ? { ...event.toolInput, command: lease.command } : null,
|
|
22166
|
+
text
|
|
22167
|
+
} : NONE5;
|
|
21257
22168
|
if (descriptor.transport !== "hook-process")
|
|
21258
|
-
return
|
|
21259
|
-
|
|
22169
|
+
return rewritten(null);
|
|
22170
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
|
|
22171
|
+
if (!lease)
|
|
22172
|
+
return nudged;
|
|
22173
|
+
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
22174
|
+
`));
|
|
21260
22175
|
}
|
|
21261
22176
|
case "shell.post":
|
|
21262
22177
|
if (usable2(descriptor.events["shell.post"].support))
|
|
@@ -21342,6 +22257,7 @@ var init_handle = __esm(() => {
|
|
|
21342
22257
|
init_branch();
|
|
21343
22258
|
init_workspaces();
|
|
21344
22259
|
init_host_session();
|
|
22260
|
+
init_heavy_gate();
|
|
21345
22261
|
init_policy();
|
|
21346
22262
|
init_raw_git();
|
|
21347
22263
|
init_skill_nudge();
|
|
@@ -21358,8 +22274,104 @@ var init_handle = __esm(() => {
|
|
|
21358
22274
|
CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
21359
22275
|
});
|
|
21360
22276
|
|
|
22277
|
+
// packages/workit-core/src/hooks/deadline.ts
|
|
22278
|
+
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
22279
|
+
import path28 from "node:path";
|
|
22280
|
+
var HOOK_SKIP_TEXT = "workit context skipped: host under load", HOOK_DEADLINE_MS = 8000, HOST_DEADLINES, HOOK_SKIPS_FILE = "hook-skips.jsonl", override = (env) => {
|
|
22281
|
+
const value = Number(env.WORKIT_HOOK_DEADLINE_MS);
|
|
22282
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
22283
|
+
}, hookDeadlineMs = (env, host = "", native) => {
|
|
22284
|
+
const row = HOST_DEADLINES[host];
|
|
22285
|
+
return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
|
|
22286
|
+
}, skipsFile = (stateDir) => path28.join(stateDir, HOOK_SKIPS_FILE), recordHookSkip = (skip, stateDir = resolveStateDir()) => {
|
|
22287
|
+
try {
|
|
22288
|
+
mkdirSync3(stateDir, { recursive: true });
|
|
22289
|
+
appendFileSync(skipsFile(stateDir), `${JSON.stringify(skip)}
|
|
22290
|
+
`);
|
|
22291
|
+
} catch {}
|
|
22292
|
+
}, QUIET, skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT }, TIMED_OUT, within = async (work, ms) => {
|
|
22293
|
+
let timer;
|
|
22294
|
+
const expired = new Promise((resolve) => {
|
|
22295
|
+
timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
|
|
22296
|
+
timer.unref?.();
|
|
22297
|
+
});
|
|
22298
|
+
try {
|
|
22299
|
+
return await Promise.race([work, expired]);
|
|
22300
|
+
} finally {
|
|
22301
|
+
clearTimeout(timer);
|
|
22302
|
+
}
|
|
22303
|
+
}, isTimedOut = (value) => value === TIMED_OUT, PROCESS_START, hookBudget = (env, options = {}, host = "") => {
|
|
22304
|
+
const now = options.now ?? Date.now;
|
|
22305
|
+
return {
|
|
22306
|
+
now,
|
|
22307
|
+
record: options.record ?? ((skip) => recordHookSkip(skip)),
|
|
22308
|
+
startedAt: options.startedAt ?? now(),
|
|
22309
|
+
deadlineMs: options.deadlineMs ?? hookDeadlineMs(env, host),
|
|
22310
|
+
host: options.deadlineMs === undefined ? host : null
|
|
22311
|
+
};
|
|
22312
|
+
}, readWithin = async (stdin, budget) => {
|
|
22313
|
+
const read = (async () => {
|
|
22314
|
+
let text = "";
|
|
22315
|
+
for await (const chunk of stdin)
|
|
22316
|
+
text += String(chunk);
|
|
22317
|
+
return text;
|
|
22318
|
+
})();
|
|
22319
|
+
const text = await within(read, budget.startedAt + budget.deadlineMs - budget.now());
|
|
22320
|
+
return isTimedOut(text) ? null : text;
|
|
22321
|
+
}, note = (adapter, raw, env, budget, outcome, stage) => {
|
|
22322
|
+
const parsed = raw === undefined ? null : adapter.parse(raw, env);
|
|
22323
|
+
const event = parsed ? parsed.ok ? parsed.input.event.kind : parsed.event : null;
|
|
22324
|
+
budget.record({
|
|
22325
|
+
at: new Date(budget.now()).toISOString(),
|
|
22326
|
+
host: adapter.descriptor.host,
|
|
22327
|
+
event,
|
|
22328
|
+
outcome,
|
|
22329
|
+
stage,
|
|
22330
|
+
elapsedMs: Math.round(budget.now() - budget.startedAt)
|
|
22331
|
+
});
|
|
22332
|
+
return { native: parsed?.native ?? null, event };
|
|
22333
|
+
}, skippedHook = (adapter, raw, env, budget, stage) => {
|
|
22334
|
+
const { native, event } = note(adapter, raw, env, budget, "skip", stage);
|
|
22335
|
+
return {
|
|
22336
|
+
...adapter.render(skipDecision(event), native),
|
|
22337
|
+
error: `${HOOK_SKIP_TEXT} (past the ${budget.deadlineMs} ms hook deadline)`,
|
|
22338
|
+
skipped: true
|
|
22339
|
+
};
|
|
22340
|
+
}, dispatchWithin = async (adapter, raw, env, budget, dispatch = dispatchHook) => {
|
|
22341
|
+
const native = raw?.hook_event_name;
|
|
22342
|
+
if (budget.host !== null && typeof native === "string")
|
|
22343
|
+
budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
|
|
22344
|
+
const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
|
|
22345
|
+
const result = remaining() > 0 ? await within(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
|
|
22346
|
+
if (isTimedOut(result))
|
|
22347
|
+
return skippedHook(adapter, raw, env, budget, "dispatch");
|
|
22348
|
+
if (remaining() < 0)
|
|
22349
|
+
note(adapter, raw, env, budget, "late", "done");
|
|
22350
|
+
return { ...result, skipped: false };
|
|
22351
|
+
};
|
|
22352
|
+
var init_deadline = __esm(() => {
|
|
22353
|
+
init_logger();
|
|
22354
|
+
init_handle();
|
|
22355
|
+
HOST_DEADLINES = {
|
|
22356
|
+
claude_code: { "": 8000, SessionStart: 16000, Stop: 16000 },
|
|
22357
|
+
codex_cli: { "": 45000, Stop: 16000 },
|
|
22358
|
+
codex_desktop: { "": 45000, Stop: 16000 },
|
|
22359
|
+
cursor: { "": 45000 }
|
|
22360
|
+
};
|
|
22361
|
+
QUIET = new Set([
|
|
22362
|
+
"stop",
|
|
22363
|
+
"subagent.stop",
|
|
22364
|
+
"shell.post",
|
|
22365
|
+
"compact.pre",
|
|
22366
|
+
"prompt.submit",
|
|
22367
|
+
null
|
|
22368
|
+
]);
|
|
22369
|
+
TIMED_OUT = Symbol("timed out");
|
|
22370
|
+
PROCESS_START = performance.timeOrigin;
|
|
22371
|
+
});
|
|
22372
|
+
|
|
21361
22373
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
21362
|
-
var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix", toolCommand = (value) =>
|
|
22374
|
+
var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix", toolCommand = (value) => isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
|
|
21363
22375
|
const toolUseId = optionalText(value.tool_use_id);
|
|
21364
22376
|
switch (name) {
|
|
21365
22377
|
case "SessionStart":
|
|
@@ -21386,19 +22398,28 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) =
|
|
|
21386
22398
|
kind: "tool.pre",
|
|
21387
22399
|
tool: value.tool_name,
|
|
21388
22400
|
toolUseId,
|
|
21389
|
-
skill:
|
|
22401
|
+
skill: isRecord6(value.tool_input) ? optionalText(value.tool_input.skill) : null
|
|
21390
22402
|
}
|
|
21391
22403
|
};
|
|
21392
22404
|
if (!SHELL_TOOLS.has(value.tool_name))
|
|
21393
22405
|
return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
|
|
21394
22406
|
const command = toolCommand(value);
|
|
21395
|
-
return command ? {
|
|
22407
|
+
return command ? {
|
|
22408
|
+
ok: true,
|
|
22409
|
+
event: {
|
|
22410
|
+
kind: "shell.pre",
|
|
22411
|
+
command,
|
|
22412
|
+
toolUseId,
|
|
22413
|
+
dialect: dialectOf2(value),
|
|
22414
|
+
toolInput: value.tool_input
|
|
22415
|
+
}
|
|
22416
|
+
} : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
|
|
21396
22417
|
}
|
|
21397
22418
|
case "PostToolUse": {
|
|
21398
22419
|
const command = toolCommand(value);
|
|
21399
22420
|
if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
|
|
21400
22421
|
return { ok: false, error: "only shell PostToolUse is mapped" };
|
|
21401
|
-
const response =
|
|
22422
|
+
const response = isRecord6(value.tool_response) ? value.tool_response : {};
|
|
21402
22423
|
return {
|
|
21403
22424
|
ok: true,
|
|
21404
22425
|
event: {
|
|
@@ -21448,6 +22469,17 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) =
|
|
|
21448
22469
|
};
|
|
21449
22470
|
}
|
|
21450
22471
|
}, CONTEXT_EVENTS, render = (decision, native) => {
|
|
22472
|
+
if (decision.kind === "rewrite" && native === "PreToolUse" && decision.input)
|
|
22473
|
+
return {
|
|
22474
|
+
json: {
|
|
22475
|
+
hookSpecificOutput: {
|
|
22476
|
+
hookEventName: "PreToolUse",
|
|
22477
|
+
updatedInput: decision.input,
|
|
22478
|
+
...decision.text ? { additionalContext: decision.text } : {}
|
|
22479
|
+
}
|
|
22480
|
+
},
|
|
22481
|
+
exitCode: 0
|
|
22482
|
+
};
|
|
21451
22483
|
if (decision.kind === "deny" && native === "PreToolUse")
|
|
21452
22484
|
return {
|
|
21453
22485
|
json: {
|
|
@@ -21491,7 +22523,12 @@ var init_claude_code = __esm(() => {
|
|
|
21491
22523
|
"compact.pre": { support: "none", native: null },
|
|
21492
22524
|
stop: { support: "native", native: "Stop" }
|
|
21493
22525
|
},
|
|
21494
|
-
shellPolicy: {
|
|
22526
|
+
shellPolicy: {
|
|
22527
|
+
deny: "native",
|
|
22528
|
+
channel: "permissionDecision",
|
|
22529
|
+
failClosed: false,
|
|
22530
|
+
rewrite: "native"
|
|
22531
|
+
},
|
|
21495
22532
|
context: {
|
|
21496
22533
|
sessionStart: "native",
|
|
21497
22534
|
perTurn: "native",
|
|
@@ -21582,14 +22619,14 @@ var init_claude_code = __esm(() => {
|
|
|
21582
22619
|
claudeCodeAdapter = {
|
|
21583
22620
|
descriptor: CLAUDE_CODE_DESCRIPTOR,
|
|
21584
22621
|
parse(raw) {
|
|
21585
|
-
const native =
|
|
22622
|
+
const native = isRecord6(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
21586
22623
|
const fail = (error) => ({
|
|
21587
22624
|
ok: false,
|
|
21588
22625
|
error,
|
|
21589
22626
|
native,
|
|
21590
22627
|
event: native ? EVENTS[native] : null
|
|
21591
22628
|
});
|
|
21592
|
-
if (!
|
|
22629
|
+
if (!isRecord6(raw) || !native)
|
|
21593
22630
|
return fail("hook_event_name is required");
|
|
21594
22631
|
if (!nonEmpty2(raw.session_id))
|
|
21595
22632
|
return fail("session_id is required");
|
|
@@ -21626,7 +22663,7 @@ function detectCodexSurface(env) {
|
|
|
21626
22663
|
return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
|
|
21627
22664
|
}
|
|
21628
22665
|
var CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase()), SOURCES2, parseCodexHookInput = (value) => {
|
|
21629
|
-
if (!
|
|
22666
|
+
if (!isRecord6(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
|
|
21630
22667
|
return { ok: false, error: "hook_event_name is required" };
|
|
21631
22668
|
const event = value.hook_event_name;
|
|
21632
22669
|
const cwd = existingDirectory(value.cwd);
|
|
@@ -21635,7 +22672,7 @@ var CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS
|
|
|
21635
22672
|
const toolEvent = event === "PreToolUse" || event === "PostToolUse";
|
|
21636
22673
|
if (toolEvent && !nonEmpty2(value.tool_name))
|
|
21637
22674
|
return { ok: false, error: "tool_name is required" };
|
|
21638
|
-
const command =
|
|
22675
|
+
const command = isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
21639
22676
|
if (toolEvent && isShellTool(value.tool_name) && !command)
|
|
21640
22677
|
return { ok: false, error: "tool_input.command is required for shell tools" };
|
|
21641
22678
|
if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
|
|
@@ -21761,7 +22798,12 @@ var init_codex = __esm(() => {
|
|
|
21761
22798
|
"compact.pre": { support: "none", native: null },
|
|
21762
22799
|
stop: { support: "native", native: "Stop" }
|
|
21763
22800
|
},
|
|
21764
|
-
shellPolicy: {
|
|
22801
|
+
shellPolicy: {
|
|
22802
|
+
deny: "native",
|
|
22803
|
+
channel: "permissionDecision",
|
|
22804
|
+
failClosed: false,
|
|
22805
|
+
rewrite: "none"
|
|
22806
|
+
},
|
|
21765
22807
|
context: {
|
|
21766
22808
|
sessionStart: "native",
|
|
21767
22809
|
perTurn: "native",
|
|
@@ -21857,7 +22899,7 @@ var init_codex = __esm(() => {
|
|
|
21857
22899
|
const host = detectCodexSurface(env);
|
|
21858
22900
|
const parsed = parseCodexHookInput(raw);
|
|
21859
22901
|
if (!parsed.ok) {
|
|
21860
|
-
const native =
|
|
22902
|
+
const native = isRecord6(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
21861
22903
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
|
|
21862
22904
|
}
|
|
21863
22905
|
const input = parsed.data;
|
|
@@ -21885,10 +22927,10 @@ var init_codex = __esm(() => {
|
|
|
21885
22927
|
});
|
|
21886
22928
|
|
|
21887
22929
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
21888
|
-
import
|
|
21889
|
-
import
|
|
22930
|
+
import fs21 from "node:fs";
|
|
22931
|
+
import path29 from "node:path";
|
|
21890
22932
|
var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
|
|
21891
|
-
if (!
|
|
22933
|
+
if (!isRecord6(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
21892
22934
|
return { ok: false, error: "hook_event_name is required" };
|
|
21893
22935
|
const roots = value.workspace_roots;
|
|
21894
22936
|
if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
|
|
@@ -21967,17 +23009,17 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
21967
23009
|
};
|
|
21968
23010
|
}
|
|
21969
23011
|
}, TRANSCRIPT_TAIL_BYTES, lastAssistantText = (file) => {
|
|
21970
|
-
if (!file || !
|
|
23012
|
+
if (!file || !path29.isAbsolute(file))
|
|
21971
23013
|
return null;
|
|
21972
23014
|
try {
|
|
21973
|
-
const size =
|
|
23015
|
+
const size = fs21.statSync(file).size;
|
|
21974
23016
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
21975
23017
|
const buffer = Buffer.alloc(size - start);
|
|
21976
|
-
const fd =
|
|
23018
|
+
const fd = fs21.openSync(file, "r");
|
|
21977
23019
|
try {
|
|
21978
|
-
|
|
23020
|
+
fs21.readSync(fd, buffer, 0, buffer.length, start);
|
|
21979
23021
|
} finally {
|
|
21980
|
-
|
|
23022
|
+
fs21.closeSync(fd);
|
|
21981
23023
|
}
|
|
21982
23024
|
const lines = buffer.toString("utf8").split(`
|
|
21983
23025
|
`);
|
|
@@ -21988,14 +23030,14 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
21988
23030
|
} catch {
|
|
21989
23031
|
continue;
|
|
21990
23032
|
}
|
|
21991
|
-
if (!
|
|
23033
|
+
if (!isRecord6(entry) || entry.role !== "assistant")
|
|
21992
23034
|
continue;
|
|
21993
|
-
const content =
|
|
23035
|
+
const content = isRecord6(entry.message) ? entry.message.content : entry.content;
|
|
21994
23036
|
if (typeof content === "string")
|
|
21995
23037
|
return content;
|
|
21996
23038
|
if (!Array.isArray(content))
|
|
21997
23039
|
return null;
|
|
21998
|
-
const text = content.filter((part) =>
|
|
23040
|
+
const text = content.filter((part) => isRecord6(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
|
|
21999
23041
|
`);
|
|
22000
23042
|
return text || null;
|
|
22001
23043
|
}
|
|
@@ -22003,14 +23045,14 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
22003
23045
|
} catch {
|
|
22004
23046
|
return null;
|
|
22005
23047
|
}
|
|
22006
|
-
},
|
|
23048
|
+
}, within2 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path29.sep) ? root : `${root}${path29.sep}`), hookCwd = (input) => {
|
|
22007
23049
|
const cwd = existingDirectory(input.cwd);
|
|
22008
23050
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
22009
23051
|
return cwd;
|
|
22010
23052
|
return cursorWorkspaceRoot(input);
|
|
22011
23053
|
}, cursorWorkspaceRoot = (input) => {
|
|
22012
23054
|
const cwd = existingDirectory(input.cwd);
|
|
22013
|
-
return cwd && input.workspace_roots.find((root) =>
|
|
23055
|
+
return cwd && input.workspace_roots.find((root) => within2(root, cwd)) || input.workspace_roots[0];
|
|
22014
23056
|
}, cursorDeny = (reason) => ({
|
|
22015
23057
|
permission: "deny",
|
|
22016
23058
|
user_message: "Workit blocked this action",
|
|
@@ -22057,7 +23099,7 @@ var init_cursor = __esm(() => {
|
|
|
22057
23099
|
"compact.pre": { support: "partial", native: "preCompact" },
|
|
22058
23100
|
stop: { support: "native", native: "stop" }
|
|
22059
23101
|
},
|
|
22060
|
-
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false },
|
|
23102
|
+
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false, rewrite: "none" },
|
|
22061
23103
|
context: {
|
|
22062
23104
|
sessionStart: "native",
|
|
22063
23105
|
perTurn: "none",
|
|
@@ -22156,7 +23198,7 @@ var init_cursor = __esm(() => {
|
|
|
22156
23198
|
parse(raw) {
|
|
22157
23199
|
const parsed = parseCursorHookInput(raw);
|
|
22158
23200
|
if (!parsed.ok) {
|
|
22159
|
-
const native =
|
|
23201
|
+
const native = isRecord6(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
22160
23202
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
|
|
22161
23203
|
}
|
|
22162
23204
|
const input = parsed.data;
|
|
@@ -22184,19 +23226,21 @@ var init_cursor = __esm(() => {
|
|
|
22184
23226
|
});
|
|
22185
23227
|
|
|
22186
23228
|
// packages/workit-core/src/hooks/run.ts
|
|
22187
|
-
async function runHookProcess(host, stdin, stdout, stderr = process.stderr, env = process.env) {
|
|
23229
|
+
async function runHookProcess(host, stdin, stdout, stderr = process.stderr, env = process.env, deadline = {}) {
|
|
22188
23230
|
const adapter = typeof host === "string" ? HOOK_ADAPTERS[host] : host;
|
|
22189
|
-
|
|
22190
|
-
|
|
22191
|
-
text += String(chunk);
|
|
23231
|
+
const budget = hookBudget(env, deadline, adapter.descriptor.host);
|
|
23232
|
+
const text = await readWithin(stdin, budget);
|
|
22192
23233
|
let raw;
|
|
22193
23234
|
try {
|
|
22194
|
-
raw = JSON.parse(text || "{}");
|
|
23235
|
+
raw = text === null ? undefined : JSON.parse(text || "{}");
|
|
22195
23236
|
} catch {
|
|
22196
23237
|
raw = undefined;
|
|
22197
23238
|
}
|
|
22198
|
-
const result =
|
|
22199
|
-
if (
|
|
23239
|
+
const result = text === null ? skippedHook(adapter, undefined, env, budget, "input") : await dispatchWithin(adapter, raw, env, budget, deadline.dispatch);
|
|
23240
|
+
if (result.skipped)
|
|
23241
|
+
stderr.write(`[workit] ${result.error}
|
|
23242
|
+
`);
|
|
23243
|
+
else if (raw === undefined || result.error)
|
|
22200
23244
|
stderr.write(`[workit] hook input rejected: ${raw === undefined ? "invalid JSON hook input" : result.error}
|
|
22201
23245
|
`);
|
|
22202
23246
|
stdout.write(`${JSON.stringify(result.json)}
|
|
@@ -22205,7 +23249,7 @@ async function runHookProcess(host, stdin, stdout, stderr = process.stderr, env
|
|
|
22205
23249
|
}
|
|
22206
23250
|
var HOOK_ADAPTERS;
|
|
22207
23251
|
var init_run = __esm(async () => {
|
|
22208
|
-
|
|
23252
|
+
init_deadline();
|
|
22209
23253
|
init_claude_code();
|
|
22210
23254
|
init_codex();
|
|
22211
23255
|
init_cursor();
|
|
@@ -22228,6 +23272,7 @@ var init_pi = () => {};
|
|
|
22228
23272
|
var init_hooks = __esm(async () => {
|
|
22229
23273
|
init_context();
|
|
22230
23274
|
init_policy();
|
|
23275
|
+
init_heavy_gate();
|
|
22231
23276
|
init_write_gate();
|
|
22232
23277
|
init_live_session();
|
|
22233
23278
|
init_stop();
|
|
@@ -22362,6 +23407,14 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
|
|
|
22362
23407
|
return { json: {} };
|
|
22363
23408
|
const note = `[workit: ${gap}; Workit checks are skipped for this action]`;
|
|
22364
23409
|
return { json: { permission: "allow", user_message: note, agent_message: note } };
|
|
23410
|
+
}, subagentStartStep = (_adapter, raw) => {
|
|
23411
|
+
let json;
|
|
23412
|
+
try {
|
|
23413
|
+
json = handleSubagentStartHook(raw);
|
|
23414
|
+
} catch (error) {
|
|
23415
|
+
json = cursorDeny(`hook failure: ${String(error)}`);
|
|
23416
|
+
}
|
|
23417
|
+
return { json, exitCode: json.permission === "deny" ? 2 : 0, error: null };
|
|
22365
23418
|
}, runCursorHook = async () => {
|
|
22366
23419
|
let text = "";
|
|
22367
23420
|
for await (const chunk of process.stdin)
|
|
@@ -22379,24 +23432,16 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
|
|
|
22379
23432
|
process.exitCode = 0;
|
|
22380
23433
|
return;
|
|
22381
23434
|
}
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
|
|
22385
|
-
|
|
22386
|
-
} catch (error) {
|
|
22387
|
-
output = cursorDeny(`hook failure: ${String(error)}`);
|
|
22388
|
-
}
|
|
22389
|
-
process.stdout.write(`${JSON.stringify(output)}
|
|
22390
|
-
`);
|
|
22391
|
-
process.exitCode = output.permission === "deny" ? 2 : 0;
|
|
22392
|
-
return;
|
|
22393
|
-
}
|
|
22394
|
-
process.exitCode = await runHookProcess(cursorAdapter, [text], process.stdout);
|
|
23435
|
+
process.exitCode = await runHookProcess(cursorAdapter, [text], process.stdout, process.stderr, process.env, {
|
|
23436
|
+
startedAt: PROCESS_START,
|
|
23437
|
+
...isSubagentStart(raw) ? { dispatch: subagentStartStep } : {}
|
|
23438
|
+
});
|
|
22395
23439
|
};
|
|
22396
23440
|
var init_workit_hook = __esm(async () => {
|
|
22397
23441
|
init_task_contract();
|
|
22398
23442
|
init_task_engine();
|
|
22399
23443
|
init_task_store();
|
|
23444
|
+
init_deadline();
|
|
22400
23445
|
await __promiseAll([
|
|
22401
23446
|
init_hooks(),
|
|
22402
23447
|
init_hooks()
|