@brainervirus/workit-pi 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/dist/worker.js +2 -0
- package/dist/workit.js +1008 -63
- package/package.json +1 -1
- 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.js
CHANGED
|
@@ -12430,6 +12430,17 @@ import fs9 from "node:fs";
|
|
|
12430
12430
|
import path9 from "node:path";
|
|
12431
12431
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
12432
12432
|
var CHECKS_FILE = "workit.checks.json";
|
|
12433
|
+
var checksConfigFile = (root) => path9.join(root, CHECKS_FILE);
|
|
12434
|
+
function repoHeavyCommands(root) {
|
|
12435
|
+
try {
|
|
12436
|
+
const value = JSON.parse(fs9.readFileSync(checksConfigFile(root), "utf8"));
|
|
12437
|
+
const resources = isRecord(value) ? value.resources : undefined;
|
|
12438
|
+
const list = isRecord(resources) ? resources.heavyCommands : undefined;
|
|
12439
|
+
return Array.isArray(list) ? list.filter((item) => typeof item === "string") : [];
|
|
12440
|
+
} catch {
|
|
12441
|
+
return [];
|
|
12442
|
+
}
|
|
12443
|
+
}
|
|
12433
12444
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12434
12445
|
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
12435
12446
|
function splitCommand(command) {
|
|
@@ -12542,7 +12553,7 @@ function detectedChecks(root) {
|
|
|
12542
12553
|
var GATES = ["testing", "verification"];
|
|
12543
12554
|
function loadCheckConfig(cwd) {
|
|
12544
12555
|
const root = checkRoot(cwd);
|
|
12545
|
-
const file =
|
|
12556
|
+
const file = checksConfigFile(root);
|
|
12546
12557
|
const none = (error = null) => ({
|
|
12547
12558
|
source: error ? CHECKS_FILE : "none",
|
|
12548
12559
|
root,
|
|
@@ -13184,6 +13195,7 @@ var workspaceConfigSchema = object({
|
|
|
13184
13195
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
13185
13196
|
defaultEndpoint: unknown().optional(),
|
|
13186
13197
|
verification: _enum(["self", "independent"]).optional(),
|
|
13198
|
+
delegation: unknown().optional(),
|
|
13187
13199
|
trailers: boolean2().optional(),
|
|
13188
13200
|
liveSessionGuard: boolean2().optional(),
|
|
13189
13201
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
@@ -17468,7 +17480,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
17468
17480
|
return found;
|
|
17469
17481
|
}
|
|
17470
17482
|
var RECENT = new Map;
|
|
17471
|
-
function
|
|
17483
|
+
function commandLines(command, dialect = "posix") {
|
|
17484
|
+
const lines = [];
|
|
17485
|
+
try {
|
|
17486
|
+
walk2(command, dialect, 0, lines);
|
|
17487
|
+
} catch (error) {
|
|
17488
|
+
throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
|
|
17489
|
+
}
|
|
17490
|
+
return lines;
|
|
17491
|
+
}
|
|
17492
|
+
function walk2(command, dialect, depth, seen) {
|
|
17472
17493
|
const found = [];
|
|
17473
17494
|
let dir = null;
|
|
17474
17495
|
const groups = [];
|
|
@@ -17479,7 +17500,8 @@ function walk2(command, dialect, depth) {
|
|
|
17479
17500
|
const nested = (script, inner) => {
|
|
17480
17501
|
if (!script || depth >= MAX_DEPTH)
|
|
17481
17502
|
return;
|
|
17482
|
-
|
|
17503
|
+
const inside = seen ? walk2(script, inner, depth + 1, seen) : rawInvocations(script, inner, depth + 1);
|
|
17504
|
+
for (const invocation of inside)
|
|
17483
17505
|
found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
|
|
17484
17506
|
};
|
|
17485
17507
|
const visit = (input, stdin, hops) => {
|
|
@@ -17491,11 +17513,17 @@ function walk2(command, dialect, depth) {
|
|
|
17491
17513
|
const name = commandName(words[0]);
|
|
17492
17514
|
if (!name)
|
|
17493
17515
|
return;
|
|
17516
|
+
seen?.push(words);
|
|
17517
|
+
if (name === "-c" || name === "--command")
|
|
17518
|
+
return nested(words[1], "posix");
|
|
17494
17519
|
const runs = (lines) => {
|
|
17495
17520
|
if (hops < MAX_DEPTH)
|
|
17496
17521
|
for (const next of lines)
|
|
17497
17522
|
visit(next, undefined, hops + 1);
|
|
17498
17523
|
};
|
|
17524
|
+
const wrapped = seen ? null : workitCheckTail(words);
|
|
17525
|
+
if (wrapped)
|
|
17526
|
+
return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
|
|
17499
17527
|
if (CD.has(name)) {
|
|
17500
17528
|
const target = cdTarget(words);
|
|
17501
17529
|
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
@@ -17586,6 +17614,51 @@ var LAUNCHERS = new Set([
|
|
|
17586
17614
|
"command"
|
|
17587
17615
|
]);
|
|
17588
17616
|
var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
17617
|
+
var WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
|
|
17618
|
+
var PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
|
|
17619
|
+
function workitCheckTail(words) {
|
|
17620
|
+
let at = 0;
|
|
17621
|
+
if (PACKAGE_RUNNERS.has(commandName(words[0]))) {
|
|
17622
|
+
at = words.findIndex((word, index) => index > 0 && !word.startsWith("-"));
|
|
17623
|
+
if (at < 0 || !WORKIT_PACKAGE.test(words[at]))
|
|
17624
|
+
return null;
|
|
17625
|
+
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
17626
|
+
return null;
|
|
17627
|
+
const args = words.slice(at + 1);
|
|
17628
|
+
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
17629
|
+
return null;
|
|
17630
|
+
const split = args.indexOf("--");
|
|
17631
|
+
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
17632
|
+
if (!tail.length)
|
|
17633
|
+
return null;
|
|
17634
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
17635
|
+
}
|
|
17636
|
+
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
17637
|
+
let segments;
|
|
17638
|
+
try {
|
|
17639
|
+
segments = segmentsOf(command, dialect);
|
|
17640
|
+
} catch {
|
|
17641
|
+
return null;
|
|
17642
|
+
}
|
|
17643
|
+
if (segments.length !== 1)
|
|
17644
|
+
return null;
|
|
17645
|
+
const [segment] = segments;
|
|
17646
|
+
if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
|
|
17647
|
+
return null;
|
|
17648
|
+
const { words } = stripPrefixes(segment.words);
|
|
17649
|
+
const name = commandName(words[0]);
|
|
17650
|
+
if (!name || depth >= MAX_DEPTH)
|
|
17651
|
+
return null;
|
|
17652
|
+
if (SHELLS.has(name)) {
|
|
17653
|
+
const script = shellScript(words, undefined);
|
|
17654
|
+
return script ? simpleCommand(script, "posix", depth + 1) : null;
|
|
17655
|
+
}
|
|
17656
|
+
if (name === "-c" || name === "--command")
|
|
17657
|
+
return words[1] ? simpleCommand(words[1], "posix", depth + 1) : null;
|
|
17658
|
+
if (EVALS.has(name) || POWERSHELLS.has(name) || ["xargs", "find", "parallel", "script", "cmd", "source", "."].includes(name))
|
|
17659
|
+
return null;
|
|
17660
|
+
return words;
|
|
17661
|
+
}
|
|
17589
17662
|
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
17590
17663
|
const found = [];
|
|
17591
17664
|
for (const { words } of segmentsOf(command, dialect)) {
|
|
@@ -21964,17 +22037,827 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21964
22037
|
}
|
|
21965
22038
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
21966
22039
|
};
|
|
21967
|
-
// packages/workit-core/src/
|
|
21968
|
-
import
|
|
22040
|
+
// packages/workit-core/src/core/heavy-rules.ts
|
|
22041
|
+
import fs19 from "node:fs";
|
|
22042
|
+
import os6 from "node:os";
|
|
22043
|
+
import path26 from "node:path";
|
|
22044
|
+
var EMPTY = () => ({ deny: [], allow: [] });
|
|
22045
|
+
var readText2 = (file) => {
|
|
22046
|
+
try {
|
|
22047
|
+
return fs19.readFileSync(file, "utf8");
|
|
22048
|
+
} catch (error) {
|
|
22049
|
+
if (error.code === "ENOENT")
|
|
22050
|
+
return null;
|
|
22051
|
+
throw error;
|
|
22052
|
+
}
|
|
22053
|
+
};
|
|
22054
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22055
|
+
function parseJsonc(text) {
|
|
22056
|
+
let out = "";
|
|
22057
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
22058
|
+
const char = text[index];
|
|
22059
|
+
if (char === '"') {
|
|
22060
|
+
const end = /"(?:[^"\\]|\\.)*"/y;
|
|
22061
|
+
end.lastIndex = index;
|
|
22062
|
+
const match = end.exec(text);
|
|
22063
|
+
if (!match)
|
|
22064
|
+
throw new Error("unterminated string");
|
|
22065
|
+
out += match[0];
|
|
22066
|
+
index += match[0].length - 1;
|
|
22067
|
+
} else if (char === "/" && text[index + 1] === "/") {
|
|
22068
|
+
while (index < text.length && text[index] !== `
|
|
22069
|
+
`)
|
|
22070
|
+
index += 1;
|
|
22071
|
+
out += `
|
|
22072
|
+
`;
|
|
22073
|
+
} else if (char === "/" && text[index + 1] === "*") {
|
|
22074
|
+
const close = text.indexOf("*/", index + 2);
|
|
22075
|
+
if (close < 0)
|
|
22076
|
+
throw new Error("unterminated comment");
|
|
22077
|
+
index = close + 1;
|
|
22078
|
+
} else
|
|
22079
|
+
out += char;
|
|
22080
|
+
}
|
|
22081
|
+
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
22082
|
+
}
|
|
22083
|
+
function gitTop(cwd) {
|
|
22084
|
+
for (let dir = path26.resolve(cwd);; dir = path26.dirname(dir)) {
|
|
22085
|
+
if (fs19.existsSync(path26.join(dir, ".git")))
|
|
22086
|
+
return dir;
|
|
22087
|
+
if (path26.dirname(dir) === dir)
|
|
22088
|
+
return null;
|
|
22089
|
+
}
|
|
22090
|
+
}
|
|
22091
|
+
var projectDirs = (cwd) => {
|
|
22092
|
+
const top = gitTop(cwd);
|
|
22093
|
+
const dirs = [];
|
|
22094
|
+
for (let dir = path26.resolve(cwd);; dir = path26.dirname(dir)) {
|
|
22095
|
+
dirs.push(dir);
|
|
22096
|
+
if (!top || dir === top || path26.dirname(dir) === dir)
|
|
22097
|
+
return dirs;
|
|
22098
|
+
}
|
|
22099
|
+
};
|
|
22100
|
+
var homeOf = (env) => env.HOME || os6.homedir();
|
|
22101
|
+
var CLAUDE_MANAGED = {
|
|
22102
|
+
linux: "/etc/claude-code/managed-settings.json",
|
|
22103
|
+
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
22104
|
+
win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
|
|
22105
|
+
};
|
|
22106
|
+
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
22107
|
+
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
22108
|
+
const user = env.CLAUDE_CONFIG_DIR || path26.join(homeOf(env), ".claude");
|
|
22109
|
+
return [
|
|
22110
|
+
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
22111
|
+
path26.join(user, "settings.json"),
|
|
22112
|
+
path26.join(project, ".claude", "settings.json"),
|
|
22113
|
+
path26.join(project, ".claude", "settings.local.json")
|
|
22114
|
+
];
|
|
22115
|
+
}
|
|
22116
|
+
var bashPattern = (entry) => {
|
|
22117
|
+
const match = /^Bash(?:\((.*)\))?$/su.exec(entry.trim());
|
|
22118
|
+
return match ? match[1] ?? "" : null;
|
|
22119
|
+
};
|
|
22120
|
+
function claudeRules(cwd, env = process.env) {
|
|
22121
|
+
const rules = EMPTY();
|
|
22122
|
+
try {
|
|
22123
|
+
for (const file of claudeSettingsFiles(cwd, env)) {
|
|
22124
|
+
const text = readText2(file);
|
|
22125
|
+
if (text === null)
|
|
22126
|
+
continue;
|
|
22127
|
+
const value = JSON.parse(text);
|
|
22128
|
+
if (!isRecord4(value))
|
|
22129
|
+
return "unreadable";
|
|
22130
|
+
const permissions = value.permissions;
|
|
22131
|
+
if (permissions === undefined)
|
|
22132
|
+
continue;
|
|
22133
|
+
if (!isRecord4(permissions))
|
|
22134
|
+
return "unreadable";
|
|
22135
|
+
for (const [key, bucket] of [
|
|
22136
|
+
["deny", rules.deny],
|
|
22137
|
+
["ask", rules.deny],
|
|
22138
|
+
["allow", rules.allow]
|
|
22139
|
+
]) {
|
|
22140
|
+
const list = permissions[key];
|
|
22141
|
+
if (list === undefined)
|
|
22142
|
+
continue;
|
|
22143
|
+
if (!Array.isArray(list))
|
|
22144
|
+
return "unreadable";
|
|
22145
|
+
for (const entry of list) {
|
|
22146
|
+
if (typeof entry !== "string")
|
|
22147
|
+
return "unreadable";
|
|
22148
|
+
const pattern = bashPattern(entry);
|
|
22149
|
+
if (pattern !== null)
|
|
22150
|
+
bucket.push(pattern);
|
|
22151
|
+
}
|
|
22152
|
+
}
|
|
22153
|
+
}
|
|
22154
|
+
} catch {
|
|
22155
|
+
return "unreadable";
|
|
22156
|
+
}
|
|
22157
|
+
return rules;
|
|
22158
|
+
}
|
|
22159
|
+
var OPENCODE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
22160
|
+
var opencodePermission = (permission, rules) => {
|
|
22161
|
+
if (permission === undefined)
|
|
22162
|
+
return true;
|
|
22163
|
+
if (typeof permission === "string") {
|
|
22164
|
+
(permission === "allow" ? rules.allow : rules.deny).push("");
|
|
22165
|
+
return ["allow", "ask", "deny"].includes(permission);
|
|
22166
|
+
}
|
|
22167
|
+
if (!isRecord4(permission))
|
|
22168
|
+
return false;
|
|
22169
|
+
for (const key of ["bash", "shell"]) {
|
|
22170
|
+
const value = permission[key];
|
|
22171
|
+
if (value === undefined)
|
|
22172
|
+
continue;
|
|
22173
|
+
if (typeof value === "string") {
|
|
22174
|
+
if (!["allow", "ask", "deny"].includes(value))
|
|
22175
|
+
return false;
|
|
22176
|
+
(value === "allow" ? rules.allow : rules.deny).push("");
|
|
22177
|
+
continue;
|
|
22178
|
+
}
|
|
22179
|
+
if (!isRecord4(value))
|
|
22180
|
+
return false;
|
|
22181
|
+
for (const [pattern, action] of Object.entries(value)) {
|
|
22182
|
+
if (action !== "allow" && action !== "ask" && action !== "deny")
|
|
22183
|
+
return false;
|
|
22184
|
+
(action === "allow" ? rules.allow : rules.deny).push(pattern);
|
|
22185
|
+
}
|
|
22186
|
+
}
|
|
22187
|
+
return true;
|
|
22188
|
+
};
|
|
22189
|
+
var opencodeConfig = (value, rules) => {
|
|
22190
|
+
if (!isRecord4(value))
|
|
22191
|
+
return false;
|
|
22192
|
+
if (!opencodePermission(value.permission, rules))
|
|
22193
|
+
return false;
|
|
22194
|
+
if (value.agent === undefined)
|
|
22195
|
+
return true;
|
|
22196
|
+
if (!isRecord4(value.agent))
|
|
22197
|
+
return false;
|
|
22198
|
+
return Object.values(value.agent).every((agent) => isRecord4(agent) && opencodePermission(agent.permission, rules));
|
|
22199
|
+
};
|
|
22200
|
+
function opencodeRules(cwd, env = process.env) {
|
|
22201
|
+
const rules = EMPTY();
|
|
22202
|
+
try {
|
|
22203
|
+
const config = env.XDG_CONFIG_HOME || path26.join(homeOf(env), ".config");
|
|
22204
|
+
const files = [
|
|
22205
|
+
...OPENCODE_NAMES.map((name) => path26.join(config, "opencode", name)),
|
|
22206
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path26.join(dir, name), path26.join(dir, ".opencode", name)])),
|
|
22207
|
+
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
22208
|
+
];
|
|
22209
|
+
for (const file of files) {
|
|
22210
|
+
const text = readText2(file);
|
|
22211
|
+
if (text !== null && !opencodeConfig(parseJsonc(text), rules))
|
|
22212
|
+
return "unreadable";
|
|
22213
|
+
}
|
|
22214
|
+
if (env.OPENCODE_CONFIG_CONTENT && !opencodeConfig(parseJsonc(env.OPENCODE_CONFIG_CONTENT), rules))
|
|
22215
|
+
return "unreadable";
|
|
22216
|
+
} catch {
|
|
22217
|
+
return "unreadable";
|
|
22218
|
+
}
|
|
22219
|
+
return rules;
|
|
22220
|
+
}
|
|
22221
|
+
var codexRule = (body) => {
|
|
22222
|
+
const open = /\bpattern\s*=\s*\[/u.exec(body);
|
|
22223
|
+
if (!open)
|
|
22224
|
+
return null;
|
|
22225
|
+
const words = [];
|
|
22226
|
+
let index = open.index + open[0].length;
|
|
22227
|
+
for (;; ) {
|
|
22228
|
+
while (/[\s,]/u.test(body[index] ?? ""))
|
|
22229
|
+
index += 1;
|
|
22230
|
+
if (body[index] === "]")
|
|
22231
|
+
break;
|
|
22232
|
+
if (body[index] !== '"')
|
|
22233
|
+
return null;
|
|
22234
|
+
const literal = /"((?:[^"\\]|\\.)*)"/y;
|
|
22235
|
+
literal.lastIndex = index;
|
|
22236
|
+
const match = literal.exec(body);
|
|
22237
|
+
if (!match)
|
|
22238
|
+
return null;
|
|
22239
|
+
words.push(JSON.parse(`"${match[1]}"`));
|
|
22240
|
+
index = literal.lastIndex;
|
|
22241
|
+
}
|
|
22242
|
+
const decision = /\bdecision\s*=\s*"(\w+)"/u.exec(body)?.[1];
|
|
22243
|
+
if (!words.length || !decision || !["allow", "prompt", "forbidden"].includes(decision))
|
|
22244
|
+
return null;
|
|
22245
|
+
return { pattern: words.join(" "), decision };
|
|
22246
|
+
};
|
|
22247
|
+
function codexRules(cwd, env = process.env) {
|
|
22248
|
+
const rules = EMPTY();
|
|
22249
|
+
try {
|
|
22250
|
+
const home = env.CODEX_HOME || path26.join(homeOf(env), ".codex");
|
|
22251
|
+
const top = gitTop(cwd) ?? cwd;
|
|
22252
|
+
for (const dir of [path26.join(home, "rules"), path26.join(top, ".codex", "rules")]) {
|
|
22253
|
+
let files;
|
|
22254
|
+
try {
|
|
22255
|
+
files = fs19.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
22256
|
+
} catch (error) {
|
|
22257
|
+
if (error.code === "ENOENT")
|
|
22258
|
+
continue;
|
|
22259
|
+
return "unreadable";
|
|
22260
|
+
}
|
|
22261
|
+
for (const name of files) {
|
|
22262
|
+
const text = readText2(path26.join(dir, name)) ?? "";
|
|
22263
|
+
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
22264
|
+
const rule = codexRule(part);
|
|
22265
|
+
if (!rule)
|
|
22266
|
+
return "unreadable";
|
|
22267
|
+
(rule.decision === "allow" ? rules.allow : rules.deny).push(rule.pattern);
|
|
22268
|
+
}
|
|
22269
|
+
}
|
|
22270
|
+
}
|
|
22271
|
+
} catch {
|
|
22272
|
+
return "unreadable";
|
|
22273
|
+
}
|
|
22274
|
+
return rules;
|
|
22275
|
+
}
|
|
22276
|
+
function cursorRules(cwd, env = process.env) {
|
|
22277
|
+
const rules = EMPTY();
|
|
22278
|
+
try {
|
|
22279
|
+
const top = gitTop(cwd) ?? cwd;
|
|
22280
|
+
for (const file of [
|
|
22281
|
+
path26.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
22282
|
+
path26.join(top, ".cursor", "cli.json")
|
|
22283
|
+
]) {
|
|
22284
|
+
const text = readText2(file);
|
|
22285
|
+
if (text === null)
|
|
22286
|
+
continue;
|
|
22287
|
+
const value = JSON.parse(text);
|
|
22288
|
+
if (!isRecord4(value))
|
|
22289
|
+
return "unreadable";
|
|
22290
|
+
if (value.permissions === undefined)
|
|
22291
|
+
continue;
|
|
22292
|
+
if (!isRecord4(value.permissions))
|
|
22293
|
+
return "unreadable";
|
|
22294
|
+
for (const [key, bucket] of [
|
|
22295
|
+
["deny", rules.deny],
|
|
22296
|
+
["allow", rules.allow]
|
|
22297
|
+
]) {
|
|
22298
|
+
const list = value.permissions[key];
|
|
22299
|
+
if (list === undefined)
|
|
22300
|
+
continue;
|
|
22301
|
+
if (!Array.isArray(list))
|
|
22302
|
+
return "unreadable";
|
|
22303
|
+
for (const entry of list) {
|
|
22304
|
+
if (typeof entry !== "string")
|
|
22305
|
+
return "unreadable";
|
|
22306
|
+
const match = /^Shell(?:\((.*)\))?$/su.exec(entry.trim());
|
|
22307
|
+
if (match)
|
|
22308
|
+
bucket.push(match[1] ?? "");
|
|
22309
|
+
}
|
|
22310
|
+
}
|
|
22311
|
+
}
|
|
22312
|
+
} catch {
|
|
22313
|
+
return "unreadable";
|
|
22314
|
+
}
|
|
22315
|
+
return rules;
|
|
22316
|
+
}
|
|
22317
|
+
var baseName = (word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "");
|
|
22318
|
+
var firstToken = (pattern) => pattern.trim().split(/[:\s]/u)[0] ?? "";
|
|
22319
|
+
function names(pattern, words) {
|
|
22320
|
+
const token = firstToken(pattern);
|
|
22321
|
+
if (!token || token === "*" || token.includes("*"))
|
|
22322
|
+
return true;
|
|
22323
|
+
const wanted = baseName(token);
|
|
22324
|
+
return words.some((word) => baseName(word) === wanted);
|
|
22325
|
+
}
|
|
22326
|
+
var literalPrefix = (pattern) => pattern.trim().replace(/(?::\*| \*|\*)$/u, "");
|
|
22327
|
+
var coversWrapper = (pattern) => {
|
|
22328
|
+
const token = firstToken(pattern);
|
|
22329
|
+
if (!token || token.includes("*"))
|
|
22330
|
+
return true;
|
|
22331
|
+
const words = literalPrefix(pattern).split(/\s+/u);
|
|
22332
|
+
if (baseName(words[0]) !== "workit")
|
|
22333
|
+
return false;
|
|
22334
|
+
return ["check", "--heavy"].slice(0, words.length - 1).every((word, at) => {
|
|
22335
|
+
const given = words[at + 1];
|
|
22336
|
+
return given === word || word.startsWith(given);
|
|
22337
|
+
});
|
|
22338
|
+
};
|
|
22339
|
+
var coversCommand = (pattern, command) => {
|
|
22340
|
+
const prefix = literalPrefix(pattern);
|
|
22341
|
+
if (!prefix)
|
|
22342
|
+
return true;
|
|
22343
|
+
return !prefix.includes("*") && command.trim().startsWith(prefix);
|
|
22344
|
+
};
|
|
22345
|
+
function wrapAllowed(read, words, command, options = {}) {
|
|
22346
|
+
if (read === "unreadable")
|
|
22347
|
+
return { ok: false, why: "named" };
|
|
22348
|
+
if (read.deny.some((pattern) => names(pattern, words)))
|
|
22349
|
+
return { ok: false, why: "named" };
|
|
22350
|
+
if (!options.bypass && read.allow.some(coversWrapper) && !read.allow.some((pattern) => coversCommand(pattern, command)))
|
|
22351
|
+
return { ok: false, why: "allowlisted" };
|
|
22352
|
+
return { ok: true };
|
|
22353
|
+
}
|
|
22354
|
+
|
|
22355
|
+
// packages/workit-core/src/core/heavy.ts
|
|
22356
|
+
import fs20 from "node:fs";
|
|
21969
22357
|
import os7 from "node:os";
|
|
21970
22358
|
import path27 from "node:path";
|
|
22359
|
+
function stateDir(env, platform = process.platform) {
|
|
22360
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
22361
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
22362
|
+
if (env.XDG_STATE_HOME)
|
|
22363
|
+
return path27.join(env.XDG_STATE_HOME, "workit");
|
|
22364
|
+
const home = env.HOME || os7.homedir();
|
|
22365
|
+
if (platform === "darwin")
|
|
22366
|
+
return path27.join(home, "Library", "Application Support", "workit");
|
|
22367
|
+
if (platform === "win32")
|
|
22368
|
+
return path27.join(env.LOCALAPPDATA ?? path27.join(home, "AppData", "Local"), "workit");
|
|
22369
|
+
return path27.join(home, ".local", "state", "workit");
|
|
22370
|
+
}
|
|
22371
|
+
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
22372
|
+
var heavyCommandsFile = (env) => path27.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
22373
|
+
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;
|
|
22374
|
+
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
22375
|
+
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
22376
|
+
var WATCH_SCRIPT = /(?:^|[:_.-])(?:watch|dev|serve|start|storybook)(?:$|[:_.-])/iu;
|
|
22377
|
+
var sub = (...names) => new Set(names);
|
|
22378
|
+
var TOOLS = {
|
|
22379
|
+
jest: "any",
|
|
22380
|
+
vitest: "any",
|
|
22381
|
+
mocha: "any",
|
|
22382
|
+
ava: "any",
|
|
22383
|
+
tap: "any",
|
|
22384
|
+
karma: "any",
|
|
22385
|
+
jasmine: "any",
|
|
22386
|
+
nyc: "any",
|
|
22387
|
+
c8: "any",
|
|
22388
|
+
tsc: "any",
|
|
22389
|
+
"vue-tsc": "any",
|
|
22390
|
+
"svelte-check": "any",
|
|
22391
|
+
eslint: "any",
|
|
22392
|
+
stylelint: "any",
|
|
22393
|
+
tslint: "any",
|
|
22394
|
+
webpack: "any",
|
|
22395
|
+
"webpack-cli": "any",
|
|
22396
|
+
rollup: "any",
|
|
22397
|
+
parcel: "any",
|
|
22398
|
+
tsup: "any",
|
|
22399
|
+
vite: sub("build", "optimize"),
|
|
22400
|
+
next: sub("build", "lint"),
|
|
22401
|
+
nuxt: sub("build", "generate"),
|
|
22402
|
+
nuxi: sub("build", "generate"),
|
|
22403
|
+
astro: sub("build", "check"),
|
|
22404
|
+
ng: sub("build", "b", "test", "t", "e2e", "lint"),
|
|
22405
|
+
"react-scripts": sub("build", "test"),
|
|
22406
|
+
playwright: sub("test", "install"),
|
|
22407
|
+
cypress: sub("run"),
|
|
22408
|
+
wdio: "any",
|
|
22409
|
+
testcafe: "any",
|
|
22410
|
+
turbo: "any",
|
|
22411
|
+
lerna: sub("run", "exec", "bootstrap"),
|
|
22412
|
+
"sonar-scanner": "any",
|
|
22413
|
+
sonarless: sub("scan"),
|
|
22414
|
+
"sonarless-mcp": sub("scan"),
|
|
22415
|
+
go: sub("test", "build", "vet", "install", "generate"),
|
|
22416
|
+
"golangci-lint": sub("run"),
|
|
22417
|
+
gotestsum: "any",
|
|
22418
|
+
staticcheck: "any",
|
|
22419
|
+
cargo: sub("build", "b", "test", "t", "check", "c", "clippy", "bench", "nextest", "doc", "install", "tarpaulin", "llvm-cov"),
|
|
22420
|
+
mvn: "any",
|
|
22421
|
+
mvnw: "any",
|
|
22422
|
+
gradle: "any",
|
|
22423
|
+
gradlew: "any",
|
|
22424
|
+
ant: "any",
|
|
22425
|
+
sbt: "any",
|
|
22426
|
+
bazel: sub("build", "test", "run", "coverage"),
|
|
22427
|
+
bazelisk: sub("build", "test", "run", "coverage"),
|
|
22428
|
+
buck2: sub("build", "test", "run"),
|
|
22429
|
+
dotnet: sub("build", "test", "publish", "restore", "pack", "msbuild"),
|
|
22430
|
+
msbuild: "any",
|
|
22431
|
+
pytest: "any",
|
|
22432
|
+
"py.test": "any",
|
|
22433
|
+
tox: "any",
|
|
22434
|
+
nox: "any",
|
|
22435
|
+
mypy: "any",
|
|
22436
|
+
pyright: "any",
|
|
22437
|
+
pylint: "any",
|
|
22438
|
+
coverage: sub("run"),
|
|
22439
|
+
maturin: sub("build", "develop"),
|
|
22440
|
+
pip: sub("install", "wheel"),
|
|
22441
|
+
pip3: sub("install", "wheel"),
|
|
22442
|
+
rspec: "any",
|
|
22443
|
+
rails: sub("test", "assets:precompile"),
|
|
22444
|
+
phpunit: "any",
|
|
22445
|
+
pest: "any",
|
|
22446
|
+
phpstan: "any",
|
|
22447
|
+
psalm: "any",
|
|
22448
|
+
composer: sub("install", "update", "require", "test"),
|
|
22449
|
+
mix: sub("test", "compile", "deps.get", "dialyzer", "credo"),
|
|
22450
|
+
swift: sub("build", "test"),
|
|
22451
|
+
xcodebuild: "any",
|
|
22452
|
+
flutter: sub("build", "test"),
|
|
22453
|
+
dart: sub("test", "compile"),
|
|
22454
|
+
pod: sub("install"),
|
|
22455
|
+
cmake: sub("--build"),
|
|
22456
|
+
ninja: "any",
|
|
22457
|
+
meson: sub("compile", "test"),
|
|
22458
|
+
ctest: "any",
|
|
22459
|
+
scons: "any",
|
|
22460
|
+
"docker-compose": sub("build"),
|
|
22461
|
+
buildah: sub("bud", "build"),
|
|
22462
|
+
earthly: "any"
|
|
22463
|
+
};
|
|
22464
|
+
var PM_VALUES = new Set([
|
|
22465
|
+
"--prefix",
|
|
22466
|
+
"-w",
|
|
22467
|
+
"--workspace",
|
|
22468
|
+
"-C",
|
|
22469
|
+
"--dir",
|
|
22470
|
+
"--filter",
|
|
22471
|
+
"-F",
|
|
22472
|
+
"--cwd",
|
|
22473
|
+
"-p",
|
|
22474
|
+
"--package",
|
|
22475
|
+
"--cache",
|
|
22476
|
+
"--registry"
|
|
22477
|
+
]);
|
|
22478
|
+
function positional2(words, from, values = PM_VALUES) {
|
|
22479
|
+
let index = from;
|
|
22480
|
+
while (index < words.length && words[index].startsWith("-")) {
|
|
22481
|
+
if (words[index] === "--")
|
|
22482
|
+
return index + 1;
|
|
22483
|
+
index += values.has(words[index]) ? 2 : 1;
|
|
22484
|
+
}
|
|
22485
|
+
return index;
|
|
22486
|
+
}
|
|
22487
|
+
var baseName2 = (word) => (word?.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/u, "");
|
|
22488
|
+
var WORKIT = /^(?:workit|@brainervirus\/workit-cli(?:@.*)?)$/u;
|
|
22489
|
+
var PYTHONS = /^(?:python[\d.]*|py)$/u;
|
|
22490
|
+
var NPM_INSTALL = new Set([
|
|
22491
|
+
"ci",
|
|
22492
|
+
"clean-install",
|
|
22493
|
+
"cit",
|
|
22494
|
+
"install-test",
|
|
22495
|
+
"it",
|
|
22496
|
+
"install",
|
|
22497
|
+
"i",
|
|
22498
|
+
"in",
|
|
22499
|
+
"add",
|
|
22500
|
+
"rebuild",
|
|
22501
|
+
"update",
|
|
22502
|
+
"up"
|
|
22503
|
+
]);
|
|
22504
|
+
var NPM_RUN = new Set(["run", "run-script", "rum", "urn"]);
|
|
22505
|
+
var NPM_TEST = new Set(["test", "t", "tst"]);
|
|
22506
|
+
var EXEC = new Set(["exec", "x", "dlx"]);
|
|
22507
|
+
function builtIn(words, depth = 0) {
|
|
22508
|
+
if (depth > 6 || !words.length)
|
|
22509
|
+
return null;
|
|
22510
|
+
const name = baseName2(words[0]);
|
|
22511
|
+
const label = () => words.slice(0, 3).join(" ");
|
|
22512
|
+
const inner = (from) => builtIn(words.slice(from), depth + 1);
|
|
22513
|
+
if (WORKIT.test(name))
|
|
22514
|
+
return null;
|
|
22515
|
+
if (INFO_FLAGS.has(words[1] ?? "") && words.length === 2)
|
|
22516
|
+
return null;
|
|
22517
|
+
if (words.some((word) => word === "--watch" || word.startsWith("--watch=") || word === "--watchAll"))
|
|
22518
|
+
return null;
|
|
22519
|
+
if (name === "npx" || name === "bunx" || name === "pnpx")
|
|
22520
|
+
return inner(positional2(words, 1));
|
|
22521
|
+
if (name === "corepack")
|
|
22522
|
+
return inner(1);
|
|
22523
|
+
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" || name === "deno") {
|
|
22524
|
+
const at = positional2(words, 1);
|
|
22525
|
+
const command = words[at];
|
|
22526
|
+
if (command === undefined)
|
|
22527
|
+
return name === "yarn" ? "yarn (install)" : null;
|
|
22528
|
+
if (name === "deno") {
|
|
22529
|
+
if (["test", "bench", "check", "compile", "lint"].includes(command))
|
|
22530
|
+
return label();
|
|
22531
|
+
if (command === "task" && HEAVY_SCRIPT.test(words[at + 1] ?? ""))
|
|
22532
|
+
return label();
|
|
22533
|
+
return null;
|
|
22534
|
+
}
|
|
22535
|
+
if (NPM_INSTALL.has(command) || NPM_TEST.has(command))
|
|
22536
|
+
return label();
|
|
22537
|
+
if (name === "bun" && command === "build")
|
|
22538
|
+
return label();
|
|
22539
|
+
if (EXEC.has(command))
|
|
22540
|
+
return inner(positional2(words, at + 1));
|
|
22541
|
+
if (name === "yarn" && command === "workspace")
|
|
22542
|
+
return inner(at + 2);
|
|
22543
|
+
if (name === "yarn" && command === "workspaces")
|
|
22544
|
+
return words.slice(at + 1).some((word) => HEAVY_SCRIPT.test(word)) ? label() : null;
|
|
22545
|
+
if (NPM_RUN.has(command)) {
|
|
22546
|
+
const script = words[positional2(words, at + 1)];
|
|
22547
|
+
if (script === undefined || WATCH_SCRIPT.test(script))
|
|
22548
|
+
return null;
|
|
22549
|
+
if (HEAVY_SCRIPT.test(script))
|
|
22550
|
+
return label();
|
|
22551
|
+
return name === "bun" ? inner(positional2(words, at + 1)) : null;
|
|
22552
|
+
}
|
|
22553
|
+
if (name === "npm")
|
|
22554
|
+
return null;
|
|
22555
|
+
if (WATCH_SCRIPT.test(command))
|
|
22556
|
+
return null;
|
|
22557
|
+
if (HEAVY_SCRIPT.test(command) && !/\.[cm]?[jt]sx?$/u.test(command))
|
|
22558
|
+
return label();
|
|
22559
|
+
return inner(at);
|
|
22560
|
+
}
|
|
22561
|
+
if (PYTHONS.test(name)) {
|
|
22562
|
+
const at = words.indexOf("-m");
|
|
22563
|
+
return at > 0 && words[at + 1] ? inner(at + 1) : null;
|
|
22564
|
+
}
|
|
22565
|
+
if (["uv", "poetry", "pdm", "pipenv", "hatch", "rye"].includes(name)) {
|
|
22566
|
+
const at = positional2(words, 1);
|
|
22567
|
+
const command = words[at];
|
|
22568
|
+
if (command === "run")
|
|
22569
|
+
return inner(positional2(words, at + 1));
|
|
22570
|
+
if (command === "pip")
|
|
22571
|
+
return inner(at);
|
|
22572
|
+
return ["sync", "install", "build", "test", "add"].includes(command ?? "") ? label() : null;
|
|
22573
|
+
}
|
|
22574
|
+
if (name === "bundle") {
|
|
22575
|
+
const command = words[positional2(words, 1)];
|
|
22576
|
+
if (command === "exec")
|
|
22577
|
+
return inner(positional2(words, 2));
|
|
22578
|
+
return command === "install" || command === undefined ? label() : null;
|
|
22579
|
+
}
|
|
22580
|
+
if (name === "make" || name === "gmake") {
|
|
22581
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
22582
|
+
if (targets.some((target) => WATCH_SCRIPT.test(target)))
|
|
22583
|
+
return null;
|
|
22584
|
+
return !targets.length || targets.some((target) => HEAVY_SCRIPT.test(target)) ? label() : null;
|
|
22585
|
+
}
|
|
22586
|
+
if (name === "rake") {
|
|
22587
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
22588
|
+
return targets.some((target) => HEAVY_SCRIPT.test(target) || /^spec(?:$|:)/u.test(target)) ? label() : null;
|
|
22589
|
+
}
|
|
22590
|
+
if (name === "nx") {
|
|
22591
|
+
const at = positional2(words, 1);
|
|
22592
|
+
const command = words[at] ?? "";
|
|
22593
|
+
if (["run-many", "affected"].includes(command))
|
|
22594
|
+
return words.some((word) => /^(?:-t|--targets?)(?:=|$)/u.test(word)) && !words.some((word) => WATCH_SCRIPT.test(word.replace(/^--?targets?=/u, ""))) ? label() : null;
|
|
22595
|
+
if (command === "run") {
|
|
22596
|
+
const target = (words[at + 1] ?? "").split(":")[1] ?? "";
|
|
22597
|
+
return HEAVY_SCRIPT.test(target) && !WATCH_SCRIPT.test(target) ? label() : null;
|
|
22598
|
+
}
|
|
22599
|
+
return HEAVY_SCRIPT.test(command) && !WATCH_SCRIPT.test(command) ? label() : null;
|
|
22600
|
+
}
|
|
22601
|
+
if (name === "docker" || name === "podman" || name === "nerdctl") {
|
|
22602
|
+
const at = positional2(words, 1, new Set(["-H", "--host", "--context", "-c", "--config"]));
|
|
22603
|
+
const command = words[at];
|
|
22604
|
+
if (command === "build")
|
|
22605
|
+
return label();
|
|
22606
|
+
if (command === "buildx" || command === "image" || command === "builder")
|
|
22607
|
+
return words[positional2(words, at + 1)] === "build" ? label() : null;
|
|
22608
|
+
if (command === "compose") {
|
|
22609
|
+
const at2 = positional2(words, at + 1, new Set(["-f", "--file", "-p"]));
|
|
22610
|
+
const action = words[at2];
|
|
22611
|
+
if (action === "build")
|
|
22612
|
+
return label();
|
|
22613
|
+
return action === "up" && words.slice(at2 + 1).includes("--build") ? label() : null;
|
|
22614
|
+
}
|
|
22615
|
+
return null;
|
|
22616
|
+
}
|
|
22617
|
+
const rule = TOOLS[name];
|
|
22618
|
+
if (!rule)
|
|
22619
|
+
return null;
|
|
22620
|
+
if (words.slice(1).some((word) => WATCH_FLAG.test(word) || word === "watch" || word === "dev"))
|
|
22621
|
+
return null;
|
|
22622
|
+
if (rule === "any")
|
|
22623
|
+
return label();
|
|
22624
|
+
const command = words.slice(1).find((word) => !word.startsWith("-") && !word.startsWith("+") || rule.has(word));
|
|
22625
|
+
return command !== undefined && rule.has(command) ? label() : null;
|
|
22626
|
+
}
|
|
22627
|
+
var globWord = (word) => new RegExp(`^${word.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*")}$`, "u");
|
|
22628
|
+
function parsePattern(pattern) {
|
|
22629
|
+
const words = pattern.trim().split(/\s+/u).filter(Boolean);
|
|
22630
|
+
return words.length ? words.map(globWord) : null;
|
|
22631
|
+
}
|
|
22632
|
+
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]));
|
|
22633
|
+
var readJson = (file) => {
|
|
22634
|
+
try {
|
|
22635
|
+
return JSON.parse(fs20.readFileSync(file, "utf8"));
|
|
22636
|
+
} catch {
|
|
22637
|
+
return;
|
|
22638
|
+
}
|
|
22639
|
+
};
|
|
22640
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22641
|
+
var stringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
22642
|
+
function machinePatterns(env) {
|
|
22643
|
+
const value = readJson(heavyCommandsFile(env));
|
|
22644
|
+
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
22645
|
+
}
|
|
22646
|
+
function repoPatterns(cwd) {
|
|
22647
|
+
return repoHeavyCommands(gitTop(cwd) ?? path27.resolve(cwd));
|
|
22648
|
+
}
|
|
22649
|
+
function heavyLines(lines, options) {
|
|
22650
|
+
const found = [];
|
|
22651
|
+
let patterns = null;
|
|
22652
|
+
for (const line of lines) {
|
|
22653
|
+
const reason = builtIn(line);
|
|
22654
|
+
if (reason) {
|
|
22655
|
+
found.push({ line, source: "catalog", reason });
|
|
22656
|
+
continue;
|
|
22657
|
+
}
|
|
22658
|
+
patterns ??= (options.patterns ?? [
|
|
22659
|
+
...machinePatterns(options.env ?? process.env).map((pattern) => ({
|
|
22660
|
+
pattern,
|
|
22661
|
+
from: "machine"
|
|
22662
|
+
})),
|
|
22663
|
+
...repoPatterns(options.cwd).map((pattern) => ({ pattern, from: "repo" }))
|
|
22664
|
+
]).flatMap((entry) => {
|
|
22665
|
+
const matchers = parsePattern(entry.pattern);
|
|
22666
|
+
return matchers ? [{ ...entry, matchers }] : [];
|
|
22667
|
+
});
|
|
22668
|
+
const hit = patterns.find((entry) => matches(entry.matchers, line));
|
|
22669
|
+
if (hit)
|
|
22670
|
+
found.push({
|
|
22671
|
+
line,
|
|
22672
|
+
source: { pattern: hit.pattern, from: hit.from },
|
|
22673
|
+
reason: `${hit.from} pattern "${hit.pattern}"`
|
|
22674
|
+
});
|
|
22675
|
+
}
|
|
22676
|
+
return found;
|
|
22677
|
+
}
|
|
22678
|
+
var linesOf = (command, dialect) => {
|
|
22679
|
+
try {
|
|
22680
|
+
return commandLines(command, dialect);
|
|
22681
|
+
} catch {
|
|
22682
|
+
return null;
|
|
22683
|
+
}
|
|
22684
|
+
};
|
|
22685
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/u.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
|
|
22686
|
+
var leaseCommand = (words) => `workit check --heavy -- ${words.map(quoteWord).join(" ")}`;
|
|
22687
|
+
function heavyPlan(command, options) {
|
|
22688
|
+
const dialect = options.dialect ?? "posix";
|
|
22689
|
+
if (dialect !== "posix" || !gitTop(options.cwd))
|
|
22690
|
+
return { kind: "none" };
|
|
22691
|
+
const lines = linesOf(command, dialect);
|
|
22692
|
+
if (!lines)
|
|
22693
|
+
return { kind: "none" };
|
|
22694
|
+
const heavy = heavyLines(lines, options);
|
|
22695
|
+
if (!heavy.length)
|
|
22696
|
+
return { kind: "none" };
|
|
22697
|
+
const simple = simpleCommand(command, dialect);
|
|
22698
|
+
const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
|
|
22699
|
+
const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
|
|
22700
|
+
words: match.line,
|
|
22701
|
+
text: match.line.join(" ")
|
|
22702
|
+
}));
|
|
22703
|
+
if (options.rules !== null)
|
|
22704
|
+
for (const { words, text } of judged) {
|
|
22705
|
+
const verdict = wrapAllowed(options.rules, words, text, { bypass: options.bypass });
|
|
22706
|
+
if (!verdict.ok)
|
|
22707
|
+
return {
|
|
22708
|
+
kind: "passthrough",
|
|
22709
|
+
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.`
|
|
22710
|
+
};
|
|
22711
|
+
}
|
|
22712
|
+
if (single) {
|
|
22713
|
+
const rewrite = leaseRewrite(command);
|
|
22714
|
+
return rewrite ? { kind: "rewrite", command: rewrite, reason: single.reason } : { kind: "none" };
|
|
22715
|
+
}
|
|
22716
|
+
const parts = heavy.map((match) => leaseCommand(match.line));
|
|
22717
|
+
return {
|
|
22718
|
+
kind: "split",
|
|
22719
|
+
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.`,
|
|
22720
|
+
unblock: parts[0] ?? null
|
|
22721
|
+
};
|
|
22722
|
+
}
|
|
22723
|
+
function workitOnPath2(env = process.env) {
|
|
22724
|
+
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
22725
|
+
for (const dir of (env.PATH ?? "").split(path27.delimiter))
|
|
22726
|
+
for (const name of names) {
|
|
22727
|
+
if (!dir)
|
|
22728
|
+
continue;
|
|
22729
|
+
try {
|
|
22730
|
+
fs20.accessSync(path27.join(dir, name), fs20.constants.X_OK);
|
|
22731
|
+
return true;
|
|
22732
|
+
} catch {}
|
|
22733
|
+
}
|
|
22734
|
+
return false;
|
|
22735
|
+
}
|
|
22736
|
+
var ASSIGNMENTS = /^\s*((?:[A-Za-z_]\w*=(?:'[^']*'|"(?:[^"\\]|\\.)*"|[^\s'"])*\s+)*)/u;
|
|
22737
|
+
function leaseRewrite(command) {
|
|
22738
|
+
const text = command.trim();
|
|
22739
|
+
const prefix = ASSIGNMENTS.exec(text)?.[1] ?? "";
|
|
22740
|
+
const rest = text.slice(prefix.length).trim();
|
|
22741
|
+
if (!rest)
|
|
22742
|
+
return null;
|
|
22743
|
+
return `${prefix}workit check --heavy -- ${rest}`;
|
|
22744
|
+
}
|
|
22745
|
+
|
|
22746
|
+
// packages/workit-core/src/core/heavy-words.ts
|
|
22747
|
+
import fs21 from "node:fs";
|
|
22748
|
+
import os8 from "node:os";
|
|
22749
|
+
import path28 from "node:path";
|
|
22750
|
+
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(" "));
|
|
22751
|
+
var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
|
|
22752
|
+
var stateDir2 = (env) => {
|
|
22753
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
22754
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
22755
|
+
if (env.XDG_STATE_HOME)
|
|
22756
|
+
return path28.join(env.XDG_STATE_HOME, "workit");
|
|
22757
|
+
const home = env.HOME || os8.homedir();
|
|
22758
|
+
if (process.platform === "darwin")
|
|
22759
|
+
return path28.join(home, "Library", "Application Support", "workit");
|
|
22760
|
+
if (process.platform === "win32")
|
|
22761
|
+
return path28.join(env.LOCALAPPDATA ?? path28.join(home, "AppData", "Local"), "workit");
|
|
22762
|
+
return path28.join(home, ".local", "state", "workit");
|
|
22763
|
+
};
|
|
22764
|
+
var readJson2 = (file) => {
|
|
22765
|
+
try {
|
|
22766
|
+
return JSON.parse(fs21.readFileSync(file, "utf8"));
|
|
22767
|
+
} catch {
|
|
22768
|
+
return;
|
|
22769
|
+
}
|
|
22770
|
+
};
|
|
22771
|
+
var patternWords = (cwd, env) => {
|
|
22772
|
+
const patterns = [];
|
|
22773
|
+
const machine = readJson2(path28.join(stateDir2(env), "heavy-commands.json"));
|
|
22774
|
+
if (Array.isArray(machine?.patterns))
|
|
22775
|
+
patterns.push(...machine.patterns);
|
|
22776
|
+
for (let dir = path28.resolve(cwd);; dir = path28.dirname(dir)) {
|
|
22777
|
+
if (fs21.existsSync(path28.join(dir, ".git"))) {
|
|
22778
|
+
patterns.push(...repoHeavyCommands(dir));
|
|
22779
|
+
break;
|
|
22780
|
+
}
|
|
22781
|
+
if (path28.dirname(dir) === dir)
|
|
22782
|
+
break;
|
|
22783
|
+
}
|
|
22784
|
+
const words = new Set;
|
|
22785
|
+
for (const pattern of patterns) {
|
|
22786
|
+
if (typeof pattern !== "string")
|
|
22787
|
+
continue;
|
|
22788
|
+
const first = wordsOf(pattern)[0];
|
|
22789
|
+
if (!first || first.includes("*"))
|
|
22790
|
+
return null;
|
|
22791
|
+
words.add(first);
|
|
22792
|
+
}
|
|
22793
|
+
return words;
|
|
22794
|
+
};
|
|
22795
|
+
function maybeHeavy(command, cwd, env = process.env) {
|
|
22796
|
+
try {
|
|
22797
|
+
const words = wordsOf(command);
|
|
22798
|
+
if (words.some((word) => HEAVY_WORDS.has(word) || /^python[\d.]*$/u.test(word)))
|
|
22799
|
+
return true;
|
|
22800
|
+
const extra = patternWords(cwd, env);
|
|
22801
|
+
return extra === null || words.some((word) => extra.has(word));
|
|
22802
|
+
} catch {
|
|
22803
|
+
return true;
|
|
22804
|
+
}
|
|
22805
|
+
}
|
|
22806
|
+
|
|
22807
|
+
// packages/workit-core/src/hooks/heavy-gate.ts
|
|
22808
|
+
var rulesFor = (host, cwd, env) => {
|
|
22809
|
+
switch (host) {
|
|
22810
|
+
case "claude_code":
|
|
22811
|
+
return claudeRules(cwd, env);
|
|
22812
|
+
case "opencode":
|
|
22813
|
+
return opencodeRules(cwd, env);
|
|
22814
|
+
case "codex_cli":
|
|
22815
|
+
case "codex_desktop":
|
|
22816
|
+
return codexRules(cwd, env);
|
|
22817
|
+
case "cursor":
|
|
22818
|
+
return cursorRules(cwd, env);
|
|
22819
|
+
case "pi":
|
|
22820
|
+
return null;
|
|
22821
|
+
}
|
|
22822
|
+
};
|
|
22823
|
+
function heavyGate(cwd, command, options) {
|
|
22824
|
+
const env = options.env ?? process.env;
|
|
22825
|
+
try {
|
|
22826
|
+
if (env.WORKIT_HEAVY_GATE === "0" || !maybeHeavy(command, cwd, env) || !workitOnPath2(env))
|
|
22827
|
+
return { kind: "none" };
|
|
22828
|
+
return heavyPlan(command, {
|
|
22829
|
+
cwd,
|
|
22830
|
+
env,
|
|
22831
|
+
dialect: options.dialect ?? "posix",
|
|
22832
|
+
rules: rulesFor(options.host, cwd, env),
|
|
22833
|
+
bypass: options.permissionMode === "bypassPermissions"
|
|
22834
|
+
});
|
|
22835
|
+
} catch {
|
|
22836
|
+
return { kind: "none" };
|
|
22837
|
+
}
|
|
22838
|
+
}
|
|
22839
|
+
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.`;
|
|
22840
|
+
var heavyDenial = (original, plan) => ({
|
|
22841
|
+
kind: "deny",
|
|
22842
|
+
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}`,
|
|
22843
|
+
unblock: plan.command
|
|
22844
|
+
});
|
|
22845
|
+
var splitDenial = (plan) => ({
|
|
22846
|
+
kind: "deny",
|
|
22847
|
+
reason: plan.reason,
|
|
22848
|
+
unblock: plan.unblock
|
|
22849
|
+
});
|
|
22850
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
22851
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
22852
|
+
import os10 from "node:os";
|
|
22853
|
+
import path30 from "node:path";
|
|
21971
22854
|
|
|
21972
22855
|
// packages/workit-core/src/core/live-session.ts
|
|
21973
22856
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21974
22857
|
import { createHash as createHash15 } from "node:crypto";
|
|
21975
|
-
import
|
|
21976
|
-
import
|
|
21977
|
-
import
|
|
22858
|
+
import fs22 from "node:fs";
|
|
22859
|
+
import os9 from "node:os";
|
|
22860
|
+
import path29 from "node:path";
|
|
21978
22861
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21979
22862
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
21980
22863
|
var liveProbe = {
|
|
@@ -21995,9 +22878,9 @@ var processAlive = (pid) => {
|
|
|
21995
22878
|
var processInfo = (pid) => {
|
|
21996
22879
|
try {
|
|
21997
22880
|
if (liveProbe.platform === "linux") {
|
|
21998
|
-
const stat =
|
|
22881
|
+
const stat = fs22.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
21999
22882
|
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
22000
|
-
const command =
|
|
22883
|
+
const command = fs22.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
22001
22884
|
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
22002
22885
|
}
|
|
22003
22886
|
if (liveProbe.platform === "win32")
|
|
@@ -22048,35 +22931,35 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
22048
22931
|
};
|
|
22049
22932
|
};
|
|
22050
22933
|
var checkoutOf = (target) => {
|
|
22051
|
-
let dir =
|
|
22052
|
-
while (!
|
|
22053
|
-
dir =
|
|
22054
|
-
if (
|
|
22055
|
-
dir =
|
|
22056
|
-
const parts = dir.split(
|
|
22934
|
+
let dir = path29.resolve(target);
|
|
22935
|
+
while (!fs22.existsSync(dir) && path29.dirname(dir) !== dir)
|
|
22936
|
+
dir = path29.dirname(dir);
|
|
22937
|
+
if (fs22.existsSync(dir) && !fs22.statSync(dir).isDirectory())
|
|
22938
|
+
dir = path29.dirname(dir);
|
|
22939
|
+
const parts = dir.split(path29.sep);
|
|
22057
22940
|
const dotGit = parts.lastIndexOf(".git");
|
|
22058
22941
|
if (dotGit > 0)
|
|
22059
|
-
dir = parts.slice(0, dotGit).join(
|
|
22942
|
+
dir = parts.slice(0, dotGit).join(path29.sep) || path29.sep;
|
|
22060
22943
|
const location = resolveStore(dir);
|
|
22061
22944
|
if (location instanceof Error || !location.shared || !location.top)
|
|
22062
22945
|
return null;
|
|
22063
22946
|
let top = location.top;
|
|
22064
22947
|
try {
|
|
22065
|
-
top =
|
|
22948
|
+
top = fs22.realpathSync.native(top);
|
|
22066
22949
|
} catch {}
|
|
22067
22950
|
return top;
|
|
22068
22951
|
};
|
|
22069
|
-
var markerFile2 = (top) =>
|
|
22952
|
+
var markerFile2 = (top) => path29.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
22070
22953
|
var checkoutKey = (top, platform = process.platform) => {
|
|
22071
|
-
let key =
|
|
22954
|
+
let key = path29.resolve(top);
|
|
22072
22955
|
try {
|
|
22073
|
-
key =
|
|
22956
|
+
key = fs22.realpathSync.native(key);
|
|
22074
22957
|
} catch {}
|
|
22075
22958
|
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
22076
22959
|
};
|
|
22077
22960
|
var readMarker = (file) => {
|
|
22078
22961
|
try {
|
|
22079
|
-
const value = JSON.parse(
|
|
22962
|
+
const value = JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
22080
22963
|
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
22081
22964
|
return null;
|
|
22082
22965
|
return {
|
|
@@ -22099,7 +22982,7 @@ var isLive = (marker, deps) => {
|
|
|
22099
22982
|
const age = ageOf2(marker, deps.now ?? new Date);
|
|
22100
22983
|
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
22101
22984
|
return false;
|
|
22102
|
-
if (marker.machine !== (deps.machine ??
|
|
22985
|
+
if (marker.machine !== (deps.machine ?? os9.hostname()) || marker.pid === null)
|
|
22103
22986
|
return true;
|
|
22104
22987
|
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
22105
22988
|
return false;
|
|
@@ -22128,7 +23011,7 @@ var liveConflict = (target, owner, deps = {}) => {
|
|
|
22128
23011
|
const marker = readMarker(file);
|
|
22129
23012
|
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
22130
23013
|
return null;
|
|
22131
|
-
if (sameOwner(marker, owner, deps.machine ??
|
|
23014
|
+
if (sameOwner(marker, owner, deps.machine ?? os9.hostname()))
|
|
22132
23015
|
return null;
|
|
22133
23016
|
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
22134
23017
|
} catch {
|
|
@@ -22142,7 +23025,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
22142
23025
|
return;
|
|
22143
23026
|
const file = markerFile2(checkout);
|
|
22144
23027
|
const now = deps.now ?? new Date;
|
|
22145
|
-
const machine = deps.machine ??
|
|
23028
|
+
const machine = deps.machine ?? os9.hostname();
|
|
22146
23029
|
const current = readMarker(file);
|
|
22147
23030
|
if (current) {
|
|
22148
23031
|
const age = ageOf2(current, now);
|
|
@@ -22163,10 +23046,10 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
22163
23046
|
checkout,
|
|
22164
23047
|
heartbeatAt: now.toISOString()
|
|
22165
23048
|
};
|
|
22166
|
-
|
|
23049
|
+
fs22.mkdirSync(path29.dirname(file), { recursive: true });
|
|
22167
23050
|
const temp = `${file}.${process.pid}.tmp`;
|
|
22168
|
-
|
|
22169
|
-
|
|
23051
|
+
fs22.writeFileSync(temp, JSON.stringify(marker));
|
|
23052
|
+
fs22.renameSync(temp, file);
|
|
22170
23053
|
} catch {}
|
|
22171
23054
|
};
|
|
22172
23055
|
|
|
@@ -22175,27 +23058,42 @@ var NONE3 = { kind: "none" };
|
|
|
22175
23058
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
22176
23059
|
var canonical2 = (file) => {
|
|
22177
23060
|
let rest = "";
|
|
22178
|
-
for (let dir =
|
|
23061
|
+
for (let dir = path30.resolve(file);; dir = path30.dirname(dir)) {
|
|
22179
23062
|
try {
|
|
22180
|
-
return
|
|
23063
|
+
return path30.join(realpathSync6(dir), rest);
|
|
22181
23064
|
} catch {
|
|
22182
|
-
if (
|
|
22183
|
-
return
|
|
22184
|
-
rest = rest ?
|
|
23065
|
+
if (path30.dirname(dir) === dir)
|
|
23066
|
+
return path30.resolve(file);
|
|
23067
|
+
rest = rest ? path30.join(path30.basename(dir), rest) : path30.basename(dir);
|
|
22185
23068
|
}
|
|
22186
23069
|
}
|
|
22187
23070
|
};
|
|
22188
23071
|
var exempt = (cwd, root, file, plans) => {
|
|
22189
23072
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
22190
|
-
const relative =
|
|
22191
|
-
if (relative.startsWith("..") ||
|
|
23073
|
+
const relative = path30.relative(fold(canonical2(root)), fold(canonical2(path30.resolve(cwd, file))));
|
|
23074
|
+
if (relative.startsWith("..") || path30.isAbsolute(relative))
|
|
22192
23075
|
return true;
|
|
22193
|
-
const posix = relative.split(
|
|
23076
|
+
const posix = relative.split(path30.sep).join("/");
|
|
22194
23077
|
const segments = posix.split("/");
|
|
22195
23078
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
22196
23079
|
};
|
|
22197
23080
|
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
22198
23081
|
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
23082
|
+
var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
23083
|
+
var wrappedScript = (input) => {
|
|
23084
|
+
let words = input;
|
|
23085
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
23086
|
+
words = words.slice(1);
|
|
23087
|
+
const tail = workitCheckTail(words);
|
|
23088
|
+
if (!tail)
|
|
23089
|
+
return null;
|
|
23090
|
+
if ("script" in tail)
|
|
23091
|
+
return tail.script;
|
|
23092
|
+
const [shell, flag, script] = tail.argv;
|
|
23093
|
+
if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
|
|
23094
|
+
return script ?? null;
|
|
23095
|
+
return tail.argv.map(quoteWord2).join(" ");
|
|
23096
|
+
};
|
|
22199
23097
|
var editsOf = (input) => {
|
|
22200
23098
|
let words = input;
|
|
22201
23099
|
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
@@ -22272,10 +23170,18 @@ var editsOf = (input) => {
|
|
|
22272
23170
|
return [];
|
|
22273
23171
|
}
|
|
22274
23172
|
};
|
|
22275
|
-
var shellWrites = (command) => {
|
|
23173
|
+
var shellWrites = (command, depth = 0) => {
|
|
22276
23174
|
const targets = [];
|
|
22277
23175
|
let unknown = false;
|
|
22278
23176
|
for (const segment of segmentsOf(command)) {
|
|
23177
|
+
const inner = depth < 4 ? wrappedScript(segment.words) : null;
|
|
23178
|
+
if (inner !== null) {
|
|
23179
|
+
const nested = shellWrites(inner, depth + 1);
|
|
23180
|
+
if (nested.writes && nested.targets === null)
|
|
23181
|
+
unknown = true;
|
|
23182
|
+
else if (nested.targets)
|
|
23183
|
+
targets.push(...nested.targets);
|
|
23184
|
+
}
|
|
22279
23185
|
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
22280
23186
|
const edits = editsOf(segment.words);
|
|
22281
23187
|
if (edits === null)
|
|
@@ -22288,7 +23194,7 @@ var shellWrites = (command) => {
|
|
|
22288
23194
|
return { writes: true, targets: unknown ? null : targets };
|
|
22289
23195
|
};
|
|
22290
23196
|
var cache2 = new Map;
|
|
22291
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
23197
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path30.resolve(root, file)) ?? "-").join("\x00");
|
|
22292
23198
|
var gateFor = (cwd) => {
|
|
22293
23199
|
const store = new TaskStore(cwd);
|
|
22294
23200
|
const listed = store.listTaskIndex();
|
|
@@ -22367,7 +23273,7 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
22367
23273
|
return [];
|
|
22368
23274
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
22369
23275
|
return [];
|
|
22370
|
-
return [
|
|
23276
|
+
return [path30.resolve(cwd, invocation.dir ?? ".")];
|
|
22371
23277
|
});
|
|
22372
23278
|
};
|
|
22373
23279
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
@@ -22377,15 +23283,15 @@ var cdInto = (dir, words) => {
|
|
|
22377
23283
|
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
22378
23284
|
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
22379
23285
|
return;
|
|
22380
|
-
return target === undefined ?
|
|
23286
|
+
return target === undefined ? os10.homedir() : path30.resolve(dir, target);
|
|
22381
23287
|
};
|
|
22382
|
-
var
|
|
22383
|
-
var segmentTargets = (cwd, command, dialect) => {
|
|
23288
|
+
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
23289
|
+
var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
22384
23290
|
const targets = [];
|
|
22385
23291
|
let dir = cwd;
|
|
22386
23292
|
let stack = [];
|
|
22387
23293
|
const groups = [];
|
|
22388
|
-
const at = (file) =>
|
|
23294
|
+
const at = (file) => path30.isAbsolute(file) ? file : dir === undefined ? null : path30.resolve(dir, file);
|
|
22389
23295
|
for (const segment of segmentsOf(command, dialect)) {
|
|
22390
23296
|
if (segment.group === "open") {
|
|
22391
23297
|
groups.push({ dir, stack: [...stack] });
|
|
@@ -22413,6 +23319,9 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
22413
23319
|
dir = stack.length ? stack.pop() : undefined;
|
|
22414
23320
|
continue;
|
|
22415
23321
|
}
|
|
23322
|
+
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
23323
|
+
if (inner !== null && dir !== undefined)
|
|
23324
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
22416
23325
|
for (const file of segment.redirects)
|
|
22417
23326
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
22418
23327
|
const resolved = at(file);
|
|
@@ -22422,15 +23331,15 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
22422
23331
|
const edits = editsOf(segment.words);
|
|
22423
23332
|
if (edits === null) {
|
|
22424
23333
|
if (dir !== undefined)
|
|
22425
|
-
targets.push(
|
|
23334
|
+
targets.push(path30.join(dir, DIR_TARGET));
|
|
22426
23335
|
} else
|
|
22427
23336
|
for (const file of edits) {
|
|
22428
23337
|
const resolved = at(file);
|
|
22429
23338
|
if (resolved)
|
|
22430
23339
|
targets.push(resolved);
|
|
22431
23340
|
}
|
|
22432
|
-
if (dir !== undefined && workitInvocations(segment.words.map(
|
|
22433
|
-
targets.push(
|
|
23341
|
+
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), "")))
|
|
23342
|
+
targets.push(path30.join(dir, DIR_TARGET));
|
|
22434
23343
|
}
|
|
22435
23344
|
return targets;
|
|
22436
23345
|
};
|
|
@@ -22465,7 +23374,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
22465
23374
|
var guarded = (targets) => {
|
|
22466
23375
|
const seen = new Set;
|
|
22467
23376
|
return targets.filter((target) => {
|
|
22468
|
-
const dir =
|
|
23377
|
+
const dir = path30.dirname(target);
|
|
22469
23378
|
if (seen.has(dir))
|
|
22470
23379
|
return false;
|
|
22471
23380
|
seen.add(dir);
|
|
@@ -22486,7 +23395,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
22486
23395
|
for (const target of guarded(targets))
|
|
22487
23396
|
claimLiveSession(target, owner, deps);
|
|
22488
23397
|
};
|
|
22489
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
23398
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path30.resolve(cwd, file)) : [path30.join(cwd, DIR_TARGET)];
|
|
22490
23399
|
function writeGate(cwd, paths, owner, deps) {
|
|
22491
23400
|
if (!owner)
|
|
22492
23401
|
return beforeWriteGate(cwd, paths);
|
|
@@ -22506,7 +23415,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
22506
23415
|
try {
|
|
22507
23416
|
targets = [
|
|
22508
23417
|
...segmentTargets(cwd, command, dialect),
|
|
22509
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
23418
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path30.join(dir, DIR_TARGET))
|
|
22510
23419
|
];
|
|
22511
23420
|
} catch {
|
|
22512
23421
|
targets = [];
|
|
@@ -22940,7 +23849,7 @@ ${line}` };
|
|
|
22940
23849
|
};
|
|
22941
23850
|
var shellNudge = (input, command) => withContextLine(rawGitPre(input, command), shipNudge(input, command));
|
|
22942
23851
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
22943
|
-
var
|
|
23852
|
+
var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
22944
23853
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
22945
23854
|
var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
22946
23855
|
var WRITE_TOOLS = new Set([
|
|
@@ -22965,7 +23874,7 @@ var WRITE_TOOLS = new Set([
|
|
|
22965
23874
|
var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
|
|
22966
23875
|
var PATCH_HEADER = /^[ \t]*\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm;
|
|
22967
23876
|
var writePaths = (input) => {
|
|
22968
|
-
if (!
|
|
23877
|
+
if (!isRecord6(input))
|
|
22969
23878
|
return [];
|
|
22970
23879
|
const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
|
|
22971
23880
|
for (const value of Object.values(input))
|
|
@@ -23056,10 +23965,32 @@ function handleHook(input, deps) {
|
|
|
23056
23965
|
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
|
|
23057
23966
|
if (gate.kind === "deny")
|
|
23058
23967
|
return gate;
|
|
23968
|
+
const plan = heavyGate(input.cwd, event.command, {
|
|
23969
|
+
host: input.host,
|
|
23970
|
+
dialect: event.dialect,
|
|
23971
|
+
permissionMode: input.permissionMode
|
|
23972
|
+
});
|
|
23973
|
+
if (plan.kind === "split" && canDeny)
|
|
23974
|
+
return splitDenial(plan);
|
|
23975
|
+
const rewrites = usable2(descriptor.shellPolicy.rewrite);
|
|
23976
|
+
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
23977
|
+
return heavyDenial(event.command, plan);
|
|
23978
|
+
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
23979
|
+
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
23059
23980
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
23981
|
+
const rewritten = (text) => lease ? {
|
|
23982
|
+
kind: "rewrite",
|
|
23983
|
+
command: lease.command,
|
|
23984
|
+
input: event.toolInput ? { ...event.toolInput, command: lease.command } : null,
|
|
23985
|
+
text
|
|
23986
|
+
} : NONE5;
|
|
23060
23987
|
if (descriptor.transport !== "hook-process")
|
|
23061
|
-
return
|
|
23062
|
-
|
|
23988
|
+
return rewritten(null);
|
|
23989
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
|
|
23990
|
+
if (!lease)
|
|
23991
|
+
return nudged;
|
|
23992
|
+
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
23993
|
+
`));
|
|
23063
23994
|
}
|
|
23064
23995
|
case "shell.post":
|
|
23065
23996
|
if (usable2(descriptor.events["shell.post"].support))
|
|
@@ -23088,6 +24019,18 @@ function handleHook(input, deps) {
|
|
|
23088
24019
|
return NONE5;
|
|
23089
24020
|
}
|
|
23090
24021
|
}
|
|
24022
|
+
// packages/workit-core/src/hooks/deadline.ts
|
|
24023
|
+
var QUIET = new Set([
|
|
24024
|
+
"stop",
|
|
24025
|
+
"subagent.stop",
|
|
24026
|
+
"shell.post",
|
|
24027
|
+
"compact.pre",
|
|
24028
|
+
"prompt.submit",
|
|
24029
|
+
null
|
|
24030
|
+
]);
|
|
24031
|
+
var TIMED_OUT = Symbol("timed out");
|
|
24032
|
+
var PROCESS_START = performance.timeOrigin;
|
|
24033
|
+
|
|
23091
24034
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
23092
24035
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
23093
24036
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
@@ -23129,7 +24072,7 @@ var PI_DESCRIPTOR = {
|
|
|
23129
24072
|
"compact.pre": { support: "native", native: "session_before_compact" },
|
|
23130
24073
|
stop: { support: "native", native: "agent_end" }
|
|
23131
24074
|
},
|
|
23132
|
-
shellPolicy: { deny: "native", channel: "block", failClosed: false },
|
|
24075
|
+
shellPolicy: { deny: "native", channel: "block", failClosed: false, rewrite: "native" },
|
|
23133
24076
|
context: {
|
|
23134
24077
|
sessionStart: "native",
|
|
23135
24078
|
perTurn: "native",
|
|
@@ -23323,6 +24266,8 @@ var enforceToolPolicy = (event, ctx) => {
|
|
|
23323
24266
|
descriptor: PI_DESCRIPTOR,
|
|
23324
24267
|
addendum: null
|
|
23325
24268
|
});
|
|
24269
|
+
if (decision.kind === "rewrite" && event.toolName === "bash")
|
|
24270
|
+
event.input.command = decision.command;
|
|
23326
24271
|
return decision.kind === "deny" ? { block: true, reason: decision.reason } : undefined;
|
|
23327
24272
|
};
|
|
23328
24273
|
if (event.toolName === "bash") {
|
|
@@ -23372,13 +24317,13 @@ var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? n
|
|
|
23372
24317
|
|
|
23373
24318
|
// packages/workit-pi/src/launches.ts
|
|
23374
24319
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
23375
|
-
import
|
|
23376
|
-
import
|
|
24320
|
+
import os11 from "node:os";
|
|
24321
|
+
import path31 from "node:path";
|
|
23377
24322
|
var launchDir = (root) => {
|
|
23378
24323
|
const location = resolveStore(root);
|
|
23379
|
-
return location instanceof Error ? null :
|
|
24324
|
+
return location instanceof Error ? null : path31.join(location.dir, "pi-launches");
|
|
23380
24325
|
};
|
|
23381
|
-
var fileFor = (dir, workerId) =>
|
|
24326
|
+
var fileFor = (dir, workerId) => path31.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
23382
24327
|
var recordLaunch = (root, attempt) => {
|
|
23383
24328
|
const dir = launchDir(root);
|
|
23384
24329
|
if (!dir)
|
|
@@ -23388,7 +24333,7 @@ var recordLaunch = (root, attempt) => {
|
|
|
23388
24333
|
writeFileSync3(fileFor(dir, attempt.workerId), JSON.stringify({
|
|
23389
24334
|
...attempt,
|
|
23390
24335
|
pid: process.pid,
|
|
23391
|
-
host:
|
|
24336
|
+
host: os11.hostname(),
|
|
23392
24337
|
at: new Date().toISOString()
|
|
23393
24338
|
}), { flag: "wx" });
|
|
23394
24339
|
return true;
|
|
@@ -23402,7 +24347,7 @@ var clearLaunch = (root, workerId) => {
|
|
|
23402
24347
|
rmSync2(fileFor(dir, workerId), { force: true });
|
|
23403
24348
|
};
|
|
23404
24349
|
var alive = (attempt) => {
|
|
23405
|
-
if (attempt.host !==
|
|
24350
|
+
if (attempt.host !== os11.hostname())
|
|
23406
24351
|
return true;
|
|
23407
24352
|
try {
|
|
23408
24353
|
process.kill(attempt.pid, 0);
|
|
@@ -23424,7 +24369,7 @@ var abandonedLaunches = (root) => {
|
|
|
23424
24369
|
const found = [];
|
|
23425
24370
|
for (const name of names) {
|
|
23426
24371
|
try {
|
|
23427
|
-
const attempt = JSON.parse(readFileSync6(
|
|
24372
|
+
const attempt = JSON.parse(readFileSync6(path31.join(dir, name), "utf8"));
|
|
23428
24373
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
23429
24374
|
if (!alive(attempt))
|
|
23430
24375
|
found.push(attempt);
|
|
@@ -23442,9 +24387,9 @@ import { randomUUID as randomUUID4 } from "node:crypto";
|
|
|
23442
24387
|
// packages/workit-pi/src/worker-protocol.ts
|
|
23443
24388
|
var MAX_WORKER_LINE_BYTES = 64 * 1024;
|
|
23444
24389
|
var MAX_WORKER_STDERR_BYTES = 64 * 1024;
|
|
23445
|
-
var
|
|
24390
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null;
|
|
23446
24391
|
var parseEvent2 = (value) => {
|
|
23447
|
-
if (!
|
|
24392
|
+
if (!isRecord7(value) || typeof value.type !== "string")
|
|
23448
24393
|
return null;
|
|
23449
24394
|
if (value.type === "workit_worker_ready") {
|
|
23450
24395
|
if (typeof value.workerId !== "string" || typeof value.sessionId !== "string")
|