@brainervirus/workit-pi 8.15.1 → 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/dist/worker.js +2 -0
- package/dist/workit.js +1061 -65
- 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(),
|
|
@@ -16023,6 +16035,58 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
16023
16035
|
requirementIds: evaluations.map((item) => item.requirementId)
|
|
16024
16036
|
});
|
|
16025
16037
|
}
|
|
16038
|
+
var BRIEF_TEXT = 250;
|
|
16039
|
+
var BRIEF_BLOCKERS = 3;
|
|
16040
|
+
var BRIEF_OPEN = 8;
|
|
16041
|
+
var clip = (value) => {
|
|
16042
|
+
const fits = (text) => Buffer.byteLength(JSON.stringify(text)) <= BRIEF_TEXT;
|
|
16043
|
+
if (fits(value))
|
|
16044
|
+
return value;
|
|
16045
|
+
let length = Math.min(value.length, BRIEF_TEXT);
|
|
16046
|
+
while (length > 0 && !fits(`${value.slice(0, length)}…`))
|
|
16047
|
+
length -= 1;
|
|
16048
|
+
return `${value.slice(0, length)}…`;
|
|
16049
|
+
};
|
|
16050
|
+
function boundTaskSummary(summary) {
|
|
16051
|
+
const rules = new Map((summary.policy?.requirements ?? []).map((item) => [item.id, item]));
|
|
16052
|
+
const unmet = summary.requirements.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
16053
|
+
const { progress, closure } = summary;
|
|
16054
|
+
const next = progress.nextAction;
|
|
16055
|
+
const open = unmet.slice(0, BRIEF_OPEN).map((item) => ({
|
|
16056
|
+
dimension: rules.get(item.requirementId)?.dimension ?? "verification",
|
|
16057
|
+
before: rules.get(item.requirementId)?.before ?? "close",
|
|
16058
|
+
status: item.status
|
|
16059
|
+
}));
|
|
16060
|
+
const brief = {
|
|
16061
|
+
id: summary.id,
|
|
16062
|
+
revision: summary.revision,
|
|
16063
|
+
workspaceRevision: summary.workspaceRevision,
|
|
16064
|
+
updatedAt: summary.updatedAt,
|
|
16065
|
+
objective: clip(summary.objective),
|
|
16066
|
+
status: summary.status,
|
|
16067
|
+
closure: closure && {
|
|
16068
|
+
outcome: closure.outcome,
|
|
16069
|
+
at: closure.at,
|
|
16070
|
+
summary: clip(closure.summary)
|
|
16071
|
+
},
|
|
16072
|
+
progress: {
|
|
16073
|
+
summary: clip(progress.summary),
|
|
16074
|
+
nextAction: next && clip(next),
|
|
16075
|
+
blockers: progress.blockers.slice(0, BRIEF_BLOCKERS).map((item) => ({
|
|
16076
|
+
reason: clip(item.reason),
|
|
16077
|
+
dependentAction: clip(item.dependentAction)
|
|
16078
|
+
}))
|
|
16079
|
+
},
|
|
16080
|
+
requirements: { total: summary.requirements.length, unmet: unmet.length, open },
|
|
16081
|
+
truncated: false
|
|
16082
|
+
};
|
|
16083
|
+
brief.truncated = JSON.stringify(brief).includes("…") || progress.blockers.length > BRIEF_BLOCKERS || unmet.length > BRIEF_OPEN;
|
|
16084
|
+
return brief;
|
|
16085
|
+
}
|
|
16086
|
+
function briefInspectResult(request, result) {
|
|
16087
|
+
const { action, view } = request ?? {};
|
|
16088
|
+
return result.ok && action === "inspect" && (view ?? "summary") === "summary" ? { ...result, data: boundTaskSummary(result.data) } : result;
|
|
16089
|
+
}
|
|
16026
16090
|
|
|
16027
16091
|
// packages/workit-core/src/core/methods.ts
|
|
16028
16092
|
var METHODS = {
|
|
@@ -17416,7 +17480,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
17416
17480
|
return found;
|
|
17417
17481
|
}
|
|
17418
17482
|
var RECENT = new Map;
|
|
17419
|
-
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) {
|
|
17420
17493
|
const found = [];
|
|
17421
17494
|
let dir = null;
|
|
17422
17495
|
const groups = [];
|
|
@@ -17427,7 +17500,8 @@ function walk2(command, dialect, depth) {
|
|
|
17427
17500
|
const nested = (script, inner) => {
|
|
17428
17501
|
if (!script || depth >= MAX_DEPTH)
|
|
17429
17502
|
return;
|
|
17430
|
-
|
|
17503
|
+
const inside = seen ? walk2(script, inner, depth + 1, seen) : rawInvocations(script, inner, depth + 1);
|
|
17504
|
+
for (const invocation of inside)
|
|
17431
17505
|
found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
|
|
17432
17506
|
};
|
|
17433
17507
|
const visit = (input, stdin, hops) => {
|
|
@@ -17439,11 +17513,17 @@ function walk2(command, dialect, depth) {
|
|
|
17439
17513
|
const name = commandName(words[0]);
|
|
17440
17514
|
if (!name)
|
|
17441
17515
|
return;
|
|
17516
|
+
seen?.push(words);
|
|
17517
|
+
if (name === "-c" || name === "--command")
|
|
17518
|
+
return nested(words[1], "posix");
|
|
17442
17519
|
const runs = (lines) => {
|
|
17443
17520
|
if (hops < MAX_DEPTH)
|
|
17444
17521
|
for (const next of lines)
|
|
17445
17522
|
visit(next, undefined, hops + 1);
|
|
17446
17523
|
};
|
|
17524
|
+
const wrapped = seen ? null : workitCheckTail(words);
|
|
17525
|
+
if (wrapped)
|
|
17526
|
+
return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
|
|
17447
17527
|
if (CD.has(name)) {
|
|
17448
17528
|
const target = cdTarget(words);
|
|
17449
17529
|
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
@@ -17534,6 +17614,51 @@ var LAUNCHERS = new Set([
|
|
|
17534
17614
|
"command"
|
|
17535
17615
|
]);
|
|
17536
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
|
+
}
|
|
17537
17662
|
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
17538
17663
|
const found = [];
|
|
17539
17664
|
for (const { words } of segmentsOf(command, dialect)) {
|
|
@@ -19460,6 +19585,15 @@ class WorkitCore {
|
|
|
19460
19585
|
if (!parsed.ok)
|
|
19461
19586
|
return parsed;
|
|
19462
19587
|
const input = parsed.data;
|
|
19588
|
+
if (this.context.readOnlyChild) {
|
|
19589
|
+
if (input.action !== "inspect")
|
|
19590
|
+
return failure2("permission_denied", "child sessions can only inspect the task summary");
|
|
19591
|
+
const task = this.store.readTask(input.taskId);
|
|
19592
|
+
if (!task.ok)
|
|
19593
|
+
return task;
|
|
19594
|
+
const summary = this.summary(task.data);
|
|
19595
|
+
return summary.ok ? { ...summary, data: boundTaskSummary(summary.data) } : summary;
|
|
19596
|
+
}
|
|
19463
19597
|
if ((this.context.workerId ?? null) !== null) {
|
|
19464
19598
|
if (input.action !== "inspect" && input.action !== "list")
|
|
19465
19599
|
return failure2("permission_denied", "helpers cannot control task lifecycle or scope");
|
|
@@ -21903,17 +22037,827 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21903
22037
|
}
|
|
21904
22038
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
21905
22039
|
};
|
|
21906
|
-
// packages/workit-core/src/
|
|
21907
|
-
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";
|
|
21908
22357
|
import os7 from "node:os";
|
|
21909
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";
|
|
21910
22854
|
|
|
21911
22855
|
// packages/workit-core/src/core/live-session.ts
|
|
21912
22856
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21913
22857
|
import { createHash as createHash15 } from "node:crypto";
|
|
21914
|
-
import
|
|
21915
|
-
import
|
|
21916
|
-
import
|
|
22858
|
+
import fs22 from "node:fs";
|
|
22859
|
+
import os9 from "node:os";
|
|
22860
|
+
import path29 from "node:path";
|
|
21917
22861
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21918
22862
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
21919
22863
|
var liveProbe = {
|
|
@@ -21934,9 +22878,9 @@ var processAlive = (pid) => {
|
|
|
21934
22878
|
var processInfo = (pid) => {
|
|
21935
22879
|
try {
|
|
21936
22880
|
if (liveProbe.platform === "linux") {
|
|
21937
|
-
const stat =
|
|
22881
|
+
const stat = fs22.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
21938
22882
|
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
21939
|
-
const command =
|
|
22883
|
+
const command = fs22.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
21940
22884
|
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
21941
22885
|
}
|
|
21942
22886
|
if (liveProbe.platform === "win32")
|
|
@@ -21987,35 +22931,35 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
21987
22931
|
};
|
|
21988
22932
|
};
|
|
21989
22933
|
var checkoutOf = (target) => {
|
|
21990
|
-
let dir =
|
|
21991
|
-
while (!
|
|
21992
|
-
dir =
|
|
21993
|
-
if (
|
|
21994
|
-
dir =
|
|
21995
|
-
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);
|
|
21996
22940
|
const dotGit = parts.lastIndexOf(".git");
|
|
21997
22941
|
if (dotGit > 0)
|
|
21998
|
-
dir = parts.slice(0, dotGit).join(
|
|
22942
|
+
dir = parts.slice(0, dotGit).join(path29.sep) || path29.sep;
|
|
21999
22943
|
const location = resolveStore(dir);
|
|
22000
22944
|
if (location instanceof Error || !location.shared || !location.top)
|
|
22001
22945
|
return null;
|
|
22002
22946
|
let top = location.top;
|
|
22003
22947
|
try {
|
|
22004
|
-
top =
|
|
22948
|
+
top = fs22.realpathSync.native(top);
|
|
22005
22949
|
} catch {}
|
|
22006
22950
|
return top;
|
|
22007
22951
|
};
|
|
22008
|
-
var markerFile2 = (top) =>
|
|
22952
|
+
var markerFile2 = (top) => path29.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
22009
22953
|
var checkoutKey = (top, platform = process.platform) => {
|
|
22010
|
-
let key =
|
|
22954
|
+
let key = path29.resolve(top);
|
|
22011
22955
|
try {
|
|
22012
|
-
key =
|
|
22956
|
+
key = fs22.realpathSync.native(key);
|
|
22013
22957
|
} catch {}
|
|
22014
22958
|
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
22015
22959
|
};
|
|
22016
22960
|
var readMarker = (file) => {
|
|
22017
22961
|
try {
|
|
22018
|
-
const value = JSON.parse(
|
|
22962
|
+
const value = JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
22019
22963
|
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
22020
22964
|
return null;
|
|
22021
22965
|
return {
|
|
@@ -22038,7 +22982,7 @@ var isLive = (marker, deps) => {
|
|
|
22038
22982
|
const age = ageOf2(marker, deps.now ?? new Date);
|
|
22039
22983
|
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
22040
22984
|
return false;
|
|
22041
|
-
if (marker.machine !== (deps.machine ??
|
|
22985
|
+
if (marker.machine !== (deps.machine ?? os9.hostname()) || marker.pid === null)
|
|
22042
22986
|
return true;
|
|
22043
22987
|
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
22044
22988
|
return false;
|
|
@@ -22067,7 +23011,7 @@ var liveConflict = (target, owner, deps = {}) => {
|
|
|
22067
23011
|
const marker = readMarker(file);
|
|
22068
23012
|
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
22069
23013
|
return null;
|
|
22070
|
-
if (sameOwner(marker, owner, deps.machine ??
|
|
23014
|
+
if (sameOwner(marker, owner, deps.machine ?? os9.hostname()))
|
|
22071
23015
|
return null;
|
|
22072
23016
|
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
22073
23017
|
} catch {
|
|
@@ -22081,7 +23025,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
22081
23025
|
return;
|
|
22082
23026
|
const file = markerFile2(checkout);
|
|
22083
23027
|
const now = deps.now ?? new Date;
|
|
22084
|
-
const machine = deps.machine ??
|
|
23028
|
+
const machine = deps.machine ?? os9.hostname();
|
|
22085
23029
|
const current = readMarker(file);
|
|
22086
23030
|
if (current) {
|
|
22087
23031
|
const age = ageOf2(current, now);
|
|
@@ -22102,10 +23046,10 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
22102
23046
|
checkout,
|
|
22103
23047
|
heartbeatAt: now.toISOString()
|
|
22104
23048
|
};
|
|
22105
|
-
|
|
23049
|
+
fs22.mkdirSync(path29.dirname(file), { recursive: true });
|
|
22106
23050
|
const temp = `${file}.${process.pid}.tmp`;
|
|
22107
|
-
|
|
22108
|
-
|
|
23051
|
+
fs22.writeFileSync(temp, JSON.stringify(marker));
|
|
23052
|
+
fs22.renameSync(temp, file);
|
|
22109
23053
|
} catch {}
|
|
22110
23054
|
};
|
|
22111
23055
|
|
|
@@ -22114,27 +23058,42 @@ var NONE3 = { kind: "none" };
|
|
|
22114
23058
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
22115
23059
|
var canonical2 = (file) => {
|
|
22116
23060
|
let rest = "";
|
|
22117
|
-
for (let dir =
|
|
23061
|
+
for (let dir = path30.resolve(file);; dir = path30.dirname(dir)) {
|
|
22118
23062
|
try {
|
|
22119
|
-
return
|
|
23063
|
+
return path30.join(realpathSync6(dir), rest);
|
|
22120
23064
|
} catch {
|
|
22121
|
-
if (
|
|
22122
|
-
return
|
|
22123
|
-
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);
|
|
22124
23068
|
}
|
|
22125
23069
|
}
|
|
22126
23070
|
};
|
|
22127
23071
|
var exempt = (cwd, root, file, plans) => {
|
|
22128
23072
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
22129
|
-
const relative =
|
|
22130
|
-
if (relative.startsWith("..") ||
|
|
23073
|
+
const relative = path30.relative(fold(canonical2(root)), fold(canonical2(path30.resolve(cwd, file))));
|
|
23074
|
+
if (relative.startsWith("..") || path30.isAbsolute(relative))
|
|
22131
23075
|
return true;
|
|
22132
|
-
const posix = relative.split(
|
|
23076
|
+
const posix = relative.split(path30.sep).join("/");
|
|
22133
23077
|
const segments = posix.split("/");
|
|
22134
23078
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
22135
23079
|
};
|
|
22136
23080
|
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
22137
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
|
+
};
|
|
22138
23097
|
var editsOf = (input) => {
|
|
22139
23098
|
let words = input;
|
|
22140
23099
|
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
@@ -22211,10 +23170,18 @@ var editsOf = (input) => {
|
|
|
22211
23170
|
return [];
|
|
22212
23171
|
}
|
|
22213
23172
|
};
|
|
22214
|
-
var shellWrites = (command) => {
|
|
23173
|
+
var shellWrites = (command, depth = 0) => {
|
|
22215
23174
|
const targets = [];
|
|
22216
23175
|
let unknown = false;
|
|
22217
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
|
+
}
|
|
22218
23185
|
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
22219
23186
|
const edits = editsOf(segment.words);
|
|
22220
23187
|
if (edits === null)
|
|
@@ -22227,7 +23194,7 @@ var shellWrites = (command) => {
|
|
|
22227
23194
|
return { writes: true, targets: unknown ? null : targets };
|
|
22228
23195
|
};
|
|
22229
23196
|
var cache2 = new Map;
|
|
22230
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
23197
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path30.resolve(root, file)) ?? "-").join("\x00");
|
|
22231
23198
|
var gateFor = (cwd) => {
|
|
22232
23199
|
const store = new TaskStore(cwd);
|
|
22233
23200
|
const listed = store.listTaskIndex();
|
|
@@ -22306,7 +23273,7 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
22306
23273
|
return [];
|
|
22307
23274
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
22308
23275
|
return [];
|
|
22309
|
-
return [
|
|
23276
|
+
return [path30.resolve(cwd, invocation.dir ?? ".")];
|
|
22310
23277
|
});
|
|
22311
23278
|
};
|
|
22312
23279
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
@@ -22316,15 +23283,15 @@ var cdInto = (dir, words) => {
|
|
|
22316
23283
|
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
22317
23284
|
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
22318
23285
|
return;
|
|
22319
|
-
return target === undefined ?
|
|
23286
|
+
return target === undefined ? os10.homedir() : path30.resolve(dir, target);
|
|
22320
23287
|
};
|
|
22321
|
-
var
|
|
22322
|
-
var segmentTargets = (cwd, command, dialect) => {
|
|
23288
|
+
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
23289
|
+
var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
22323
23290
|
const targets = [];
|
|
22324
23291
|
let dir = cwd;
|
|
22325
23292
|
let stack = [];
|
|
22326
23293
|
const groups = [];
|
|
22327
|
-
const at = (file) =>
|
|
23294
|
+
const at = (file) => path30.isAbsolute(file) ? file : dir === undefined ? null : path30.resolve(dir, file);
|
|
22328
23295
|
for (const segment of segmentsOf(command, dialect)) {
|
|
22329
23296
|
if (segment.group === "open") {
|
|
22330
23297
|
groups.push({ dir, stack: [...stack] });
|
|
@@ -22352,6 +23319,9 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
22352
23319
|
dir = stack.length ? stack.pop() : undefined;
|
|
22353
23320
|
continue;
|
|
22354
23321
|
}
|
|
23322
|
+
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
23323
|
+
if (inner !== null && dir !== undefined)
|
|
23324
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
22355
23325
|
for (const file of segment.redirects)
|
|
22356
23326
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
22357
23327
|
const resolved = at(file);
|
|
@@ -22361,15 +23331,15 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
22361
23331
|
const edits = editsOf(segment.words);
|
|
22362
23332
|
if (edits === null) {
|
|
22363
23333
|
if (dir !== undefined)
|
|
22364
|
-
targets.push(
|
|
23334
|
+
targets.push(path30.join(dir, DIR_TARGET));
|
|
22365
23335
|
} else
|
|
22366
23336
|
for (const file of edits) {
|
|
22367
23337
|
const resolved = at(file);
|
|
22368
23338
|
if (resolved)
|
|
22369
23339
|
targets.push(resolved);
|
|
22370
23340
|
}
|
|
22371
|
-
if (dir !== undefined && workitInvocations(segment.words.map(
|
|
22372
|
-
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));
|
|
22373
23343
|
}
|
|
22374
23344
|
return targets;
|
|
22375
23345
|
};
|
|
@@ -22404,7 +23374,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
22404
23374
|
var guarded = (targets) => {
|
|
22405
23375
|
const seen = new Set;
|
|
22406
23376
|
return targets.filter((target) => {
|
|
22407
|
-
const dir =
|
|
23377
|
+
const dir = path30.dirname(target);
|
|
22408
23378
|
if (seen.has(dir))
|
|
22409
23379
|
return false;
|
|
22410
23380
|
seen.add(dir);
|
|
@@ -22425,7 +23395,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
22425
23395
|
for (const target of guarded(targets))
|
|
22426
23396
|
claimLiveSession(target, owner, deps);
|
|
22427
23397
|
};
|
|
22428
|
-
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)];
|
|
22429
23399
|
function writeGate(cwd, paths, owner, deps) {
|
|
22430
23400
|
if (!owner)
|
|
22431
23401
|
return beforeWriteGate(cwd, paths);
|
|
@@ -22445,7 +23415,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
22445
23415
|
try {
|
|
22446
23416
|
targets = [
|
|
22447
23417
|
...segmentTargets(cwd, command, dialect),
|
|
22448
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
23418
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path30.join(dir, DIR_TARGET))
|
|
22449
23419
|
];
|
|
22450
23420
|
} catch {
|
|
22451
23421
|
targets = [];
|
|
@@ -22879,7 +23849,7 @@ ${line}` };
|
|
|
22879
23849
|
};
|
|
22880
23850
|
var shellNudge = (input, command) => withContextLine(rawGitPre(input, command), shipNudge(input, command));
|
|
22881
23851
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
22882
|
-
var
|
|
23852
|
+
var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
22883
23853
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
22884
23854
|
var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
22885
23855
|
var WRITE_TOOLS = new Set([
|
|
@@ -22904,7 +23874,7 @@ var WRITE_TOOLS = new Set([
|
|
|
22904
23874
|
var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
|
|
22905
23875
|
var PATCH_HEADER = /^[ \t]*\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm;
|
|
22906
23876
|
var writePaths = (input) => {
|
|
22907
|
-
if (!
|
|
23877
|
+
if (!isRecord6(input))
|
|
22908
23878
|
return [];
|
|
22909
23879
|
const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
|
|
22910
23880
|
for (const value of Object.values(input))
|
|
@@ -22995,10 +23965,32 @@ function handleHook(input, deps) {
|
|
|
22995
23965
|
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
|
|
22996
23966
|
if (gate.kind === "deny")
|
|
22997
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;
|
|
22998
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;
|
|
22999
23987
|
if (descriptor.transport !== "hook-process")
|
|
23000
|
-
return
|
|
23001
|
-
|
|
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
|
+
`));
|
|
23002
23994
|
}
|
|
23003
23995
|
case "shell.post":
|
|
23004
23996
|
if (usable2(descriptor.events["shell.post"].support))
|
|
@@ -23068,7 +24060,7 @@ var PI_DESCRIPTOR = {
|
|
|
23068
24060
|
"compact.pre": { support: "native", native: "session_before_compact" },
|
|
23069
24061
|
stop: { support: "native", native: "agent_end" }
|
|
23070
24062
|
},
|
|
23071
|
-
shellPolicy: { deny: "native", channel: "block", failClosed: false },
|
|
24063
|
+
shellPolicy: { deny: "native", channel: "block", failClosed: false, rewrite: "native" },
|
|
23072
24064
|
context: {
|
|
23073
24065
|
sessionStart: "native",
|
|
23074
24066
|
perTurn: "native",
|
|
@@ -23211,9 +24203,11 @@ var executeFamily = async (family, input, ctx) => {
|
|
|
23211
24203
|
const trust = trustedForMutation(ctx, parsed.data.action);
|
|
23212
24204
|
if (!trust.ok)
|
|
23213
24205
|
return output(trust);
|
|
23214
|
-
const
|
|
24206
|
+
const context = piContext(ctx);
|
|
24207
|
+
const core = new WorkitCore(new TaskStore(ctx.cwd), context);
|
|
23215
24208
|
const run = core[family];
|
|
23216
|
-
|
|
24209
|
+
const result = run.call(core, parsed.data);
|
|
24210
|
+
return output(family === "task" && (context.workerId ?? null) !== null ? briefInspectResult(parsed.data, result) : result);
|
|
23217
24211
|
};
|
|
23218
24212
|
var registerWorkitTools = (pi, options = {}) => {
|
|
23219
24213
|
for (const family of OPERATION_FAMILIES)
|
|
@@ -23260,6 +24254,8 @@ var enforceToolPolicy = (event, ctx) => {
|
|
|
23260
24254
|
descriptor: PI_DESCRIPTOR,
|
|
23261
24255
|
addendum: null
|
|
23262
24256
|
});
|
|
24257
|
+
if (decision.kind === "rewrite" && event.toolName === "bash")
|
|
24258
|
+
event.input.command = decision.command;
|
|
23263
24259
|
return decision.kind === "deny" ? { block: true, reason: decision.reason } : undefined;
|
|
23264
24260
|
};
|
|
23265
24261
|
if (event.toolName === "bash") {
|
|
@@ -23309,13 +24305,13 @@ var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? n
|
|
|
23309
24305
|
|
|
23310
24306
|
// packages/workit-pi/src/launches.ts
|
|
23311
24307
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
23312
|
-
import
|
|
23313
|
-
import
|
|
24308
|
+
import os11 from "node:os";
|
|
24309
|
+
import path31 from "node:path";
|
|
23314
24310
|
var launchDir = (root) => {
|
|
23315
24311
|
const location = resolveStore(root);
|
|
23316
|
-
return location instanceof Error ? null :
|
|
24312
|
+
return location instanceof Error ? null : path31.join(location.dir, "pi-launches");
|
|
23317
24313
|
};
|
|
23318
|
-
var fileFor = (dir, workerId) =>
|
|
24314
|
+
var fileFor = (dir, workerId) => path31.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
23319
24315
|
var recordLaunch = (root, attempt) => {
|
|
23320
24316
|
const dir = launchDir(root);
|
|
23321
24317
|
if (!dir)
|
|
@@ -23325,7 +24321,7 @@ var recordLaunch = (root, attempt) => {
|
|
|
23325
24321
|
writeFileSync3(fileFor(dir, attempt.workerId), JSON.stringify({
|
|
23326
24322
|
...attempt,
|
|
23327
24323
|
pid: process.pid,
|
|
23328
|
-
host:
|
|
24324
|
+
host: os11.hostname(),
|
|
23329
24325
|
at: new Date().toISOString()
|
|
23330
24326
|
}), { flag: "wx" });
|
|
23331
24327
|
return true;
|
|
@@ -23339,7 +24335,7 @@ var clearLaunch = (root, workerId) => {
|
|
|
23339
24335
|
rmSync2(fileFor(dir, workerId), { force: true });
|
|
23340
24336
|
};
|
|
23341
24337
|
var alive = (attempt) => {
|
|
23342
|
-
if (attempt.host !==
|
|
24338
|
+
if (attempt.host !== os11.hostname())
|
|
23343
24339
|
return true;
|
|
23344
24340
|
try {
|
|
23345
24341
|
process.kill(attempt.pid, 0);
|
|
@@ -23361,7 +24357,7 @@ var abandonedLaunches = (root) => {
|
|
|
23361
24357
|
const found = [];
|
|
23362
24358
|
for (const name of names) {
|
|
23363
24359
|
try {
|
|
23364
|
-
const attempt = JSON.parse(readFileSync6(
|
|
24360
|
+
const attempt = JSON.parse(readFileSync6(path31.join(dir, name), "utf8"));
|
|
23365
24361
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
23366
24362
|
if (!alive(attempt))
|
|
23367
24363
|
found.push(attempt);
|
|
@@ -23379,9 +24375,9 @@ import { randomUUID as randomUUID4 } from "node:crypto";
|
|
|
23379
24375
|
// packages/workit-pi/src/worker-protocol.ts
|
|
23380
24376
|
var MAX_WORKER_LINE_BYTES = 64 * 1024;
|
|
23381
24377
|
var MAX_WORKER_STDERR_BYTES = 64 * 1024;
|
|
23382
|
-
var
|
|
24378
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null;
|
|
23383
24379
|
var parseEvent2 = (value) => {
|
|
23384
|
-
if (!
|
|
24380
|
+
if (!isRecord7(value) || typeof value.type !== "string")
|
|
23385
24381
|
return null;
|
|
23386
24382
|
if (value.type === "workit_worker_ready") {
|
|
23387
24383
|
if (typeof value.workerId !== "string" || typeof value.sessionId !== "string")
|