@brainervirus/workit-cursor 8.16.0 → 8.17.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 +1029 -82
- package/dist/mcp-server.js +129 -2
- package/dist/workit-hook.js +1041 -80
- 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
package/dist/workit-hook.js
CHANGED
|
@@ -9366,6 +9366,7 @@ var workspaceConfigSchema = object({
|
|
|
9366
9366
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
9367
9367
|
defaultEndpoint: unknown().optional(),
|
|
9368
9368
|
verification: _enum(["self", "independent"]).optional(),
|
|
9369
|
+
delegation: unknown().optional(),
|
|
9369
9370
|
trailers: boolean2().optional(),
|
|
9370
9371
|
liveSessionGuard: boolean2().optional(),
|
|
9371
9372
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
@@ -15071,6 +15072,17 @@ import fs12 from "node:fs";
|
|
|
15071
15072
|
import path16 from "node:path";
|
|
15072
15073
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
15073
15074
|
var CHECKS_FILE = "workit.checks.json";
|
|
15075
|
+
var checksConfigFile = (root) => path16.join(root, CHECKS_FILE);
|
|
15076
|
+
function repoHeavyCommands(root) {
|
|
15077
|
+
try {
|
|
15078
|
+
const value = JSON.parse(fs12.readFileSync(checksConfigFile(root), "utf8"));
|
|
15079
|
+
const resources = isRecord3(value) ? value.resources : undefined;
|
|
15080
|
+
const list = isRecord3(resources) ? resources.heavyCommands : undefined;
|
|
15081
|
+
return Array.isArray(list) ? list.filter((item) => typeof item === "string") : [];
|
|
15082
|
+
} catch {
|
|
15083
|
+
return [];
|
|
15084
|
+
}
|
|
15085
|
+
}
|
|
15074
15086
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15075
15087
|
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
15076
15088
|
function splitCommand(command) {
|
|
@@ -15183,7 +15195,7 @@ function detectedChecks(root) {
|
|
|
15183
15195
|
var GATES = ["testing", "verification"];
|
|
15184
15196
|
function loadCheckConfig(cwd) {
|
|
15185
15197
|
const root = checkRoot(cwd);
|
|
15186
|
-
const file =
|
|
15198
|
+
const file = checksConfigFile(root);
|
|
15187
15199
|
const none = (error = null) => ({
|
|
15188
15200
|
source: error ? CHECKS_FILE : "none",
|
|
15189
15201
|
root,
|
|
@@ -19232,7 +19244,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
19232
19244
|
return found;
|
|
19233
19245
|
}
|
|
19234
19246
|
var RECENT = new Map;
|
|
19235
|
-
function
|
|
19247
|
+
function commandLines(command, dialect = "posix") {
|
|
19248
|
+
const lines = [];
|
|
19249
|
+
try {
|
|
19250
|
+
walk2(command, dialect, 0, lines);
|
|
19251
|
+
} catch (error) {
|
|
19252
|
+
throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
|
|
19253
|
+
}
|
|
19254
|
+
return lines;
|
|
19255
|
+
}
|
|
19256
|
+
function walk2(command, dialect, depth, seen) {
|
|
19236
19257
|
const found = [];
|
|
19237
19258
|
let dir = null;
|
|
19238
19259
|
const groups = [];
|
|
@@ -19243,7 +19264,8 @@ function walk2(command, dialect, depth) {
|
|
|
19243
19264
|
const nested = (script, inner) => {
|
|
19244
19265
|
if (!script || depth >= MAX_DEPTH)
|
|
19245
19266
|
return;
|
|
19246
|
-
|
|
19267
|
+
const inside = seen ? walk2(script, inner, depth + 1, seen) : rawInvocations(script, inner, depth + 1);
|
|
19268
|
+
for (const invocation of inside)
|
|
19247
19269
|
found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
|
|
19248
19270
|
};
|
|
19249
19271
|
const visit = (input, stdin, hops) => {
|
|
@@ -19255,11 +19277,17 @@ function walk2(command, dialect, depth) {
|
|
|
19255
19277
|
const name = commandName(words[0]);
|
|
19256
19278
|
if (!name)
|
|
19257
19279
|
return;
|
|
19280
|
+
seen?.push(words);
|
|
19281
|
+
if (name === "-c" || name === "--command")
|
|
19282
|
+
return nested(words[1], "posix");
|
|
19258
19283
|
const runs = (lines) => {
|
|
19259
19284
|
if (hops < MAX_DEPTH)
|
|
19260
19285
|
for (const next of lines)
|
|
19261
19286
|
visit(next, undefined, hops + 1);
|
|
19262
19287
|
};
|
|
19288
|
+
const wrapped = seen ? null : workitCheckTail(words);
|
|
19289
|
+
if (wrapped)
|
|
19290
|
+
return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
|
|
19263
19291
|
if (CD.has(name)) {
|
|
19264
19292
|
const target = cdTarget(words);
|
|
19265
19293
|
dir = target === null ? os5.homedir() : joinDir(dir, target);
|
|
@@ -19350,6 +19378,51 @@ var LAUNCHERS = new Set([
|
|
|
19350
19378
|
"command"
|
|
19351
19379
|
]);
|
|
19352
19380
|
var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
19381
|
+
var WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
|
|
19382
|
+
var PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
|
|
19383
|
+
function workitCheckTail(words) {
|
|
19384
|
+
let at = 0;
|
|
19385
|
+
if (PACKAGE_RUNNERS.has(commandName(words[0]))) {
|
|
19386
|
+
at = words.findIndex((word, index) => index > 0 && !word.startsWith("-"));
|
|
19387
|
+
if (at < 0 || !WORKIT_PACKAGE.test(words[at]))
|
|
19388
|
+
return null;
|
|
19389
|
+
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
19390
|
+
return null;
|
|
19391
|
+
const args = words.slice(at + 1);
|
|
19392
|
+
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
19393
|
+
return null;
|
|
19394
|
+
const split = args.indexOf("--");
|
|
19395
|
+
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
19396
|
+
if (!tail.length)
|
|
19397
|
+
return null;
|
|
19398
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
19399
|
+
}
|
|
19400
|
+
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
19401
|
+
let segments;
|
|
19402
|
+
try {
|
|
19403
|
+
segments = segmentsOf(command, dialect);
|
|
19404
|
+
} catch {
|
|
19405
|
+
return null;
|
|
19406
|
+
}
|
|
19407
|
+
if (segments.length !== 1)
|
|
19408
|
+
return null;
|
|
19409
|
+
const [segment] = segments;
|
|
19410
|
+
if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
|
|
19411
|
+
return null;
|
|
19412
|
+
const { words } = stripPrefixes(segment.words);
|
|
19413
|
+
const name = commandName(words[0]);
|
|
19414
|
+
if (!name || depth >= MAX_DEPTH)
|
|
19415
|
+
return null;
|
|
19416
|
+
if (SHELLS.has(name)) {
|
|
19417
|
+
const script = shellScript(words, undefined);
|
|
19418
|
+
return script ? simpleCommand(script, "posix", depth + 1) : null;
|
|
19419
|
+
}
|
|
19420
|
+
if (name === "-c" || name === "--command")
|
|
19421
|
+
return words[1] ? simpleCommand(words[1], "posix", depth + 1) : null;
|
|
19422
|
+
if (EVALS.has(name) || POWERSHELLS.has(name) || ["xargs", "find", "parallel", "script", "cmd", "source", "."].includes(name))
|
|
19423
|
+
return null;
|
|
19424
|
+
return words;
|
|
19425
|
+
}
|
|
19353
19426
|
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
19354
19427
|
const found = [];
|
|
19355
19428
|
for (const { words } of segmentsOf(command, dialect)) {
|
|
@@ -20377,17 +20450,827 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
20377
20450
|
}
|
|
20378
20451
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
20379
20452
|
};
|
|
20380
|
-
// packages/workit-core/src/
|
|
20381
|
-
import
|
|
20453
|
+
// packages/workit-core/src/core/heavy-rules.ts
|
|
20454
|
+
import fs17 from "node:fs";
|
|
20455
|
+
import os6 from "node:os";
|
|
20456
|
+
import path22 from "node:path";
|
|
20457
|
+
var EMPTY = () => ({ deny: [], allow: [] });
|
|
20458
|
+
var readText2 = (file) => {
|
|
20459
|
+
try {
|
|
20460
|
+
return fs17.readFileSync(file, "utf8");
|
|
20461
|
+
} catch (error) {
|
|
20462
|
+
if (error.code === "ENOENT")
|
|
20463
|
+
return null;
|
|
20464
|
+
throw error;
|
|
20465
|
+
}
|
|
20466
|
+
};
|
|
20467
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20468
|
+
function parseJsonc(text) {
|
|
20469
|
+
let out = "";
|
|
20470
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
20471
|
+
const char = text[index];
|
|
20472
|
+
if (char === '"') {
|
|
20473
|
+
const end = /"(?:[^"\\]|\\.)*"/y;
|
|
20474
|
+
end.lastIndex = index;
|
|
20475
|
+
const match = end.exec(text);
|
|
20476
|
+
if (!match)
|
|
20477
|
+
throw new Error("unterminated string");
|
|
20478
|
+
out += match[0];
|
|
20479
|
+
index += match[0].length - 1;
|
|
20480
|
+
} else if (char === "/" && text[index + 1] === "/") {
|
|
20481
|
+
while (index < text.length && text[index] !== `
|
|
20482
|
+
`)
|
|
20483
|
+
index += 1;
|
|
20484
|
+
out += `
|
|
20485
|
+
`;
|
|
20486
|
+
} else if (char === "/" && text[index + 1] === "*") {
|
|
20487
|
+
const close = text.indexOf("*/", index + 2);
|
|
20488
|
+
if (close < 0)
|
|
20489
|
+
throw new Error("unterminated comment");
|
|
20490
|
+
index = close + 1;
|
|
20491
|
+
} else
|
|
20492
|
+
out += char;
|
|
20493
|
+
}
|
|
20494
|
+
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
20495
|
+
}
|
|
20496
|
+
function gitTop(cwd) {
|
|
20497
|
+
for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
|
|
20498
|
+
if (fs17.existsSync(path22.join(dir, ".git")))
|
|
20499
|
+
return dir;
|
|
20500
|
+
if (path22.dirname(dir) === dir)
|
|
20501
|
+
return null;
|
|
20502
|
+
}
|
|
20503
|
+
}
|
|
20504
|
+
var projectDirs = (cwd) => {
|
|
20505
|
+
const top = gitTop(cwd);
|
|
20506
|
+
const dirs = [];
|
|
20507
|
+
for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
|
|
20508
|
+
dirs.push(dir);
|
|
20509
|
+
if (!top || dir === top || path22.dirname(dir) === dir)
|
|
20510
|
+
return dirs;
|
|
20511
|
+
}
|
|
20512
|
+
};
|
|
20513
|
+
var homeOf = (env) => env.HOME || os6.homedir();
|
|
20514
|
+
var CLAUDE_MANAGED = {
|
|
20515
|
+
linux: "/etc/claude-code/managed-settings.json",
|
|
20516
|
+
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
20517
|
+
win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
|
|
20518
|
+
};
|
|
20519
|
+
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
20520
|
+
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
20521
|
+
const user = env.CLAUDE_CONFIG_DIR || path22.join(homeOf(env), ".claude");
|
|
20522
|
+
return [
|
|
20523
|
+
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
20524
|
+
path22.join(user, "settings.json"),
|
|
20525
|
+
path22.join(project, ".claude", "settings.json"),
|
|
20526
|
+
path22.join(project, ".claude", "settings.local.json")
|
|
20527
|
+
];
|
|
20528
|
+
}
|
|
20529
|
+
var bashPattern = (entry) => {
|
|
20530
|
+
const match = /^Bash(?:\((.*)\))?$/su.exec(entry.trim());
|
|
20531
|
+
return match ? match[1] ?? "" : null;
|
|
20532
|
+
};
|
|
20533
|
+
function claudeRules(cwd, env = process.env) {
|
|
20534
|
+
const rules = EMPTY();
|
|
20535
|
+
try {
|
|
20536
|
+
for (const file of claudeSettingsFiles(cwd, env)) {
|
|
20537
|
+
const text = readText2(file);
|
|
20538
|
+
if (text === null)
|
|
20539
|
+
continue;
|
|
20540
|
+
const value = JSON.parse(text);
|
|
20541
|
+
if (!isRecord4(value))
|
|
20542
|
+
return "unreadable";
|
|
20543
|
+
const permissions = value.permissions;
|
|
20544
|
+
if (permissions === undefined)
|
|
20545
|
+
continue;
|
|
20546
|
+
if (!isRecord4(permissions))
|
|
20547
|
+
return "unreadable";
|
|
20548
|
+
for (const [key, bucket] of [
|
|
20549
|
+
["deny", rules.deny],
|
|
20550
|
+
["ask", rules.deny],
|
|
20551
|
+
["allow", rules.allow]
|
|
20552
|
+
]) {
|
|
20553
|
+
const list = permissions[key];
|
|
20554
|
+
if (list === undefined)
|
|
20555
|
+
continue;
|
|
20556
|
+
if (!Array.isArray(list))
|
|
20557
|
+
return "unreadable";
|
|
20558
|
+
for (const entry of list) {
|
|
20559
|
+
if (typeof entry !== "string")
|
|
20560
|
+
return "unreadable";
|
|
20561
|
+
const pattern = bashPattern(entry);
|
|
20562
|
+
if (pattern !== null)
|
|
20563
|
+
bucket.push(pattern);
|
|
20564
|
+
}
|
|
20565
|
+
}
|
|
20566
|
+
}
|
|
20567
|
+
} catch {
|
|
20568
|
+
return "unreadable";
|
|
20569
|
+
}
|
|
20570
|
+
return rules;
|
|
20571
|
+
}
|
|
20572
|
+
var OPENCODE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
20573
|
+
var opencodePermission = (permission, rules) => {
|
|
20574
|
+
if (permission === undefined)
|
|
20575
|
+
return true;
|
|
20576
|
+
if (typeof permission === "string") {
|
|
20577
|
+
(permission === "allow" ? rules.allow : rules.deny).push("");
|
|
20578
|
+
return ["allow", "ask", "deny"].includes(permission);
|
|
20579
|
+
}
|
|
20580
|
+
if (!isRecord4(permission))
|
|
20581
|
+
return false;
|
|
20582
|
+
for (const key of ["bash", "shell"]) {
|
|
20583
|
+
const value = permission[key];
|
|
20584
|
+
if (value === undefined)
|
|
20585
|
+
continue;
|
|
20586
|
+
if (typeof value === "string") {
|
|
20587
|
+
if (!["allow", "ask", "deny"].includes(value))
|
|
20588
|
+
return false;
|
|
20589
|
+
(value === "allow" ? rules.allow : rules.deny).push("");
|
|
20590
|
+
continue;
|
|
20591
|
+
}
|
|
20592
|
+
if (!isRecord4(value))
|
|
20593
|
+
return false;
|
|
20594
|
+
for (const [pattern, action] of Object.entries(value)) {
|
|
20595
|
+
if (action !== "allow" && action !== "ask" && action !== "deny")
|
|
20596
|
+
return false;
|
|
20597
|
+
(action === "allow" ? rules.allow : rules.deny).push(pattern);
|
|
20598
|
+
}
|
|
20599
|
+
}
|
|
20600
|
+
return true;
|
|
20601
|
+
};
|
|
20602
|
+
var opencodeConfig = (value, rules) => {
|
|
20603
|
+
if (!isRecord4(value))
|
|
20604
|
+
return false;
|
|
20605
|
+
if (!opencodePermission(value.permission, rules))
|
|
20606
|
+
return false;
|
|
20607
|
+
if (value.agent === undefined)
|
|
20608
|
+
return true;
|
|
20609
|
+
if (!isRecord4(value.agent))
|
|
20610
|
+
return false;
|
|
20611
|
+
return Object.values(value.agent).every((agent) => isRecord4(agent) && opencodePermission(agent.permission, rules));
|
|
20612
|
+
};
|
|
20613
|
+
function opencodeRules(cwd, env = process.env) {
|
|
20614
|
+
const rules = EMPTY();
|
|
20615
|
+
try {
|
|
20616
|
+
const config = env.XDG_CONFIG_HOME || path22.join(homeOf(env), ".config");
|
|
20617
|
+
const files = [
|
|
20618
|
+
...OPENCODE_NAMES.map((name) => path22.join(config, "opencode", name)),
|
|
20619
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path22.join(dir, name), path22.join(dir, ".opencode", name)])),
|
|
20620
|
+
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
20621
|
+
];
|
|
20622
|
+
for (const file of files) {
|
|
20623
|
+
const text = readText2(file);
|
|
20624
|
+
if (text !== null && !opencodeConfig(parseJsonc(text), rules))
|
|
20625
|
+
return "unreadable";
|
|
20626
|
+
}
|
|
20627
|
+
if (env.OPENCODE_CONFIG_CONTENT && !opencodeConfig(parseJsonc(env.OPENCODE_CONFIG_CONTENT), rules))
|
|
20628
|
+
return "unreadable";
|
|
20629
|
+
} catch {
|
|
20630
|
+
return "unreadable";
|
|
20631
|
+
}
|
|
20632
|
+
return rules;
|
|
20633
|
+
}
|
|
20634
|
+
var codexRule = (body) => {
|
|
20635
|
+
const open = /\bpattern\s*=\s*\[/u.exec(body);
|
|
20636
|
+
if (!open)
|
|
20637
|
+
return null;
|
|
20638
|
+
const words = [];
|
|
20639
|
+
let index = open.index + open[0].length;
|
|
20640
|
+
for (;; ) {
|
|
20641
|
+
while (/[\s,]/u.test(body[index] ?? ""))
|
|
20642
|
+
index += 1;
|
|
20643
|
+
if (body[index] === "]")
|
|
20644
|
+
break;
|
|
20645
|
+
if (body[index] !== '"')
|
|
20646
|
+
return null;
|
|
20647
|
+
const literal = /"((?:[^"\\]|\\.)*)"/y;
|
|
20648
|
+
literal.lastIndex = index;
|
|
20649
|
+
const match = literal.exec(body);
|
|
20650
|
+
if (!match)
|
|
20651
|
+
return null;
|
|
20652
|
+
words.push(JSON.parse(`"${match[1]}"`));
|
|
20653
|
+
index = literal.lastIndex;
|
|
20654
|
+
}
|
|
20655
|
+
const decision = /\bdecision\s*=\s*"(\w+)"/u.exec(body)?.[1];
|
|
20656
|
+
if (!words.length || !decision || !["allow", "prompt", "forbidden"].includes(decision))
|
|
20657
|
+
return null;
|
|
20658
|
+
return { pattern: words.join(" "), decision };
|
|
20659
|
+
};
|
|
20660
|
+
function codexRules(cwd, env = process.env) {
|
|
20661
|
+
const rules = EMPTY();
|
|
20662
|
+
try {
|
|
20663
|
+
const home = env.CODEX_HOME || path22.join(homeOf(env), ".codex");
|
|
20664
|
+
const top = gitTop(cwd) ?? cwd;
|
|
20665
|
+
for (const dir of [path22.join(home, "rules"), path22.join(top, ".codex", "rules")]) {
|
|
20666
|
+
let files;
|
|
20667
|
+
try {
|
|
20668
|
+
files = fs17.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
20669
|
+
} catch (error) {
|
|
20670
|
+
if (error.code === "ENOENT")
|
|
20671
|
+
continue;
|
|
20672
|
+
return "unreadable";
|
|
20673
|
+
}
|
|
20674
|
+
for (const name of files) {
|
|
20675
|
+
const text = readText2(path22.join(dir, name)) ?? "";
|
|
20676
|
+
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
20677
|
+
const rule = codexRule(part);
|
|
20678
|
+
if (!rule)
|
|
20679
|
+
return "unreadable";
|
|
20680
|
+
(rule.decision === "allow" ? rules.allow : rules.deny).push(rule.pattern);
|
|
20681
|
+
}
|
|
20682
|
+
}
|
|
20683
|
+
}
|
|
20684
|
+
} catch {
|
|
20685
|
+
return "unreadable";
|
|
20686
|
+
}
|
|
20687
|
+
return rules;
|
|
20688
|
+
}
|
|
20689
|
+
function cursorRules(cwd, env = process.env) {
|
|
20690
|
+
const rules = EMPTY();
|
|
20691
|
+
try {
|
|
20692
|
+
const top = gitTop(cwd) ?? cwd;
|
|
20693
|
+
for (const file of [
|
|
20694
|
+
path22.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
20695
|
+
path22.join(top, ".cursor", "cli.json")
|
|
20696
|
+
]) {
|
|
20697
|
+
const text = readText2(file);
|
|
20698
|
+
if (text === null)
|
|
20699
|
+
continue;
|
|
20700
|
+
const value = JSON.parse(text);
|
|
20701
|
+
if (!isRecord4(value))
|
|
20702
|
+
return "unreadable";
|
|
20703
|
+
if (value.permissions === undefined)
|
|
20704
|
+
continue;
|
|
20705
|
+
if (!isRecord4(value.permissions))
|
|
20706
|
+
return "unreadable";
|
|
20707
|
+
for (const [key, bucket] of [
|
|
20708
|
+
["deny", rules.deny],
|
|
20709
|
+
["allow", rules.allow]
|
|
20710
|
+
]) {
|
|
20711
|
+
const list = value.permissions[key];
|
|
20712
|
+
if (list === undefined)
|
|
20713
|
+
continue;
|
|
20714
|
+
if (!Array.isArray(list))
|
|
20715
|
+
return "unreadable";
|
|
20716
|
+
for (const entry of list) {
|
|
20717
|
+
if (typeof entry !== "string")
|
|
20718
|
+
return "unreadable";
|
|
20719
|
+
const match = /^Shell(?:\((.*)\))?$/su.exec(entry.trim());
|
|
20720
|
+
if (match)
|
|
20721
|
+
bucket.push(match[1] ?? "");
|
|
20722
|
+
}
|
|
20723
|
+
}
|
|
20724
|
+
}
|
|
20725
|
+
} catch {
|
|
20726
|
+
return "unreadable";
|
|
20727
|
+
}
|
|
20728
|
+
return rules;
|
|
20729
|
+
}
|
|
20730
|
+
var baseName = (word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "");
|
|
20731
|
+
var firstToken = (pattern) => pattern.trim().split(/[:\s]/u)[0] ?? "";
|
|
20732
|
+
function names(pattern, words) {
|
|
20733
|
+
const token = firstToken(pattern);
|
|
20734
|
+
if (!token || token === "*" || token.includes("*"))
|
|
20735
|
+
return true;
|
|
20736
|
+
const wanted = baseName(token);
|
|
20737
|
+
return words.some((word) => baseName(word) === wanted);
|
|
20738
|
+
}
|
|
20739
|
+
var literalPrefix = (pattern) => pattern.trim().replace(/(?::\*| \*|\*)$/u, "");
|
|
20740
|
+
var coversWrapper = (pattern) => {
|
|
20741
|
+
const token = firstToken(pattern);
|
|
20742
|
+
if (!token || token.includes("*"))
|
|
20743
|
+
return true;
|
|
20744
|
+
const words = literalPrefix(pattern).split(/\s+/u);
|
|
20745
|
+
if (baseName(words[0]) !== "workit")
|
|
20746
|
+
return false;
|
|
20747
|
+
return ["check", "--heavy"].slice(0, words.length - 1).every((word, at) => {
|
|
20748
|
+
const given = words[at + 1];
|
|
20749
|
+
return given === word || word.startsWith(given);
|
|
20750
|
+
});
|
|
20751
|
+
};
|
|
20752
|
+
var coversCommand = (pattern, command) => {
|
|
20753
|
+
const prefix = literalPrefix(pattern);
|
|
20754
|
+
if (!prefix)
|
|
20755
|
+
return true;
|
|
20756
|
+
return !prefix.includes("*") && command.trim().startsWith(prefix);
|
|
20757
|
+
};
|
|
20758
|
+
function wrapAllowed(read, words, command, options = {}) {
|
|
20759
|
+
if (read === "unreadable")
|
|
20760
|
+
return { ok: false, why: "named" };
|
|
20761
|
+
if (read.deny.some((pattern) => names(pattern, words)))
|
|
20762
|
+
return { ok: false, why: "named" };
|
|
20763
|
+
if (!options.bypass && read.allow.some(coversWrapper) && !read.allow.some((pattern) => coversCommand(pattern, command)))
|
|
20764
|
+
return { ok: false, why: "allowlisted" };
|
|
20765
|
+
return { ok: true };
|
|
20766
|
+
}
|
|
20767
|
+
|
|
20768
|
+
// packages/workit-core/src/core/heavy.ts
|
|
20769
|
+
import fs18 from "node:fs";
|
|
20382
20770
|
import os7 from "node:os";
|
|
20383
20771
|
import path23 from "node:path";
|
|
20772
|
+
function stateDir(env, platform = process.platform) {
|
|
20773
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
20774
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
20775
|
+
if (env.XDG_STATE_HOME)
|
|
20776
|
+
return path23.join(env.XDG_STATE_HOME, "workit");
|
|
20777
|
+
const home = env.HOME || os7.homedir();
|
|
20778
|
+
if (platform === "darwin")
|
|
20779
|
+
return path23.join(home, "Library", "Application Support", "workit");
|
|
20780
|
+
if (platform === "win32")
|
|
20781
|
+
return path23.join(env.LOCALAPPDATA ?? path23.join(home, "AppData", "Local"), "workit");
|
|
20782
|
+
return path23.join(home, ".local", "state", "workit");
|
|
20783
|
+
}
|
|
20784
|
+
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
20785
|
+
var heavyCommandsFile = (env) => path23.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
20786
|
+
var 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;
|
|
20787
|
+
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
20788
|
+
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
20789
|
+
var WATCH_SCRIPT = /(?:^|[:_.-])(?:watch|dev|serve|start|storybook)(?:$|[:_.-])/iu;
|
|
20790
|
+
var sub = (...names) => new Set(names);
|
|
20791
|
+
var TOOLS = {
|
|
20792
|
+
jest: "any",
|
|
20793
|
+
vitest: "any",
|
|
20794
|
+
mocha: "any",
|
|
20795
|
+
ava: "any",
|
|
20796
|
+
tap: "any",
|
|
20797
|
+
karma: "any",
|
|
20798
|
+
jasmine: "any",
|
|
20799
|
+
nyc: "any",
|
|
20800
|
+
c8: "any",
|
|
20801
|
+
tsc: "any",
|
|
20802
|
+
"vue-tsc": "any",
|
|
20803
|
+
"svelte-check": "any",
|
|
20804
|
+
eslint: "any",
|
|
20805
|
+
stylelint: "any",
|
|
20806
|
+
tslint: "any",
|
|
20807
|
+
webpack: "any",
|
|
20808
|
+
"webpack-cli": "any",
|
|
20809
|
+
rollup: "any",
|
|
20810
|
+
parcel: "any",
|
|
20811
|
+
tsup: "any",
|
|
20812
|
+
vite: sub("build", "optimize"),
|
|
20813
|
+
next: sub("build", "lint"),
|
|
20814
|
+
nuxt: sub("build", "generate"),
|
|
20815
|
+
nuxi: sub("build", "generate"),
|
|
20816
|
+
astro: sub("build", "check"),
|
|
20817
|
+
ng: sub("build", "b", "test", "t", "e2e", "lint"),
|
|
20818
|
+
"react-scripts": sub("build", "test"),
|
|
20819
|
+
playwright: sub("test", "install"),
|
|
20820
|
+
cypress: sub("run"),
|
|
20821
|
+
wdio: "any",
|
|
20822
|
+
testcafe: "any",
|
|
20823
|
+
turbo: "any",
|
|
20824
|
+
lerna: sub("run", "exec", "bootstrap"),
|
|
20825
|
+
"sonar-scanner": "any",
|
|
20826
|
+
sonarless: sub("scan"),
|
|
20827
|
+
"sonarless-mcp": sub("scan"),
|
|
20828
|
+
go: sub("test", "build", "vet", "install", "generate"),
|
|
20829
|
+
"golangci-lint": sub("run"),
|
|
20830
|
+
gotestsum: "any",
|
|
20831
|
+
staticcheck: "any",
|
|
20832
|
+
cargo: sub("build", "b", "test", "t", "check", "c", "clippy", "bench", "nextest", "doc", "install", "tarpaulin", "llvm-cov"),
|
|
20833
|
+
mvn: "any",
|
|
20834
|
+
mvnw: "any",
|
|
20835
|
+
gradle: "any",
|
|
20836
|
+
gradlew: "any",
|
|
20837
|
+
ant: "any",
|
|
20838
|
+
sbt: "any",
|
|
20839
|
+
bazel: sub("build", "test", "run", "coverage"),
|
|
20840
|
+
bazelisk: sub("build", "test", "run", "coverage"),
|
|
20841
|
+
buck2: sub("build", "test", "run"),
|
|
20842
|
+
dotnet: sub("build", "test", "publish", "restore", "pack", "msbuild"),
|
|
20843
|
+
msbuild: "any",
|
|
20844
|
+
pytest: "any",
|
|
20845
|
+
"py.test": "any",
|
|
20846
|
+
tox: "any",
|
|
20847
|
+
nox: "any",
|
|
20848
|
+
mypy: "any",
|
|
20849
|
+
pyright: "any",
|
|
20850
|
+
pylint: "any",
|
|
20851
|
+
coverage: sub("run"),
|
|
20852
|
+
maturin: sub("build", "develop"),
|
|
20853
|
+
pip: sub("install", "wheel"),
|
|
20854
|
+
pip3: sub("install", "wheel"),
|
|
20855
|
+
rspec: "any",
|
|
20856
|
+
rails: sub("test", "assets:precompile"),
|
|
20857
|
+
phpunit: "any",
|
|
20858
|
+
pest: "any",
|
|
20859
|
+
phpstan: "any",
|
|
20860
|
+
psalm: "any",
|
|
20861
|
+
composer: sub("install", "update", "require", "test"),
|
|
20862
|
+
mix: sub("test", "compile", "deps.get", "dialyzer", "credo"),
|
|
20863
|
+
swift: sub("build", "test"),
|
|
20864
|
+
xcodebuild: "any",
|
|
20865
|
+
flutter: sub("build", "test"),
|
|
20866
|
+
dart: sub("test", "compile"),
|
|
20867
|
+
pod: sub("install"),
|
|
20868
|
+
cmake: sub("--build"),
|
|
20869
|
+
ninja: "any",
|
|
20870
|
+
meson: sub("compile", "test"),
|
|
20871
|
+
ctest: "any",
|
|
20872
|
+
scons: "any",
|
|
20873
|
+
"docker-compose": sub("build"),
|
|
20874
|
+
buildah: sub("bud", "build"),
|
|
20875
|
+
earthly: "any"
|
|
20876
|
+
};
|
|
20877
|
+
var PM_VALUES = new Set([
|
|
20878
|
+
"--prefix",
|
|
20879
|
+
"-w",
|
|
20880
|
+
"--workspace",
|
|
20881
|
+
"-C",
|
|
20882
|
+
"--dir",
|
|
20883
|
+
"--filter",
|
|
20884
|
+
"-F",
|
|
20885
|
+
"--cwd",
|
|
20886
|
+
"-p",
|
|
20887
|
+
"--package",
|
|
20888
|
+
"--cache",
|
|
20889
|
+
"--registry"
|
|
20890
|
+
]);
|
|
20891
|
+
function positional2(words, from, values = PM_VALUES) {
|
|
20892
|
+
let index = from;
|
|
20893
|
+
while (index < words.length && words[index].startsWith("-")) {
|
|
20894
|
+
if (words[index] === "--")
|
|
20895
|
+
return index + 1;
|
|
20896
|
+
index += values.has(words[index]) ? 2 : 1;
|
|
20897
|
+
}
|
|
20898
|
+
return index;
|
|
20899
|
+
}
|
|
20900
|
+
var baseName2 = (word) => (word?.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/u, "");
|
|
20901
|
+
var WORKIT = /^(?:workit|@brainervirus\/workit-cli(?:@.*)?)$/u;
|
|
20902
|
+
var PYTHONS = /^(?:python[\d.]*|py)$/u;
|
|
20903
|
+
var NPM_INSTALL = new Set([
|
|
20904
|
+
"ci",
|
|
20905
|
+
"clean-install",
|
|
20906
|
+
"cit",
|
|
20907
|
+
"install-test",
|
|
20908
|
+
"it",
|
|
20909
|
+
"install",
|
|
20910
|
+
"i",
|
|
20911
|
+
"in",
|
|
20912
|
+
"add",
|
|
20913
|
+
"rebuild",
|
|
20914
|
+
"update",
|
|
20915
|
+
"up"
|
|
20916
|
+
]);
|
|
20917
|
+
var NPM_RUN = new Set(["run", "run-script", "rum", "urn"]);
|
|
20918
|
+
var NPM_TEST = new Set(["test", "t", "tst"]);
|
|
20919
|
+
var EXEC = new Set(["exec", "x", "dlx"]);
|
|
20920
|
+
function builtIn(words, depth = 0) {
|
|
20921
|
+
if (depth > 6 || !words.length)
|
|
20922
|
+
return null;
|
|
20923
|
+
const name = baseName2(words[0]);
|
|
20924
|
+
const label = () => words.slice(0, 3).join(" ");
|
|
20925
|
+
const inner = (from) => builtIn(words.slice(from), depth + 1);
|
|
20926
|
+
if (WORKIT.test(name))
|
|
20927
|
+
return null;
|
|
20928
|
+
if (INFO_FLAGS.has(words[1] ?? "") && words.length === 2)
|
|
20929
|
+
return null;
|
|
20930
|
+
if (words.some((word) => word === "--watch" || word.startsWith("--watch=") || word === "--watchAll"))
|
|
20931
|
+
return null;
|
|
20932
|
+
if (name === "npx" || name === "bunx" || name === "pnpx")
|
|
20933
|
+
return inner(positional2(words, 1));
|
|
20934
|
+
if (name === "corepack")
|
|
20935
|
+
return inner(1);
|
|
20936
|
+
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" || name === "deno") {
|
|
20937
|
+
const at = positional2(words, 1);
|
|
20938
|
+
const command = words[at];
|
|
20939
|
+
if (command === undefined)
|
|
20940
|
+
return name === "yarn" ? "yarn (install)" : null;
|
|
20941
|
+
if (name === "deno") {
|
|
20942
|
+
if (["test", "bench", "check", "compile", "lint"].includes(command))
|
|
20943
|
+
return label();
|
|
20944
|
+
if (command === "task" && HEAVY_SCRIPT.test(words[at + 1] ?? ""))
|
|
20945
|
+
return label();
|
|
20946
|
+
return null;
|
|
20947
|
+
}
|
|
20948
|
+
if (NPM_INSTALL.has(command) || NPM_TEST.has(command))
|
|
20949
|
+
return label();
|
|
20950
|
+
if (name === "bun" && command === "build")
|
|
20951
|
+
return label();
|
|
20952
|
+
if (EXEC.has(command))
|
|
20953
|
+
return inner(positional2(words, at + 1));
|
|
20954
|
+
if (name === "yarn" && command === "workspace")
|
|
20955
|
+
return inner(at + 2);
|
|
20956
|
+
if (name === "yarn" && command === "workspaces")
|
|
20957
|
+
return words.slice(at + 1).some((word) => HEAVY_SCRIPT.test(word)) ? label() : null;
|
|
20958
|
+
if (NPM_RUN.has(command)) {
|
|
20959
|
+
const script = words[positional2(words, at + 1)];
|
|
20960
|
+
if (script === undefined || WATCH_SCRIPT.test(script))
|
|
20961
|
+
return null;
|
|
20962
|
+
if (HEAVY_SCRIPT.test(script))
|
|
20963
|
+
return label();
|
|
20964
|
+
return name === "bun" ? inner(positional2(words, at + 1)) : null;
|
|
20965
|
+
}
|
|
20966
|
+
if (name === "npm")
|
|
20967
|
+
return null;
|
|
20968
|
+
if (WATCH_SCRIPT.test(command))
|
|
20969
|
+
return null;
|
|
20970
|
+
if (HEAVY_SCRIPT.test(command) && !/\.[cm]?[jt]sx?$/u.test(command))
|
|
20971
|
+
return label();
|
|
20972
|
+
return inner(at);
|
|
20973
|
+
}
|
|
20974
|
+
if (PYTHONS.test(name)) {
|
|
20975
|
+
const at = words.indexOf("-m");
|
|
20976
|
+
return at > 0 && words[at + 1] ? inner(at + 1) : null;
|
|
20977
|
+
}
|
|
20978
|
+
if (["uv", "poetry", "pdm", "pipenv", "hatch", "rye"].includes(name)) {
|
|
20979
|
+
const at = positional2(words, 1);
|
|
20980
|
+
const command = words[at];
|
|
20981
|
+
if (command === "run")
|
|
20982
|
+
return inner(positional2(words, at + 1));
|
|
20983
|
+
if (command === "pip")
|
|
20984
|
+
return inner(at);
|
|
20985
|
+
return ["sync", "install", "build", "test", "add"].includes(command ?? "") ? label() : null;
|
|
20986
|
+
}
|
|
20987
|
+
if (name === "bundle") {
|
|
20988
|
+
const command = words[positional2(words, 1)];
|
|
20989
|
+
if (command === "exec")
|
|
20990
|
+
return inner(positional2(words, 2));
|
|
20991
|
+
return command === "install" || command === undefined ? label() : null;
|
|
20992
|
+
}
|
|
20993
|
+
if (name === "make" || name === "gmake") {
|
|
20994
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
20995
|
+
if (targets.some((target) => WATCH_SCRIPT.test(target)))
|
|
20996
|
+
return null;
|
|
20997
|
+
return !targets.length || targets.some((target) => HEAVY_SCRIPT.test(target)) ? label() : null;
|
|
20998
|
+
}
|
|
20999
|
+
if (name === "rake") {
|
|
21000
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
21001
|
+
return targets.some((target) => HEAVY_SCRIPT.test(target) || /^spec(?:$|:)/u.test(target)) ? label() : null;
|
|
21002
|
+
}
|
|
21003
|
+
if (name === "nx") {
|
|
21004
|
+
const at = positional2(words, 1);
|
|
21005
|
+
const command = words[at] ?? "";
|
|
21006
|
+
if (["run-many", "affected"].includes(command))
|
|
21007
|
+
return words.some((word) => /^(?:-t|--targets?)(?:=|$)/u.test(word)) && !words.some((word) => WATCH_SCRIPT.test(word.replace(/^--?targets?=/u, ""))) ? label() : null;
|
|
21008
|
+
if (command === "run") {
|
|
21009
|
+
const target = (words[at + 1] ?? "").split(":")[1] ?? "";
|
|
21010
|
+
return HEAVY_SCRIPT.test(target) && !WATCH_SCRIPT.test(target) ? label() : null;
|
|
21011
|
+
}
|
|
21012
|
+
return HEAVY_SCRIPT.test(command) && !WATCH_SCRIPT.test(command) ? label() : null;
|
|
21013
|
+
}
|
|
21014
|
+
if (name === "docker" || name === "podman" || name === "nerdctl") {
|
|
21015
|
+
const at = positional2(words, 1, new Set(["-H", "--host", "--context", "-c", "--config"]));
|
|
21016
|
+
const command = words[at];
|
|
21017
|
+
if (command === "build")
|
|
21018
|
+
return label();
|
|
21019
|
+
if (command === "buildx" || command === "image" || command === "builder")
|
|
21020
|
+
return words[positional2(words, at + 1)] === "build" ? label() : null;
|
|
21021
|
+
if (command === "compose") {
|
|
21022
|
+
const at2 = positional2(words, at + 1, new Set(["-f", "--file", "-p"]));
|
|
21023
|
+
const action = words[at2];
|
|
21024
|
+
if (action === "build")
|
|
21025
|
+
return label();
|
|
21026
|
+
return action === "up" && words.slice(at2 + 1).includes("--build") ? label() : null;
|
|
21027
|
+
}
|
|
21028
|
+
return null;
|
|
21029
|
+
}
|
|
21030
|
+
const rule = TOOLS[name];
|
|
21031
|
+
if (!rule)
|
|
21032
|
+
return null;
|
|
21033
|
+
if (words.slice(1).some((word) => WATCH_FLAG.test(word) || word === "watch" || word === "dev"))
|
|
21034
|
+
return null;
|
|
21035
|
+
if (rule === "any")
|
|
21036
|
+
return label();
|
|
21037
|
+
const command = words.slice(1).find((word) => !word.startsWith("-") && !word.startsWith("+") || rule.has(word));
|
|
21038
|
+
return command !== undefined && rule.has(command) ? label() : null;
|
|
21039
|
+
}
|
|
21040
|
+
var globWord = (word) => new RegExp(`^${word.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*")}$`, "u");
|
|
21041
|
+
function parsePattern(pattern) {
|
|
21042
|
+
const words = pattern.trim().split(/\s+/u).filter(Boolean);
|
|
21043
|
+
return words.length ? words.map(globWord) : null;
|
|
21044
|
+
}
|
|
21045
|
+
var 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]));
|
|
21046
|
+
var readJson = (file) => {
|
|
21047
|
+
try {
|
|
21048
|
+
return JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
21049
|
+
} catch {
|
|
21050
|
+
return;
|
|
21051
|
+
}
|
|
21052
|
+
};
|
|
21053
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21054
|
+
var stringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
21055
|
+
function machinePatterns(env) {
|
|
21056
|
+
const value = readJson(heavyCommandsFile(env));
|
|
21057
|
+
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
21058
|
+
}
|
|
21059
|
+
function repoPatterns(cwd) {
|
|
21060
|
+
return repoHeavyCommands(gitTop(cwd) ?? path23.resolve(cwd));
|
|
21061
|
+
}
|
|
21062
|
+
function heavyLines(lines, options) {
|
|
21063
|
+
const found = [];
|
|
21064
|
+
let patterns = null;
|
|
21065
|
+
for (const line of lines) {
|
|
21066
|
+
const reason = builtIn(line);
|
|
21067
|
+
if (reason) {
|
|
21068
|
+
found.push({ line, source: "catalog", reason });
|
|
21069
|
+
continue;
|
|
21070
|
+
}
|
|
21071
|
+
patterns ??= (options.patterns ?? [
|
|
21072
|
+
...machinePatterns(options.env ?? process.env).map((pattern) => ({
|
|
21073
|
+
pattern,
|
|
21074
|
+
from: "machine"
|
|
21075
|
+
})),
|
|
21076
|
+
...repoPatterns(options.cwd).map((pattern) => ({ pattern, from: "repo" }))
|
|
21077
|
+
]).flatMap((entry) => {
|
|
21078
|
+
const matchers = parsePattern(entry.pattern);
|
|
21079
|
+
return matchers ? [{ ...entry, matchers }] : [];
|
|
21080
|
+
});
|
|
21081
|
+
const hit = patterns.find((entry) => matches(entry.matchers, line));
|
|
21082
|
+
if (hit)
|
|
21083
|
+
found.push({
|
|
21084
|
+
line,
|
|
21085
|
+
source: { pattern: hit.pattern, from: hit.from },
|
|
21086
|
+
reason: `${hit.from} pattern "${hit.pattern}"`
|
|
21087
|
+
});
|
|
21088
|
+
}
|
|
21089
|
+
return found;
|
|
21090
|
+
}
|
|
21091
|
+
var linesOf = (command, dialect) => {
|
|
21092
|
+
try {
|
|
21093
|
+
return commandLines(command, dialect);
|
|
21094
|
+
} catch {
|
|
21095
|
+
return null;
|
|
21096
|
+
}
|
|
21097
|
+
};
|
|
21098
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/u.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
|
|
21099
|
+
var leaseCommand = (words) => `workit check --heavy -- ${words.map(quoteWord).join(" ")}`;
|
|
21100
|
+
function heavyPlan(command, options) {
|
|
21101
|
+
const dialect = options.dialect ?? "posix";
|
|
21102
|
+
if (dialect !== "posix" || !gitTop(options.cwd))
|
|
21103
|
+
return { kind: "none" };
|
|
21104
|
+
const lines = linesOf(command, dialect);
|
|
21105
|
+
if (!lines)
|
|
21106
|
+
return { kind: "none" };
|
|
21107
|
+
const heavy = heavyLines(lines, options);
|
|
21108
|
+
if (!heavy.length)
|
|
21109
|
+
return { kind: "none" };
|
|
21110
|
+
const simple = simpleCommand(command, dialect);
|
|
21111
|
+
const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
|
|
21112
|
+
const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
|
|
21113
|
+
words: match.line,
|
|
21114
|
+
text: match.line.join(" ")
|
|
21115
|
+
}));
|
|
21116
|
+
if (options.rules !== null)
|
|
21117
|
+
for (const { words, text } of judged) {
|
|
21118
|
+
const verdict = wrapAllowed(options.rules, words, text, { bypass: options.bypass });
|
|
21119
|
+
if (!verdict.ok)
|
|
21120
|
+
return {
|
|
21121
|
+
kind: "passthrough",
|
|
21122
|
+
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.`
|
|
21123
|
+
};
|
|
21124
|
+
}
|
|
21125
|
+
if (single) {
|
|
21126
|
+
const rewrite = leaseRewrite(command);
|
|
21127
|
+
return rewrite ? { kind: "rewrite", command: rewrite, reason: single.reason } : { kind: "none" };
|
|
21128
|
+
}
|
|
21129
|
+
const parts = heavy.map((match) => leaseCommand(match.line));
|
|
21130
|
+
return {
|
|
21131
|
+
kind: "split",
|
|
21132
|
+
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.`,
|
|
21133
|
+
unblock: parts[0] ?? null
|
|
21134
|
+
};
|
|
21135
|
+
}
|
|
21136
|
+
function workitOnPath2(env = process.env) {
|
|
21137
|
+
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
21138
|
+
for (const dir of (env.PATH ?? "").split(path23.delimiter))
|
|
21139
|
+
for (const name of names) {
|
|
21140
|
+
if (!dir)
|
|
21141
|
+
continue;
|
|
21142
|
+
try {
|
|
21143
|
+
fs18.accessSync(path23.join(dir, name), fs18.constants.X_OK);
|
|
21144
|
+
return true;
|
|
21145
|
+
} catch {}
|
|
21146
|
+
}
|
|
21147
|
+
return false;
|
|
21148
|
+
}
|
|
21149
|
+
var ASSIGNMENTS = /^\s*((?:[A-Za-z_]\w*=(?:'[^']*'|"(?:[^"\\]|\\.)*"|[^\s'"])*\s+)*)/u;
|
|
21150
|
+
function leaseRewrite(command) {
|
|
21151
|
+
const text = command.trim();
|
|
21152
|
+
const prefix = ASSIGNMENTS.exec(text)?.[1] ?? "";
|
|
21153
|
+
const rest = text.slice(prefix.length).trim();
|
|
21154
|
+
if (!rest)
|
|
21155
|
+
return null;
|
|
21156
|
+
return `${prefix}workit check --heavy -- ${rest}`;
|
|
21157
|
+
}
|
|
21158
|
+
|
|
21159
|
+
// packages/workit-core/src/core/heavy-words.ts
|
|
21160
|
+
import fs19 from "node:fs";
|
|
21161
|
+
import os8 from "node:os";
|
|
21162
|
+
import path24 from "node:path";
|
|
21163
|
+
var HEAVY_WORDS = new Set(("npm npx pnpm pnpx yarn bun bunx deno corepack make gmake docker podman nerdctl " + "python python3 py uv poetry pdm pipenv hatch rye bundle rake nx " + "jest vitest mocha ava tap karma jasmine nyc c8 tsc vue-tsc svelte-check eslint stylelint " + "tslint webpack webpack-cli rollup parcel tsup vite next nuxt nuxi astro ng react-scripts " + "playwright cypress wdio testcafe turbo lerna sonar-scanner sonarless sonarless-mcp go " + "golangci-lint gotestsum staticcheck cargo mvn mvnw gradle gradlew ant sbt bazel bazelisk " + "buck2 dotnet msbuild pytest py.test tox nox mypy pyright pylint coverage maturin pip pip3 " + "rspec rails phpunit pest phpstan psalm composer mix swift xcodebuild flutter dart pod cmake " + "ninja meson ctest scons docker-compose buildah earthly").split(" "));
|
|
21164
|
+
var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
|
|
21165
|
+
var stateDir2 = (env) => {
|
|
21166
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
21167
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
21168
|
+
if (env.XDG_STATE_HOME)
|
|
21169
|
+
return path24.join(env.XDG_STATE_HOME, "workit");
|
|
21170
|
+
const home = env.HOME || os8.homedir();
|
|
21171
|
+
if (process.platform === "darwin")
|
|
21172
|
+
return path24.join(home, "Library", "Application Support", "workit");
|
|
21173
|
+
if (process.platform === "win32")
|
|
21174
|
+
return path24.join(env.LOCALAPPDATA ?? path24.join(home, "AppData", "Local"), "workit");
|
|
21175
|
+
return path24.join(home, ".local", "state", "workit");
|
|
21176
|
+
};
|
|
21177
|
+
var readJson2 = (file) => {
|
|
21178
|
+
try {
|
|
21179
|
+
return JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
21180
|
+
} catch {
|
|
21181
|
+
return;
|
|
21182
|
+
}
|
|
21183
|
+
};
|
|
21184
|
+
var patternWords = (cwd, env) => {
|
|
21185
|
+
const patterns = [];
|
|
21186
|
+
const machine = readJson2(path24.join(stateDir2(env), "heavy-commands.json"));
|
|
21187
|
+
if (Array.isArray(machine?.patterns))
|
|
21188
|
+
patterns.push(...machine.patterns);
|
|
21189
|
+
for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
|
|
21190
|
+
if (fs19.existsSync(path24.join(dir, ".git"))) {
|
|
21191
|
+
patterns.push(...repoHeavyCommands(dir));
|
|
21192
|
+
break;
|
|
21193
|
+
}
|
|
21194
|
+
if (path24.dirname(dir) === dir)
|
|
21195
|
+
break;
|
|
21196
|
+
}
|
|
21197
|
+
const words = new Set;
|
|
21198
|
+
for (const pattern of patterns) {
|
|
21199
|
+
if (typeof pattern !== "string")
|
|
21200
|
+
continue;
|
|
21201
|
+
const first = wordsOf(pattern)[0];
|
|
21202
|
+
if (!first || first.includes("*"))
|
|
21203
|
+
return null;
|
|
21204
|
+
words.add(first);
|
|
21205
|
+
}
|
|
21206
|
+
return words;
|
|
21207
|
+
};
|
|
21208
|
+
function maybeHeavy(command, cwd, env = process.env) {
|
|
21209
|
+
try {
|
|
21210
|
+
const words = wordsOf(command);
|
|
21211
|
+
if (words.some((word) => HEAVY_WORDS.has(word) || /^python[\d.]*$/u.test(word)))
|
|
21212
|
+
return true;
|
|
21213
|
+
const extra = patternWords(cwd, env);
|
|
21214
|
+
return extra === null || words.some((word) => extra.has(word));
|
|
21215
|
+
} catch {
|
|
21216
|
+
return true;
|
|
21217
|
+
}
|
|
21218
|
+
}
|
|
21219
|
+
|
|
21220
|
+
// packages/workit-core/src/hooks/heavy-gate.ts
|
|
21221
|
+
var rulesFor = (host, cwd, env) => {
|
|
21222
|
+
switch (host) {
|
|
21223
|
+
case "claude_code":
|
|
21224
|
+
return claudeRules(cwd, env);
|
|
21225
|
+
case "opencode":
|
|
21226
|
+
return opencodeRules(cwd, env);
|
|
21227
|
+
case "codex_cli":
|
|
21228
|
+
case "codex_desktop":
|
|
21229
|
+
return codexRules(cwd, env);
|
|
21230
|
+
case "cursor":
|
|
21231
|
+
return cursorRules(cwd, env);
|
|
21232
|
+
case "pi":
|
|
21233
|
+
return null;
|
|
21234
|
+
}
|
|
21235
|
+
};
|
|
21236
|
+
function heavyGate(cwd, command, options) {
|
|
21237
|
+
const env = options.env ?? process.env;
|
|
21238
|
+
try {
|
|
21239
|
+
if (env.WORKIT_HEAVY_GATE === "0" || !maybeHeavy(command, cwd, env) || !workitOnPath2(env))
|
|
21240
|
+
return { kind: "none" };
|
|
21241
|
+
return heavyPlan(command, {
|
|
21242
|
+
cwd,
|
|
21243
|
+
env,
|
|
21244
|
+
dialect: options.dialect ?? "posix",
|
|
21245
|
+
rules: rulesFor(options.host, cwd, env),
|
|
21246
|
+
bypass: options.permissionMode === "bypassPermissions"
|
|
21247
|
+
});
|
|
21248
|
+
} catch {
|
|
21249
|
+
return { kind: "none" };
|
|
21250
|
+
}
|
|
21251
|
+
}
|
|
21252
|
+
var 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.`;
|
|
21253
|
+
var heavyDenial = (original, plan) => ({
|
|
21254
|
+
kind: "deny",
|
|
21255
|
+
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}`,
|
|
21256
|
+
unblock: plan.command
|
|
21257
|
+
});
|
|
21258
|
+
var splitDenial = (plan) => ({
|
|
21259
|
+
kind: "deny",
|
|
21260
|
+
reason: plan.reason,
|
|
21261
|
+
unblock: plan.unblock
|
|
21262
|
+
});
|
|
21263
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
21264
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
21265
|
+
import os10 from "node:os";
|
|
21266
|
+
import path26 from "node:path";
|
|
20384
21267
|
|
|
20385
21268
|
// packages/workit-core/src/core/live-session.ts
|
|
20386
21269
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
20387
21270
|
import { createHash as createHash15 } from "node:crypto";
|
|
20388
|
-
import
|
|
20389
|
-
import
|
|
20390
|
-
import
|
|
21271
|
+
import fs20 from "node:fs";
|
|
21272
|
+
import os9 from "node:os";
|
|
21273
|
+
import path25 from "node:path";
|
|
20391
21274
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
20392
21275
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
20393
21276
|
var liveProbe = {
|
|
@@ -20408,9 +21291,9 @@ var processAlive = (pid) => {
|
|
|
20408
21291
|
var processInfo = (pid) => {
|
|
20409
21292
|
try {
|
|
20410
21293
|
if (liveProbe.platform === "linux") {
|
|
20411
|
-
const stat =
|
|
21294
|
+
const stat = fs20.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
20412
21295
|
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
20413
|
-
const command =
|
|
21296
|
+
const command = fs20.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
20414
21297
|
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
20415
21298
|
}
|
|
20416
21299
|
if (liveProbe.platform === "win32")
|
|
@@ -20461,35 +21344,35 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
20461
21344
|
};
|
|
20462
21345
|
};
|
|
20463
21346
|
var checkoutOf = (target) => {
|
|
20464
|
-
let dir =
|
|
20465
|
-
while (!
|
|
20466
|
-
dir =
|
|
20467
|
-
if (
|
|
20468
|
-
dir =
|
|
20469
|
-
const parts = dir.split(
|
|
21347
|
+
let dir = path25.resolve(target);
|
|
21348
|
+
while (!fs20.existsSync(dir) && path25.dirname(dir) !== dir)
|
|
21349
|
+
dir = path25.dirname(dir);
|
|
21350
|
+
if (fs20.existsSync(dir) && !fs20.statSync(dir).isDirectory())
|
|
21351
|
+
dir = path25.dirname(dir);
|
|
21352
|
+
const parts = dir.split(path25.sep);
|
|
20470
21353
|
const dotGit = parts.lastIndexOf(".git");
|
|
20471
21354
|
if (dotGit > 0)
|
|
20472
|
-
dir = parts.slice(0, dotGit).join(
|
|
21355
|
+
dir = parts.slice(0, dotGit).join(path25.sep) || path25.sep;
|
|
20473
21356
|
const location = resolveStore(dir);
|
|
20474
21357
|
if (location instanceof Error || !location.shared || !location.top)
|
|
20475
21358
|
return null;
|
|
20476
21359
|
let top = location.top;
|
|
20477
21360
|
try {
|
|
20478
|
-
top =
|
|
21361
|
+
top = fs20.realpathSync.native(top);
|
|
20479
21362
|
} catch {}
|
|
20480
21363
|
return top;
|
|
20481
21364
|
};
|
|
20482
|
-
var markerFile2 = (top) =>
|
|
21365
|
+
var markerFile2 = (top) => path25.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
20483
21366
|
var checkoutKey = (top, platform = process.platform) => {
|
|
20484
|
-
let key =
|
|
21367
|
+
let key = path25.resolve(top);
|
|
20485
21368
|
try {
|
|
20486
|
-
key =
|
|
21369
|
+
key = fs20.realpathSync.native(key);
|
|
20487
21370
|
} catch {}
|
|
20488
21371
|
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
20489
21372
|
};
|
|
20490
21373
|
var readMarker = (file) => {
|
|
20491
21374
|
try {
|
|
20492
|
-
const value = JSON.parse(
|
|
21375
|
+
const value = JSON.parse(fs20.readFileSync(file, "utf8"));
|
|
20493
21376
|
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
20494
21377
|
return null;
|
|
20495
21378
|
return {
|
|
@@ -20512,7 +21395,7 @@ var isLive = (marker, deps) => {
|
|
|
20512
21395
|
const age = ageOf2(marker, deps.now ?? new Date);
|
|
20513
21396
|
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
20514
21397
|
return false;
|
|
20515
|
-
if (marker.machine !== (deps.machine ??
|
|
21398
|
+
if (marker.machine !== (deps.machine ?? os9.hostname()) || marker.pid === null)
|
|
20516
21399
|
return true;
|
|
20517
21400
|
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
20518
21401
|
return false;
|
|
@@ -20541,7 +21424,7 @@ var liveConflict = (target, owner, deps = {}) => {
|
|
|
20541
21424
|
const marker = readMarker(file);
|
|
20542
21425
|
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
20543
21426
|
return null;
|
|
20544
|
-
if (sameOwner(marker, owner, deps.machine ??
|
|
21427
|
+
if (sameOwner(marker, owner, deps.machine ?? os9.hostname()))
|
|
20545
21428
|
return null;
|
|
20546
21429
|
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
20547
21430
|
} catch {
|
|
@@ -20555,7 +21438,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
20555
21438
|
return;
|
|
20556
21439
|
const file = markerFile2(checkout);
|
|
20557
21440
|
const now = deps.now ?? new Date;
|
|
20558
|
-
const machine = deps.machine ??
|
|
21441
|
+
const machine = deps.machine ?? os9.hostname();
|
|
20559
21442
|
const current = readMarker(file);
|
|
20560
21443
|
if (current) {
|
|
20561
21444
|
const age = ageOf2(current, now);
|
|
@@ -20576,10 +21459,10 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
20576
21459
|
checkout,
|
|
20577
21460
|
heartbeatAt: now.toISOString()
|
|
20578
21461
|
};
|
|
20579
|
-
|
|
21462
|
+
fs20.mkdirSync(path25.dirname(file), { recursive: true });
|
|
20580
21463
|
const temp = `${file}.${process.pid}.tmp`;
|
|
20581
|
-
|
|
20582
|
-
|
|
21464
|
+
fs20.writeFileSync(temp, JSON.stringify(marker));
|
|
21465
|
+
fs20.renameSync(temp, file);
|
|
20583
21466
|
} catch {}
|
|
20584
21467
|
};
|
|
20585
21468
|
|
|
@@ -20588,27 +21471,42 @@ var NONE3 = { kind: "none" };
|
|
|
20588
21471
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
20589
21472
|
var canonical2 = (file) => {
|
|
20590
21473
|
let rest = "";
|
|
20591
|
-
for (let dir =
|
|
21474
|
+
for (let dir = path26.resolve(file);; dir = path26.dirname(dir)) {
|
|
20592
21475
|
try {
|
|
20593
|
-
return
|
|
21476
|
+
return path26.join(realpathSync5(dir), rest);
|
|
20594
21477
|
} catch {
|
|
20595
|
-
if (
|
|
20596
|
-
return
|
|
20597
|
-
rest = rest ?
|
|
21478
|
+
if (path26.dirname(dir) === dir)
|
|
21479
|
+
return path26.resolve(file);
|
|
21480
|
+
rest = rest ? path26.join(path26.basename(dir), rest) : path26.basename(dir);
|
|
20598
21481
|
}
|
|
20599
21482
|
}
|
|
20600
21483
|
};
|
|
20601
21484
|
var exempt = (cwd, root, file, plans) => {
|
|
20602
21485
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
20603
|
-
const relative =
|
|
20604
|
-
if (relative.startsWith("..") ||
|
|
21486
|
+
const relative = path26.relative(fold(canonical2(root)), fold(canonical2(path26.resolve(cwd, file))));
|
|
21487
|
+
if (relative.startsWith("..") || path26.isAbsolute(relative))
|
|
20605
21488
|
return true;
|
|
20606
|
-
const posix = relative.split(
|
|
21489
|
+
const posix = relative.split(path26.sep).join("/");
|
|
20607
21490
|
const segments = posix.split("/");
|
|
20608
21491
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
20609
21492
|
};
|
|
20610
21493
|
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
20611
21494
|
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
21495
|
+
var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
21496
|
+
var wrappedScript = (input) => {
|
|
21497
|
+
let words = input;
|
|
21498
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
21499
|
+
words = words.slice(1);
|
|
21500
|
+
const tail = workitCheckTail(words);
|
|
21501
|
+
if (!tail)
|
|
21502
|
+
return null;
|
|
21503
|
+
if ("script" in tail)
|
|
21504
|
+
return tail.script;
|
|
21505
|
+
const [shell, flag, script] = tail.argv;
|
|
21506
|
+
if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
|
|
21507
|
+
return script ?? null;
|
|
21508
|
+
return tail.argv.map(quoteWord2).join(" ");
|
|
21509
|
+
};
|
|
20612
21510
|
var editsOf = (input) => {
|
|
20613
21511
|
let words = input;
|
|
20614
21512
|
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
@@ -20685,10 +21583,18 @@ var editsOf = (input) => {
|
|
|
20685
21583
|
return [];
|
|
20686
21584
|
}
|
|
20687
21585
|
};
|
|
20688
|
-
var shellWrites = (command) => {
|
|
21586
|
+
var shellWrites = (command, depth = 0) => {
|
|
20689
21587
|
const targets = [];
|
|
20690
21588
|
let unknown = false;
|
|
20691
21589
|
for (const segment of segmentsOf(command)) {
|
|
21590
|
+
const inner = depth < 4 ? wrappedScript(segment.words) : null;
|
|
21591
|
+
if (inner !== null) {
|
|
21592
|
+
const nested = shellWrites(inner, depth + 1);
|
|
21593
|
+
if (nested.writes && nested.targets === null)
|
|
21594
|
+
unknown = true;
|
|
21595
|
+
else if (nested.targets)
|
|
21596
|
+
targets.push(...nested.targets);
|
|
21597
|
+
}
|
|
20692
21598
|
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
20693
21599
|
const edits = editsOf(segment.words);
|
|
20694
21600
|
if (edits === null)
|
|
@@ -20701,7 +21607,7 @@ var shellWrites = (command) => {
|
|
|
20701
21607
|
return { writes: true, targets: unknown ? null : targets };
|
|
20702
21608
|
};
|
|
20703
21609
|
var cache2 = new Map;
|
|
20704
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
21610
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path26.resolve(root, file)) ?? "-").join("\x00");
|
|
20705
21611
|
var gateFor = (cwd) => {
|
|
20706
21612
|
const store = new TaskStore(cwd);
|
|
20707
21613
|
const listed = store.listTaskIndex();
|
|
@@ -20780,7 +21686,7 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
20780
21686
|
return [];
|
|
20781
21687
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
20782
21688
|
return [];
|
|
20783
|
-
return [
|
|
21689
|
+
return [path26.resolve(cwd, invocation.dir ?? ".")];
|
|
20784
21690
|
});
|
|
20785
21691
|
};
|
|
20786
21692
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
@@ -20790,15 +21696,15 @@ var cdInto = (dir, words) => {
|
|
|
20790
21696
|
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
20791
21697
|
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
20792
21698
|
return;
|
|
20793
|
-
return target === undefined ?
|
|
21699
|
+
return target === undefined ? os10.homedir() : path26.resolve(dir, target);
|
|
20794
21700
|
};
|
|
20795
|
-
var
|
|
20796
|
-
var segmentTargets = (cwd, command, dialect) => {
|
|
21701
|
+
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
21702
|
+
var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
20797
21703
|
const targets = [];
|
|
20798
21704
|
let dir = cwd;
|
|
20799
21705
|
let stack = [];
|
|
20800
21706
|
const groups = [];
|
|
20801
|
-
const at = (file) =>
|
|
21707
|
+
const at = (file) => path26.isAbsolute(file) ? file : dir === undefined ? null : path26.resolve(dir, file);
|
|
20802
21708
|
for (const segment of segmentsOf(command, dialect)) {
|
|
20803
21709
|
if (segment.group === "open") {
|
|
20804
21710
|
groups.push({ dir, stack: [...stack] });
|
|
@@ -20826,6 +21732,9 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
20826
21732
|
dir = stack.length ? stack.pop() : undefined;
|
|
20827
21733
|
continue;
|
|
20828
21734
|
}
|
|
21735
|
+
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
21736
|
+
if (inner !== null && dir !== undefined)
|
|
21737
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
20829
21738
|
for (const file of segment.redirects)
|
|
20830
21739
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
20831
21740
|
const resolved = at(file);
|
|
@@ -20835,15 +21744,15 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
20835
21744
|
const edits = editsOf(segment.words);
|
|
20836
21745
|
if (edits === null) {
|
|
20837
21746
|
if (dir !== undefined)
|
|
20838
|
-
targets.push(
|
|
21747
|
+
targets.push(path26.join(dir, DIR_TARGET));
|
|
20839
21748
|
} else
|
|
20840
21749
|
for (const file of edits) {
|
|
20841
21750
|
const resolved = at(file);
|
|
20842
21751
|
if (resolved)
|
|
20843
21752
|
targets.push(resolved);
|
|
20844
21753
|
}
|
|
20845
|
-
if (dir !== undefined && workitInvocations(segment.words.map(
|
|
20846
|
-
targets.push(
|
|
21754
|
+
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), "")))
|
|
21755
|
+
targets.push(path26.join(dir, DIR_TARGET));
|
|
20847
21756
|
}
|
|
20848
21757
|
return targets;
|
|
20849
21758
|
};
|
|
@@ -20878,7 +21787,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
20878
21787
|
var guarded = (targets) => {
|
|
20879
21788
|
const seen = new Set;
|
|
20880
21789
|
return targets.filter((target) => {
|
|
20881
|
-
const dir =
|
|
21790
|
+
const dir = path26.dirname(target);
|
|
20882
21791
|
if (seen.has(dir))
|
|
20883
21792
|
return false;
|
|
20884
21793
|
seen.add(dir);
|
|
@@ -20899,7 +21808,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
20899
21808
|
for (const target of guarded(targets))
|
|
20900
21809
|
claimLiveSession(target, owner, deps);
|
|
20901
21810
|
};
|
|
20902
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
21811
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path26.resolve(cwd, file)) : [path26.join(cwd, DIR_TARGET)];
|
|
20903
21812
|
function writeGate(cwd, paths, owner, deps) {
|
|
20904
21813
|
if (!owner)
|
|
20905
21814
|
return beforeWriteGate(cwd, paths);
|
|
@@ -20919,7 +21828,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
20919
21828
|
try {
|
|
20920
21829
|
targets = [
|
|
20921
21830
|
...segmentTargets(cwd, command, dialect),
|
|
20922
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
21831
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path26.join(dir, DIR_TARGET))
|
|
20923
21832
|
];
|
|
20924
21833
|
} catch {
|
|
20925
21834
|
targets = [];
|
|
@@ -21353,12 +22262,12 @@ ${line}` };
|
|
|
21353
22262
|
};
|
|
21354
22263
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
21355
22264
|
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
21356
|
-
import
|
|
21357
|
-
var
|
|
22265
|
+
import path27 from "node:path";
|
|
22266
|
+
var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
21358
22267
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
21359
22268
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
21360
22269
|
var existingDirectory = (value) => {
|
|
21361
|
-
if (!nonEmpty2(value) || !
|
|
22270
|
+
if (!nonEmpty2(value) || !path27.isAbsolute(value) || !existsSync7(value))
|
|
21362
22271
|
return null;
|
|
21363
22272
|
try {
|
|
21364
22273
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -21411,7 +22320,7 @@ var isWriteTool = (name) => typeof name === "string" && WRITE_TOOLS.has(name.toL
|
|
|
21411
22320
|
var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
|
|
21412
22321
|
var PATCH_HEADER = /^[ \t]*\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm;
|
|
21413
22322
|
var writePaths = (input) => {
|
|
21414
|
-
if (!
|
|
22323
|
+
if (!isRecord6(input))
|
|
21415
22324
|
return [];
|
|
21416
22325
|
const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
|
|
21417
22326
|
for (const value of Object.values(input))
|
|
@@ -21506,10 +22415,32 @@ function handleHook(input, deps) {
|
|
|
21506
22415
|
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
|
|
21507
22416
|
if (gate.kind === "deny")
|
|
21508
22417
|
return gate;
|
|
22418
|
+
const plan = heavyGate(input.cwd, event.command, {
|
|
22419
|
+
host: input.host,
|
|
22420
|
+
dialect: event.dialect,
|
|
22421
|
+
permissionMode: input.permissionMode
|
|
22422
|
+
});
|
|
22423
|
+
if (plan.kind === "split" && canDeny)
|
|
22424
|
+
return splitDenial(plan);
|
|
22425
|
+
const rewrites = usable2(descriptor.shellPolicy.rewrite);
|
|
22426
|
+
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
22427
|
+
return heavyDenial(event.command, plan);
|
|
22428
|
+
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
22429
|
+
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
21509
22430
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
22431
|
+
const rewritten = (text) => lease ? {
|
|
22432
|
+
kind: "rewrite",
|
|
22433
|
+
command: lease.command,
|
|
22434
|
+
input: event.toolInput ? { ...event.toolInput, command: lease.command } : null,
|
|
22435
|
+
text
|
|
22436
|
+
} : NONE5;
|
|
21510
22437
|
if (descriptor.transport !== "hook-process")
|
|
21511
|
-
return
|
|
21512
|
-
|
|
22438
|
+
return rewritten(null);
|
|
22439
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
|
|
22440
|
+
if (!lease)
|
|
22441
|
+
return nudged;
|
|
22442
|
+
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
22443
|
+
`));
|
|
21513
22444
|
}
|
|
21514
22445
|
case "shell.post":
|
|
21515
22446
|
if (usable2(descriptor.events["shell.post"].support))
|
|
@@ -21587,7 +22518,12 @@ var CLAUDE_CODE_DESCRIPTOR = {
|
|
|
21587
22518
|
"compact.pre": { support: "none", native: null },
|
|
21588
22519
|
stop: { support: "native", native: "Stop" }
|
|
21589
22520
|
},
|
|
21590
|
-
shellPolicy: {
|
|
22521
|
+
shellPolicy: {
|
|
22522
|
+
deny: "native",
|
|
22523
|
+
channel: "permissionDecision",
|
|
22524
|
+
failClosed: false,
|
|
22525
|
+
rewrite: "native"
|
|
22526
|
+
},
|
|
21591
22527
|
context: {
|
|
21592
22528
|
sessionStart: "native",
|
|
21593
22529
|
perTurn: "native",
|
|
@@ -21669,7 +22605,7 @@ var EVENTS = {
|
|
|
21669
22605
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
21670
22606
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
21671
22607
|
var dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix";
|
|
21672
|
-
var toolCommand = (value) =>
|
|
22608
|
+
var toolCommand = (value) => isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
21673
22609
|
var eventOf = (name, value) => {
|
|
21674
22610
|
const toolUseId = optionalText(value.tool_use_id);
|
|
21675
22611
|
switch (name) {
|
|
@@ -21697,19 +22633,28 @@ var eventOf = (name, value) => {
|
|
|
21697
22633
|
kind: "tool.pre",
|
|
21698
22634
|
tool: value.tool_name,
|
|
21699
22635
|
toolUseId,
|
|
21700
|
-
skill:
|
|
22636
|
+
skill: isRecord6(value.tool_input) ? optionalText(value.tool_input.skill) : null
|
|
21701
22637
|
}
|
|
21702
22638
|
};
|
|
21703
22639
|
if (!SHELL_TOOLS.has(value.tool_name))
|
|
21704
22640
|
return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
|
|
21705
22641
|
const command = toolCommand(value);
|
|
21706
|
-
return command ? {
|
|
22642
|
+
return command ? {
|
|
22643
|
+
ok: true,
|
|
22644
|
+
event: {
|
|
22645
|
+
kind: "shell.pre",
|
|
22646
|
+
command,
|
|
22647
|
+
toolUseId,
|
|
22648
|
+
dialect: dialectOf2(value),
|
|
22649
|
+
toolInput: value.tool_input
|
|
22650
|
+
}
|
|
22651
|
+
} : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
|
|
21707
22652
|
}
|
|
21708
22653
|
case "PostToolUse": {
|
|
21709
22654
|
const command = toolCommand(value);
|
|
21710
22655
|
if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
|
|
21711
22656
|
return { ok: false, error: "only shell PostToolUse is mapped" };
|
|
21712
|
-
const response =
|
|
22657
|
+
const response = isRecord6(value.tool_response) ? value.tool_response : {};
|
|
21713
22658
|
return {
|
|
21714
22659
|
ok: true,
|
|
21715
22660
|
event: {
|
|
@@ -21767,6 +22712,17 @@ var CONTEXT_EVENTS = new Set([
|
|
|
21767
22712
|
"PostToolUse"
|
|
21768
22713
|
]);
|
|
21769
22714
|
var render = (decision, native) => {
|
|
22715
|
+
if (decision.kind === "rewrite" && native === "PreToolUse" && decision.input)
|
|
22716
|
+
return {
|
|
22717
|
+
json: {
|
|
22718
|
+
hookSpecificOutput: {
|
|
22719
|
+
hookEventName: "PreToolUse",
|
|
22720
|
+
updatedInput: decision.input,
|
|
22721
|
+
...decision.text ? { additionalContext: decision.text } : {}
|
|
22722
|
+
}
|
|
22723
|
+
},
|
|
22724
|
+
exitCode: 0
|
|
22725
|
+
};
|
|
21770
22726
|
if (decision.kind === "deny" && native === "PreToolUse")
|
|
21771
22727
|
return {
|
|
21772
22728
|
json: {
|
|
@@ -21792,14 +22748,14 @@ var render = (decision, native) => {
|
|
|
21792
22748
|
var claudeCodeAdapter = {
|
|
21793
22749
|
descriptor: CLAUDE_CODE_DESCRIPTOR,
|
|
21794
22750
|
parse(raw) {
|
|
21795
|
-
const native =
|
|
22751
|
+
const native = isRecord6(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
21796
22752
|
const fail = (error) => ({
|
|
21797
22753
|
ok: false,
|
|
21798
22754
|
error,
|
|
21799
22755
|
native,
|
|
21800
22756
|
event: native ? EVENTS[native] : null
|
|
21801
22757
|
});
|
|
21802
|
-
if (!
|
|
22758
|
+
if (!isRecord6(raw) || !native)
|
|
21803
22759
|
return fail("hook_event_name is required");
|
|
21804
22760
|
if (!nonEmpty2(raw.session_id))
|
|
21805
22761
|
return fail("session_id is required");
|
|
@@ -21853,7 +22809,12 @@ var CODEX_DESCRIPTOR = {
|
|
|
21853
22809
|
"compact.pre": { support: "none", native: null },
|
|
21854
22810
|
stop: { support: "native", native: "Stop" }
|
|
21855
22811
|
},
|
|
21856
|
-
shellPolicy: {
|
|
22812
|
+
shellPolicy: {
|
|
22813
|
+
deny: "native",
|
|
22814
|
+
channel: "permissionDecision",
|
|
22815
|
+
failClosed: false,
|
|
22816
|
+
rewrite: "none"
|
|
22817
|
+
},
|
|
21857
22818
|
context: {
|
|
21858
22819
|
sessionStart: "native",
|
|
21859
22820
|
perTurn: "native",
|
|
@@ -21945,7 +22906,7 @@ var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
|
21945
22906
|
var isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase());
|
|
21946
22907
|
var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
21947
22908
|
var parseCodexHookInput = (value) => {
|
|
21948
|
-
if (!
|
|
22909
|
+
if (!isRecord6(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
|
|
21949
22910
|
return { ok: false, error: "hook_event_name is required" };
|
|
21950
22911
|
const event = value.hook_event_name;
|
|
21951
22912
|
const cwd = existingDirectory(value.cwd);
|
|
@@ -21954,7 +22915,7 @@ var parseCodexHookInput = (value) => {
|
|
|
21954
22915
|
const toolEvent = event === "PreToolUse" || event === "PostToolUse";
|
|
21955
22916
|
if (toolEvent && !nonEmpty2(value.tool_name))
|
|
21956
22917
|
return { ok: false, error: "tool_name is required" };
|
|
21957
|
-
const command =
|
|
22918
|
+
const command = isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
21958
22919
|
if (toolEvent && isShellTool(value.tool_name) && !command)
|
|
21959
22920
|
return { ok: false, error: "tool_input.command is required for shell tools" };
|
|
21960
22921
|
if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
|
|
@@ -22068,7 +23029,7 @@ var codexAdapter = {
|
|
|
22068
23029
|
const host = detectCodexSurface(env);
|
|
22069
23030
|
const parsed = parseCodexHookInput(raw);
|
|
22070
23031
|
if (!parsed.ok) {
|
|
22071
|
-
const native =
|
|
23032
|
+
const native = isRecord6(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
22072
23033
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
|
|
22073
23034
|
}
|
|
22074
23035
|
const input = parsed.data;
|
|
@@ -22095,8 +23056,8 @@ var codexAdapter = {
|
|
|
22095
23056
|
};
|
|
22096
23057
|
|
|
22097
23058
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
22098
|
-
import
|
|
22099
|
-
import
|
|
23059
|
+
import fs21 from "node:fs";
|
|
23060
|
+
import path28 from "node:path";
|
|
22100
23061
|
var undocumented = { support: "undocumented", native: null };
|
|
22101
23062
|
var none = { support: "none", native: null };
|
|
22102
23063
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -22118,7 +23079,7 @@ var CURSOR_DESCRIPTOR = {
|
|
|
22118
23079
|
"compact.pre": { support: "partial", native: "preCompact" },
|
|
22119
23080
|
stop: { support: "native", native: "stop" }
|
|
22120
23081
|
},
|
|
22121
|
-
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false },
|
|
23082
|
+
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false, rewrite: "none" },
|
|
22122
23083
|
context: {
|
|
22123
23084
|
sessionStart: "native",
|
|
22124
23085
|
perTurn: "none",
|
|
@@ -22211,7 +23172,7 @@ var EVENTS3 = {
|
|
|
22211
23172
|
stop: "stop"
|
|
22212
23173
|
};
|
|
22213
23174
|
var parseCursorHookInput = (value) => {
|
|
22214
|
-
if (!
|
|
23175
|
+
if (!isRecord6(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
22215
23176
|
return { ok: false, error: "hook_event_name is required" };
|
|
22216
23177
|
const roots = value.workspace_roots;
|
|
22217
23178
|
if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
|
|
@@ -22293,17 +23254,17 @@ var protocolEvent2 = (input) => {
|
|
|
22293
23254
|
};
|
|
22294
23255
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
22295
23256
|
var lastAssistantText = (file) => {
|
|
22296
|
-
if (!file || !
|
|
23257
|
+
if (!file || !path28.isAbsolute(file))
|
|
22297
23258
|
return null;
|
|
22298
23259
|
try {
|
|
22299
|
-
const size =
|
|
23260
|
+
const size = fs21.statSync(file).size;
|
|
22300
23261
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
22301
23262
|
const buffer = Buffer.alloc(size - start);
|
|
22302
|
-
const fd =
|
|
23263
|
+
const fd = fs21.openSync(file, "r");
|
|
22303
23264
|
try {
|
|
22304
|
-
|
|
23265
|
+
fs21.readSync(fd, buffer, 0, buffer.length, start);
|
|
22305
23266
|
} finally {
|
|
22306
|
-
|
|
23267
|
+
fs21.closeSync(fd);
|
|
22307
23268
|
}
|
|
22308
23269
|
const lines = buffer.toString("utf8").split(`
|
|
22309
23270
|
`);
|
|
@@ -22314,14 +23275,14 @@ var lastAssistantText = (file) => {
|
|
|
22314
23275
|
} catch {
|
|
22315
23276
|
continue;
|
|
22316
23277
|
}
|
|
22317
|
-
if (!
|
|
23278
|
+
if (!isRecord6(entry) || entry.role !== "assistant")
|
|
22318
23279
|
continue;
|
|
22319
|
-
const content =
|
|
23280
|
+
const content = isRecord6(entry.message) ? entry.message.content : entry.content;
|
|
22320
23281
|
if (typeof content === "string")
|
|
22321
23282
|
return content;
|
|
22322
23283
|
if (!Array.isArray(content))
|
|
22323
23284
|
return null;
|
|
22324
|
-
const text = content.filter((part) =>
|
|
23285
|
+
const text = content.filter((part) => isRecord6(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
|
|
22325
23286
|
`);
|
|
22326
23287
|
return text || null;
|
|
22327
23288
|
}
|
|
@@ -22330,7 +23291,7 @@ var lastAssistantText = (file) => {
|
|
|
22330
23291
|
return null;
|
|
22331
23292
|
}
|
|
22332
23293
|
};
|
|
22333
|
-
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
23294
|
+
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path28.sep) ? root : `${root}${path28.sep}`);
|
|
22334
23295
|
var hookCwd = (input) => {
|
|
22335
23296
|
const cwd = existingDirectory(input.cwd);
|
|
22336
23297
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
@@ -22371,7 +23332,7 @@ var cursorAdapter = {
|
|
|
22371
23332
|
parse(raw) {
|
|
22372
23333
|
const parsed = parseCursorHookInput(raw);
|
|
22373
23334
|
if (!parsed.ok) {
|
|
22374
|
-
const native =
|
|
23335
|
+
const native = isRecord6(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
22375
23336
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
|
|
22376
23337
|
}
|
|
22377
23338
|
const input = parsed.data;
|