@brainervirus/workit-opencode 8.16.1 → 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/assets/skills/workit-fanout/SKILL.md +7 -7
- package/assets/skills/workit-fanout/references/brief.md +3 -1
- package/assets/skills/workit-implement/SKILL.md +8 -1
- package/assets/skills/workit-review/SKILL.md +2 -1
- package/assets/skills/workit-verify-app/SKILL.md +2 -0
- package/dist/plugin.js +1082 -139
- package/package.json +2 -2
package/dist/plugin.js
CHANGED
|
@@ -12398,6 +12398,17 @@ import fs9 from "node:fs";
|
|
|
12398
12398
|
import path9 from "node:path";
|
|
12399
12399
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
12400
12400
|
var CHECKS_FILE = "workit.checks.json";
|
|
12401
|
+
var checksConfigFile = (root) => path9.join(root, CHECKS_FILE);
|
|
12402
|
+
function repoHeavyCommands(root) {
|
|
12403
|
+
try {
|
|
12404
|
+
const value = JSON.parse(fs9.readFileSync(checksConfigFile(root), "utf8"));
|
|
12405
|
+
const resources = isRecord(value) ? value.resources : undefined;
|
|
12406
|
+
const list = isRecord(resources) ? resources.heavyCommands : undefined;
|
|
12407
|
+
return Array.isArray(list) ? list.filter((item) => typeof item === "string") : [];
|
|
12408
|
+
} catch {
|
|
12409
|
+
return [];
|
|
12410
|
+
}
|
|
12411
|
+
}
|
|
12401
12412
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12402
12413
|
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
12403
12414
|
function splitCommand(command) {
|
|
@@ -12510,7 +12521,7 @@ function detectedChecks(root) {
|
|
|
12510
12521
|
var GATES = ["testing", "verification"];
|
|
12511
12522
|
function loadCheckConfig(cwd) {
|
|
12512
12523
|
const root = checkRoot(cwd);
|
|
12513
|
-
const file =
|
|
12524
|
+
const file = checksConfigFile(root);
|
|
12514
12525
|
const none = (error = null) => ({
|
|
12515
12526
|
source: error ? CHECKS_FILE : "none",
|
|
12516
12527
|
root,
|
|
@@ -13255,6 +13266,7 @@ var workspaceConfigSchema = object({
|
|
|
13255
13266
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
13256
13267
|
defaultEndpoint: unknown().optional(),
|
|
13257
13268
|
verification: _enum(["self", "independent"]).optional(),
|
|
13269
|
+
delegation: unknown().optional(),
|
|
13258
13270
|
trailers: boolean2().optional(),
|
|
13259
13271
|
liveSessionGuard: boolean2().optional(),
|
|
13260
13272
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
@@ -17506,7 +17518,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
17506
17518
|
return found;
|
|
17507
17519
|
}
|
|
17508
17520
|
var RECENT = new Map;
|
|
17509
|
-
function
|
|
17521
|
+
function commandLines(command, dialect = "posix") {
|
|
17522
|
+
const lines = [];
|
|
17523
|
+
try {
|
|
17524
|
+
walk2(command, dialect, 0, lines);
|
|
17525
|
+
} catch (error) {
|
|
17526
|
+
throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
|
|
17527
|
+
}
|
|
17528
|
+
return lines;
|
|
17529
|
+
}
|
|
17530
|
+
function walk2(command, dialect, depth, seen) {
|
|
17510
17531
|
const found = [];
|
|
17511
17532
|
let dir = null;
|
|
17512
17533
|
const groups = [];
|
|
@@ -17517,7 +17538,8 @@ function walk2(command, dialect, depth) {
|
|
|
17517
17538
|
const nested = (script, inner) => {
|
|
17518
17539
|
if (!script || depth >= MAX_DEPTH)
|
|
17519
17540
|
return;
|
|
17520
|
-
|
|
17541
|
+
const inside = seen ? walk2(script, inner, depth + 1, seen) : rawInvocations(script, inner, depth + 1);
|
|
17542
|
+
for (const invocation of inside)
|
|
17521
17543
|
found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
|
|
17522
17544
|
};
|
|
17523
17545
|
const visit = (input, stdin, hops) => {
|
|
@@ -17529,11 +17551,17 @@ function walk2(command, dialect, depth) {
|
|
|
17529
17551
|
const name = commandName(words[0]);
|
|
17530
17552
|
if (!name)
|
|
17531
17553
|
return;
|
|
17554
|
+
seen?.push(words);
|
|
17555
|
+
if (name === "-c" || name === "--command")
|
|
17556
|
+
return nested(words[1], "posix");
|
|
17532
17557
|
const runs = (lines) => {
|
|
17533
17558
|
if (hops < MAX_DEPTH)
|
|
17534
17559
|
for (const next of lines)
|
|
17535
17560
|
visit(next, undefined, hops + 1);
|
|
17536
17561
|
};
|
|
17562
|
+
const wrapped = seen ? null : workitCheckTail(words);
|
|
17563
|
+
if (wrapped)
|
|
17564
|
+
return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
|
|
17537
17565
|
if (CD.has(name)) {
|
|
17538
17566
|
const target = cdTarget(words);
|
|
17539
17567
|
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
@@ -17624,6 +17652,51 @@ var LAUNCHERS = new Set([
|
|
|
17624
17652
|
"command"
|
|
17625
17653
|
]);
|
|
17626
17654
|
var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
17655
|
+
var WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
|
|
17656
|
+
var PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
|
|
17657
|
+
function workitCheckTail(words) {
|
|
17658
|
+
let at = 0;
|
|
17659
|
+
if (PACKAGE_RUNNERS.has(commandName(words[0]))) {
|
|
17660
|
+
at = words.findIndex((word, index) => index > 0 && !word.startsWith("-"));
|
|
17661
|
+
if (at < 0 || !WORKIT_PACKAGE.test(words[at]))
|
|
17662
|
+
return null;
|
|
17663
|
+
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
17664
|
+
return null;
|
|
17665
|
+
const args = words.slice(at + 1);
|
|
17666
|
+
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
17667
|
+
return null;
|
|
17668
|
+
const split = args.indexOf("--");
|
|
17669
|
+
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
17670
|
+
if (!tail.length)
|
|
17671
|
+
return null;
|
|
17672
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
17673
|
+
}
|
|
17674
|
+
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
17675
|
+
let segments;
|
|
17676
|
+
try {
|
|
17677
|
+
segments = segmentsOf(command, dialect);
|
|
17678
|
+
} catch {
|
|
17679
|
+
return null;
|
|
17680
|
+
}
|
|
17681
|
+
if (segments.length !== 1)
|
|
17682
|
+
return null;
|
|
17683
|
+
const [segment] = segments;
|
|
17684
|
+
if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
|
|
17685
|
+
return null;
|
|
17686
|
+
const { words } = stripPrefixes(segment.words);
|
|
17687
|
+
const name = commandName(words[0]);
|
|
17688
|
+
if (!name || depth >= MAX_DEPTH)
|
|
17689
|
+
return null;
|
|
17690
|
+
if (SHELLS.has(name)) {
|
|
17691
|
+
const script = shellScript(words, undefined);
|
|
17692
|
+
return script ? simpleCommand(script, "posix", depth + 1) : null;
|
|
17693
|
+
}
|
|
17694
|
+
if (name === "-c" || name === "--command")
|
|
17695
|
+
return words[1] ? simpleCommand(words[1], "posix", depth + 1) : null;
|
|
17696
|
+
if (EVALS.has(name) || POWERSHELLS.has(name) || ["xargs", "find", "parallel", "script", "cmd", "source", "."].includes(name))
|
|
17697
|
+
return null;
|
|
17698
|
+
return words;
|
|
17699
|
+
}
|
|
17627
17700
|
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
17628
17701
|
const found = [];
|
|
17629
17702
|
for (const { words } of segmentsOf(command, dialect)) {
|
|
@@ -21971,17 +22044,816 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21971
22044
|
}
|
|
21972
22045
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
21973
22046
|
};
|
|
21974
|
-
// packages/workit-core/src/
|
|
21975
|
-
import
|
|
22047
|
+
// packages/workit-core/src/core/heavy-rules.ts
|
|
22048
|
+
import fs18 from "node:fs";
|
|
22049
|
+
import os6 from "node:os";
|
|
22050
|
+
import path26 from "node:path";
|
|
22051
|
+
var EMPTY = () => ({ deny: [], allow: [] });
|
|
22052
|
+
var readText2 = (file) => {
|
|
22053
|
+
try {
|
|
22054
|
+
return fs18.readFileSync(file, "utf8");
|
|
22055
|
+
} catch (error) {
|
|
22056
|
+
if (error.code === "ENOENT")
|
|
22057
|
+
return null;
|
|
22058
|
+
throw error;
|
|
22059
|
+
}
|
|
22060
|
+
};
|
|
22061
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22062
|
+
function parseJsonc(text) {
|
|
22063
|
+
let out = "";
|
|
22064
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
22065
|
+
const char = text[index];
|
|
22066
|
+
if (char === '"') {
|
|
22067
|
+
const end = /"(?:[^"\\]|\\.)*"/y;
|
|
22068
|
+
end.lastIndex = index;
|
|
22069
|
+
const match = end.exec(text);
|
|
22070
|
+
if (!match)
|
|
22071
|
+
throw new Error("unterminated string");
|
|
22072
|
+
out += match[0];
|
|
22073
|
+
index += match[0].length - 1;
|
|
22074
|
+
} else if (char === "/" && text[index + 1] === "/") {
|
|
22075
|
+
while (index < text.length && text[index] !== `
|
|
22076
|
+
`)
|
|
22077
|
+
index += 1;
|
|
22078
|
+
out += `
|
|
22079
|
+
`;
|
|
22080
|
+
} else if (char === "/" && text[index + 1] === "*") {
|
|
22081
|
+
const close = text.indexOf("*/", index + 2);
|
|
22082
|
+
if (close < 0)
|
|
22083
|
+
throw new Error("unterminated comment");
|
|
22084
|
+
index = close + 1;
|
|
22085
|
+
} else
|
|
22086
|
+
out += char;
|
|
22087
|
+
}
|
|
22088
|
+
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
22089
|
+
}
|
|
22090
|
+
function gitTop(cwd) {
|
|
22091
|
+
for (let dir = path26.resolve(cwd);; dir = path26.dirname(dir)) {
|
|
22092
|
+
if (fs18.existsSync(path26.join(dir, ".git")))
|
|
22093
|
+
return dir;
|
|
22094
|
+
if (path26.dirname(dir) === dir)
|
|
22095
|
+
return null;
|
|
22096
|
+
}
|
|
22097
|
+
}
|
|
22098
|
+
var projectDirs = (cwd) => {
|
|
22099
|
+
const top = gitTop(cwd);
|
|
22100
|
+
const dirs = [];
|
|
22101
|
+
for (let dir = path26.resolve(cwd);; dir = path26.dirname(dir)) {
|
|
22102
|
+
dirs.push(dir);
|
|
22103
|
+
if (!top || dir === top || path26.dirname(dir) === dir)
|
|
22104
|
+
return dirs;
|
|
22105
|
+
}
|
|
22106
|
+
};
|
|
22107
|
+
var homeOf = (env) => env.HOME || os6.homedir();
|
|
22108
|
+
var CLAUDE_MANAGED = {
|
|
22109
|
+
linux: "/etc/claude-code/managed-settings.json",
|
|
22110
|
+
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
22111
|
+
win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
|
|
22112
|
+
};
|
|
22113
|
+
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
22114
|
+
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
22115
|
+
const user = env.CLAUDE_CONFIG_DIR || path26.join(homeOf(env), ".claude");
|
|
22116
|
+
return [
|
|
22117
|
+
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
22118
|
+
path26.join(user, "settings.json"),
|
|
22119
|
+
path26.join(project, ".claude", "settings.json"),
|
|
22120
|
+
path26.join(project, ".claude", "settings.local.json")
|
|
22121
|
+
];
|
|
22122
|
+
}
|
|
22123
|
+
var bashPattern = (entry) => {
|
|
22124
|
+
const match = /^Bash(?:\((.*)\))?$/su.exec(entry.trim());
|
|
22125
|
+
return match ? match[1] ?? "" : null;
|
|
22126
|
+
};
|
|
22127
|
+
function claudeRules(cwd, env = process.env) {
|
|
22128
|
+
const rules = EMPTY();
|
|
22129
|
+
try {
|
|
22130
|
+
for (const file of claudeSettingsFiles(cwd, env)) {
|
|
22131
|
+
const text = readText2(file);
|
|
22132
|
+
if (text === null)
|
|
22133
|
+
continue;
|
|
22134
|
+
const value = JSON.parse(text);
|
|
22135
|
+
if (!isRecord4(value))
|
|
22136
|
+
return "unreadable";
|
|
22137
|
+
const permissions = value.permissions;
|
|
22138
|
+
if (permissions === undefined)
|
|
22139
|
+
continue;
|
|
22140
|
+
if (!isRecord4(permissions))
|
|
22141
|
+
return "unreadable";
|
|
22142
|
+
for (const [key, bucket] of [
|
|
22143
|
+
["deny", rules.deny],
|
|
22144
|
+
["ask", rules.deny],
|
|
22145
|
+
["allow", rules.allow]
|
|
22146
|
+
]) {
|
|
22147
|
+
const list = permissions[key];
|
|
22148
|
+
if (list === undefined)
|
|
22149
|
+
continue;
|
|
22150
|
+
if (!Array.isArray(list))
|
|
22151
|
+
return "unreadable";
|
|
22152
|
+
for (const entry of list) {
|
|
22153
|
+
if (typeof entry !== "string")
|
|
22154
|
+
return "unreadable";
|
|
22155
|
+
const pattern = bashPattern(entry);
|
|
22156
|
+
if (pattern !== null)
|
|
22157
|
+
bucket.push(pattern);
|
|
22158
|
+
}
|
|
22159
|
+
}
|
|
22160
|
+
}
|
|
22161
|
+
} catch {
|
|
22162
|
+
return "unreadable";
|
|
22163
|
+
}
|
|
22164
|
+
return rules;
|
|
22165
|
+
}
|
|
22166
|
+
var OPENCODE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
22167
|
+
var opencodePermission = (permission, rules) => {
|
|
22168
|
+
if (permission === undefined)
|
|
22169
|
+
return true;
|
|
22170
|
+
if (typeof permission === "string") {
|
|
22171
|
+
(permission === "allow" ? rules.allow : rules.deny).push("");
|
|
22172
|
+
return ["allow", "ask", "deny"].includes(permission);
|
|
22173
|
+
}
|
|
22174
|
+
if (!isRecord4(permission))
|
|
22175
|
+
return false;
|
|
22176
|
+
for (const key of ["bash", "shell"]) {
|
|
22177
|
+
const value = permission[key];
|
|
22178
|
+
if (value === undefined)
|
|
22179
|
+
continue;
|
|
22180
|
+
if (typeof value === "string") {
|
|
22181
|
+
if (!["allow", "ask", "deny"].includes(value))
|
|
22182
|
+
return false;
|
|
22183
|
+
(value === "allow" ? rules.allow : rules.deny).push("");
|
|
22184
|
+
continue;
|
|
22185
|
+
}
|
|
22186
|
+
if (!isRecord4(value))
|
|
22187
|
+
return false;
|
|
22188
|
+
for (const [pattern, action] of Object.entries(value)) {
|
|
22189
|
+
if (action !== "allow" && action !== "ask" && action !== "deny")
|
|
22190
|
+
return false;
|
|
22191
|
+
(action === "allow" ? rules.allow : rules.deny).push(pattern);
|
|
22192
|
+
}
|
|
22193
|
+
}
|
|
22194
|
+
return true;
|
|
22195
|
+
};
|
|
22196
|
+
var opencodeConfig = (value, rules) => {
|
|
22197
|
+
if (!isRecord4(value))
|
|
22198
|
+
return false;
|
|
22199
|
+
if (!opencodePermission(value.permission, rules))
|
|
22200
|
+
return false;
|
|
22201
|
+
if (value.agent === undefined)
|
|
22202
|
+
return true;
|
|
22203
|
+
if (!isRecord4(value.agent))
|
|
22204
|
+
return false;
|
|
22205
|
+
return Object.values(value.agent).every((agent) => isRecord4(agent) && opencodePermission(agent.permission, rules));
|
|
22206
|
+
};
|
|
22207
|
+
function opencodeRules(cwd, env = process.env) {
|
|
22208
|
+
const rules = EMPTY();
|
|
22209
|
+
try {
|
|
22210
|
+
const config = env.XDG_CONFIG_HOME || path26.join(homeOf(env), ".config");
|
|
22211
|
+
const files = [
|
|
22212
|
+
...OPENCODE_NAMES.map((name) => path26.join(config, "opencode", name)),
|
|
22213
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path26.join(dir, name), path26.join(dir, ".opencode", name)])),
|
|
22214
|
+
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
22215
|
+
];
|
|
22216
|
+
for (const file of files) {
|
|
22217
|
+
const text = readText2(file);
|
|
22218
|
+
if (text !== null && !opencodeConfig(parseJsonc(text), rules))
|
|
22219
|
+
return "unreadable";
|
|
22220
|
+
}
|
|
22221
|
+
if (env.OPENCODE_CONFIG_CONTENT && !opencodeConfig(parseJsonc(env.OPENCODE_CONFIG_CONTENT), rules))
|
|
22222
|
+
return "unreadable";
|
|
22223
|
+
} catch {
|
|
22224
|
+
return "unreadable";
|
|
22225
|
+
}
|
|
22226
|
+
return rules;
|
|
22227
|
+
}
|
|
22228
|
+
var codexRule = (body) => {
|
|
22229
|
+
const open = /\bpattern\s*=\s*\[/u.exec(body);
|
|
22230
|
+
if (!open)
|
|
22231
|
+
return null;
|
|
22232
|
+
const words = [];
|
|
22233
|
+
let index = open.index + open[0].length;
|
|
22234
|
+
for (;; ) {
|
|
22235
|
+
while (/[\s,]/u.test(body[index] ?? ""))
|
|
22236
|
+
index += 1;
|
|
22237
|
+
if (body[index] === "]")
|
|
22238
|
+
break;
|
|
22239
|
+
if (body[index] !== '"')
|
|
22240
|
+
return null;
|
|
22241
|
+
const literal = /"((?:[^"\\]|\\.)*)"/y;
|
|
22242
|
+
literal.lastIndex = index;
|
|
22243
|
+
const match = literal.exec(body);
|
|
22244
|
+
if (!match)
|
|
22245
|
+
return null;
|
|
22246
|
+
words.push(JSON.parse(`"${match[1]}"`));
|
|
22247
|
+
index = literal.lastIndex;
|
|
22248
|
+
}
|
|
22249
|
+
const decision = /\bdecision\s*=\s*"(\w+)"/u.exec(body)?.[1];
|
|
22250
|
+
if (!words.length || !decision || !["allow", "prompt", "forbidden"].includes(decision))
|
|
22251
|
+
return null;
|
|
22252
|
+
return { pattern: words.join(" "), decision };
|
|
22253
|
+
};
|
|
22254
|
+
function codexRules(cwd, env = process.env) {
|
|
22255
|
+
const rules = EMPTY();
|
|
22256
|
+
try {
|
|
22257
|
+
const home = env.CODEX_HOME || path26.join(homeOf(env), ".codex");
|
|
22258
|
+
const top = gitTop(cwd) ?? cwd;
|
|
22259
|
+
for (const dir of [path26.join(home, "rules"), path26.join(top, ".codex", "rules")]) {
|
|
22260
|
+
let files;
|
|
22261
|
+
try {
|
|
22262
|
+
files = fs18.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
22263
|
+
} catch (error) {
|
|
22264
|
+
if (error.code === "ENOENT")
|
|
22265
|
+
continue;
|
|
22266
|
+
return "unreadable";
|
|
22267
|
+
}
|
|
22268
|
+
for (const name of files) {
|
|
22269
|
+
const text = readText2(path26.join(dir, name)) ?? "";
|
|
22270
|
+
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
22271
|
+
const rule = codexRule(part);
|
|
22272
|
+
if (!rule)
|
|
22273
|
+
return "unreadable";
|
|
22274
|
+
(rule.decision === "allow" ? rules.allow : rules.deny).push(rule.pattern);
|
|
22275
|
+
}
|
|
22276
|
+
}
|
|
22277
|
+
}
|
|
22278
|
+
} catch {
|
|
22279
|
+
return "unreadable";
|
|
22280
|
+
}
|
|
22281
|
+
return rules;
|
|
22282
|
+
}
|
|
22283
|
+
function cursorRules(cwd, env = process.env) {
|
|
22284
|
+
const rules = EMPTY();
|
|
22285
|
+
try {
|
|
22286
|
+
const top = gitTop(cwd) ?? cwd;
|
|
22287
|
+
for (const file of [
|
|
22288
|
+
path26.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
22289
|
+
path26.join(top, ".cursor", "cli.json")
|
|
22290
|
+
]) {
|
|
22291
|
+
const text = readText2(file);
|
|
22292
|
+
if (text === null)
|
|
22293
|
+
continue;
|
|
22294
|
+
const value = JSON.parse(text);
|
|
22295
|
+
if (!isRecord4(value))
|
|
22296
|
+
return "unreadable";
|
|
22297
|
+
if (value.permissions === undefined)
|
|
22298
|
+
continue;
|
|
22299
|
+
if (!isRecord4(value.permissions))
|
|
22300
|
+
return "unreadable";
|
|
22301
|
+
for (const [key, bucket] of [
|
|
22302
|
+
["deny", rules.deny],
|
|
22303
|
+
["allow", rules.allow]
|
|
22304
|
+
]) {
|
|
22305
|
+
const list = value.permissions[key];
|
|
22306
|
+
if (list === undefined)
|
|
22307
|
+
continue;
|
|
22308
|
+
if (!Array.isArray(list))
|
|
22309
|
+
return "unreadable";
|
|
22310
|
+
for (const entry of list) {
|
|
22311
|
+
if (typeof entry !== "string")
|
|
22312
|
+
return "unreadable";
|
|
22313
|
+
const match = /^Shell(?:\((.*)\))?$/su.exec(entry.trim());
|
|
22314
|
+
if (match)
|
|
22315
|
+
bucket.push(match[1] ?? "");
|
|
22316
|
+
}
|
|
22317
|
+
}
|
|
22318
|
+
}
|
|
22319
|
+
} catch {
|
|
22320
|
+
return "unreadable";
|
|
22321
|
+
}
|
|
22322
|
+
return rules;
|
|
22323
|
+
}
|
|
22324
|
+
var baseName = (word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "");
|
|
22325
|
+
var firstToken = (pattern) => pattern.trim().split(/[:\s]/u)[0] ?? "";
|
|
22326
|
+
function names(pattern, words) {
|
|
22327
|
+
const token = firstToken(pattern);
|
|
22328
|
+
if (!token || token === "*" || token.includes("*"))
|
|
22329
|
+
return true;
|
|
22330
|
+
const wanted = baseName(token);
|
|
22331
|
+
return words.some((word) => baseName(word) === wanted);
|
|
22332
|
+
}
|
|
22333
|
+
var literalPrefix = (pattern) => pattern.trim().replace(/(?::\*| \*|\*)$/u, "");
|
|
22334
|
+
var coversWrapper = (pattern) => {
|
|
22335
|
+
const token = firstToken(pattern);
|
|
22336
|
+
if (!token || token.includes("*"))
|
|
22337
|
+
return true;
|
|
22338
|
+
const words = literalPrefix(pattern).split(/\s+/u);
|
|
22339
|
+
if (baseName(words[0]) !== "workit")
|
|
22340
|
+
return false;
|
|
22341
|
+
return ["check", "--heavy"].slice(0, words.length - 1).every((word, at) => {
|
|
22342
|
+
const given = words[at + 1];
|
|
22343
|
+
return given === word || word.startsWith(given);
|
|
22344
|
+
});
|
|
22345
|
+
};
|
|
22346
|
+
var coversCommand = (pattern, command) => {
|
|
22347
|
+
const prefix = literalPrefix(pattern);
|
|
22348
|
+
if (!prefix)
|
|
22349
|
+
return true;
|
|
22350
|
+
return !prefix.includes("*") && command.trim().startsWith(prefix);
|
|
22351
|
+
};
|
|
22352
|
+
function wrapAllowed(read, words, command, options = {}) {
|
|
22353
|
+
if (read === "unreadable")
|
|
22354
|
+
return { ok: false, why: "named" };
|
|
22355
|
+
if (read.deny.some((pattern) => names(pattern, words)))
|
|
22356
|
+
return { ok: false, why: "named" };
|
|
22357
|
+
if (!options.bypass && read.allow.some(coversWrapper) && !read.allow.some((pattern) => coversCommand(pattern, command)))
|
|
22358
|
+
return { ok: false, why: "allowlisted" };
|
|
22359
|
+
return { ok: true };
|
|
22360
|
+
}
|
|
22361
|
+
|
|
22362
|
+
// packages/workit-core/src/core/heavy.ts
|
|
22363
|
+
import fs19 from "node:fs";
|
|
21976
22364
|
import os7 from "node:os";
|
|
21977
22365
|
import path27 from "node:path";
|
|
22366
|
+
function stateDir(env, platform = process.platform) {
|
|
22367
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
22368
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
22369
|
+
if (env.XDG_STATE_HOME)
|
|
22370
|
+
return path27.join(env.XDG_STATE_HOME, "workit");
|
|
22371
|
+
const home = env.HOME || os7.homedir();
|
|
22372
|
+
if (platform === "darwin")
|
|
22373
|
+
return path27.join(home, "Library", "Application Support", "workit");
|
|
22374
|
+
if (platform === "win32")
|
|
22375
|
+
return path27.join(env.LOCALAPPDATA ?? path27.join(home, "AppData", "Local"), "workit");
|
|
22376
|
+
return path27.join(home, ".local", "state", "workit");
|
|
22377
|
+
}
|
|
22378
|
+
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
22379
|
+
var heavyCommandsFile = (env) => path27.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
22380
|
+
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;
|
|
22381
|
+
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
22382
|
+
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
22383
|
+
var WATCH_SCRIPT = /(?:^|[:_.-])(?:watch|dev|serve|start|storybook)(?:$|[:_.-])/iu;
|
|
22384
|
+
var sub = (...names) => new Set(names);
|
|
22385
|
+
var TOOLS = {
|
|
22386
|
+
jest: "any",
|
|
22387
|
+
vitest: "any",
|
|
22388
|
+
mocha: "any",
|
|
22389
|
+
ava: "any",
|
|
22390
|
+
tap: "any",
|
|
22391
|
+
karma: "any",
|
|
22392
|
+
jasmine: "any",
|
|
22393
|
+
nyc: "any",
|
|
22394
|
+
c8: "any",
|
|
22395
|
+
tsc: "any",
|
|
22396
|
+
"vue-tsc": "any",
|
|
22397
|
+
"svelte-check": "any",
|
|
22398
|
+
eslint: "any",
|
|
22399
|
+
stylelint: "any",
|
|
22400
|
+
tslint: "any",
|
|
22401
|
+
webpack: "any",
|
|
22402
|
+
"webpack-cli": "any",
|
|
22403
|
+
rollup: "any",
|
|
22404
|
+
parcel: "any",
|
|
22405
|
+
tsup: "any",
|
|
22406
|
+
vite: sub("build", "optimize"),
|
|
22407
|
+
next: sub("build", "lint"),
|
|
22408
|
+
nuxt: sub("build", "generate"),
|
|
22409
|
+
nuxi: sub("build", "generate"),
|
|
22410
|
+
astro: sub("build", "check"),
|
|
22411
|
+
ng: sub("build", "b", "test", "t", "e2e", "lint"),
|
|
22412
|
+
"react-scripts": sub("build", "test"),
|
|
22413
|
+
playwright: sub("test", "install"),
|
|
22414
|
+
cypress: sub("run"),
|
|
22415
|
+
wdio: "any",
|
|
22416
|
+
testcafe: "any",
|
|
22417
|
+
turbo: "any",
|
|
22418
|
+
lerna: sub("run", "exec", "bootstrap"),
|
|
22419
|
+
"sonar-scanner": "any",
|
|
22420
|
+
sonarless: sub("scan"),
|
|
22421
|
+
"sonarless-mcp": sub("scan"),
|
|
22422
|
+
go: sub("test", "build", "vet", "install", "generate"),
|
|
22423
|
+
"golangci-lint": sub("run"),
|
|
22424
|
+
gotestsum: "any",
|
|
22425
|
+
staticcheck: "any",
|
|
22426
|
+
cargo: sub("build", "b", "test", "t", "check", "c", "clippy", "bench", "nextest", "doc", "install", "tarpaulin", "llvm-cov"),
|
|
22427
|
+
mvn: "any",
|
|
22428
|
+
mvnw: "any",
|
|
22429
|
+
gradle: "any",
|
|
22430
|
+
gradlew: "any",
|
|
22431
|
+
ant: "any",
|
|
22432
|
+
sbt: "any",
|
|
22433
|
+
bazel: sub("build", "test", "run", "coverage"),
|
|
22434
|
+
bazelisk: sub("build", "test", "run", "coverage"),
|
|
22435
|
+
buck2: sub("build", "test", "run"),
|
|
22436
|
+
dotnet: sub("build", "test", "publish", "restore", "pack", "msbuild"),
|
|
22437
|
+
msbuild: "any",
|
|
22438
|
+
pytest: "any",
|
|
22439
|
+
"py.test": "any",
|
|
22440
|
+
tox: "any",
|
|
22441
|
+
nox: "any",
|
|
22442
|
+
mypy: "any",
|
|
22443
|
+
pyright: "any",
|
|
22444
|
+
pylint: "any",
|
|
22445
|
+
coverage: sub("run"),
|
|
22446
|
+
maturin: sub("build", "develop"),
|
|
22447
|
+
pip: sub("install", "wheel"),
|
|
22448
|
+
pip3: sub("install", "wheel"),
|
|
22449
|
+
rspec: "any",
|
|
22450
|
+
rails: sub("test", "assets:precompile"),
|
|
22451
|
+
phpunit: "any",
|
|
22452
|
+
pest: "any",
|
|
22453
|
+
phpstan: "any",
|
|
22454
|
+
psalm: "any",
|
|
22455
|
+
composer: sub("install", "update", "require", "test"),
|
|
22456
|
+
mix: sub("test", "compile", "deps.get", "dialyzer", "credo"),
|
|
22457
|
+
swift: sub("build", "test"),
|
|
22458
|
+
xcodebuild: "any",
|
|
22459
|
+
flutter: sub("build", "test"),
|
|
22460
|
+
dart: sub("test", "compile"),
|
|
22461
|
+
pod: sub("install"),
|
|
22462
|
+
cmake: sub("--build"),
|
|
22463
|
+
ninja: "any",
|
|
22464
|
+
meson: sub("compile", "test"),
|
|
22465
|
+
ctest: "any",
|
|
22466
|
+
scons: "any",
|
|
22467
|
+
"docker-compose": sub("build"),
|
|
22468
|
+
buildah: sub("bud", "build"),
|
|
22469
|
+
earthly: "any"
|
|
22470
|
+
};
|
|
22471
|
+
var PM_VALUES = new Set([
|
|
22472
|
+
"--prefix",
|
|
22473
|
+
"-w",
|
|
22474
|
+
"--workspace",
|
|
22475
|
+
"-C",
|
|
22476
|
+
"--dir",
|
|
22477
|
+
"--filter",
|
|
22478
|
+
"-F",
|
|
22479
|
+
"--cwd",
|
|
22480
|
+
"-p",
|
|
22481
|
+
"--package",
|
|
22482
|
+
"--cache",
|
|
22483
|
+
"--registry"
|
|
22484
|
+
]);
|
|
22485
|
+
function positional2(words, from, values = PM_VALUES) {
|
|
22486
|
+
let index = from;
|
|
22487
|
+
while (index < words.length && words[index].startsWith("-")) {
|
|
22488
|
+
if (words[index] === "--")
|
|
22489
|
+
return index + 1;
|
|
22490
|
+
index += values.has(words[index]) ? 2 : 1;
|
|
22491
|
+
}
|
|
22492
|
+
return index;
|
|
22493
|
+
}
|
|
22494
|
+
var baseName2 = (word) => (word?.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/u, "");
|
|
22495
|
+
var WORKIT = /^(?:workit|@brainervirus\/workit-cli(?:@.*)?)$/u;
|
|
22496
|
+
var PYTHONS = /^(?:python[\d.]*|py)$/u;
|
|
22497
|
+
var NPM_INSTALL = new Set([
|
|
22498
|
+
"ci",
|
|
22499
|
+
"clean-install",
|
|
22500
|
+
"cit",
|
|
22501
|
+
"install-test",
|
|
22502
|
+
"it",
|
|
22503
|
+
"install",
|
|
22504
|
+
"i",
|
|
22505
|
+
"in",
|
|
22506
|
+
"add",
|
|
22507
|
+
"rebuild",
|
|
22508
|
+
"update",
|
|
22509
|
+
"up"
|
|
22510
|
+
]);
|
|
22511
|
+
var NPM_RUN = new Set(["run", "run-script", "rum", "urn"]);
|
|
22512
|
+
var NPM_TEST = new Set(["test", "t", "tst"]);
|
|
22513
|
+
var EXEC = new Set(["exec", "x", "dlx"]);
|
|
22514
|
+
function builtIn(words, depth = 0) {
|
|
22515
|
+
if (depth > 6 || !words.length)
|
|
22516
|
+
return null;
|
|
22517
|
+
const name = baseName2(words[0]);
|
|
22518
|
+
const label = () => words.slice(0, 3).join(" ");
|
|
22519
|
+
const inner = (from) => builtIn(words.slice(from), depth + 1);
|
|
22520
|
+
if (WORKIT.test(name))
|
|
22521
|
+
return null;
|
|
22522
|
+
if (INFO_FLAGS.has(words[1] ?? "") && words.length === 2)
|
|
22523
|
+
return null;
|
|
22524
|
+
if (words.some((word) => word === "--watch" || word.startsWith("--watch=") || word === "--watchAll"))
|
|
22525
|
+
return null;
|
|
22526
|
+
if (name === "npx" || name === "bunx" || name === "pnpx")
|
|
22527
|
+
return inner(positional2(words, 1));
|
|
22528
|
+
if (name === "corepack")
|
|
22529
|
+
return inner(1);
|
|
22530
|
+
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" || name === "deno") {
|
|
22531
|
+
const at = positional2(words, 1);
|
|
22532
|
+
const command = words[at];
|
|
22533
|
+
if (command === undefined)
|
|
22534
|
+
return name === "yarn" ? "yarn (install)" : null;
|
|
22535
|
+
if (name === "deno") {
|
|
22536
|
+
if (["test", "bench", "check", "compile", "lint"].includes(command))
|
|
22537
|
+
return label();
|
|
22538
|
+
if (command === "task" && HEAVY_SCRIPT.test(words[at + 1] ?? ""))
|
|
22539
|
+
return label();
|
|
22540
|
+
return null;
|
|
22541
|
+
}
|
|
22542
|
+
if (NPM_INSTALL.has(command) || NPM_TEST.has(command))
|
|
22543
|
+
return label();
|
|
22544
|
+
if (name === "bun" && command === "build")
|
|
22545
|
+
return label();
|
|
22546
|
+
if (EXEC.has(command))
|
|
22547
|
+
return inner(positional2(words, at + 1));
|
|
22548
|
+
if (name === "yarn" && command === "workspace")
|
|
22549
|
+
return inner(at + 2);
|
|
22550
|
+
if (name === "yarn" && command === "workspaces")
|
|
22551
|
+
return words.slice(at + 1).some((word) => HEAVY_SCRIPT.test(word)) ? label() : null;
|
|
22552
|
+
if (NPM_RUN.has(command)) {
|
|
22553
|
+
const script = words[positional2(words, at + 1)];
|
|
22554
|
+
if (script === undefined || WATCH_SCRIPT.test(script))
|
|
22555
|
+
return null;
|
|
22556
|
+
if (HEAVY_SCRIPT.test(script))
|
|
22557
|
+
return label();
|
|
22558
|
+
return name === "bun" ? inner(positional2(words, at + 1)) : null;
|
|
22559
|
+
}
|
|
22560
|
+
if (name === "npm")
|
|
22561
|
+
return null;
|
|
22562
|
+
if (WATCH_SCRIPT.test(command))
|
|
22563
|
+
return null;
|
|
22564
|
+
if (HEAVY_SCRIPT.test(command) && !/\.[cm]?[jt]sx?$/u.test(command))
|
|
22565
|
+
return label();
|
|
22566
|
+
return inner(at);
|
|
22567
|
+
}
|
|
22568
|
+
if (PYTHONS.test(name)) {
|
|
22569
|
+
const at = words.indexOf("-m");
|
|
22570
|
+
return at > 0 && words[at + 1] ? inner(at + 1) : null;
|
|
22571
|
+
}
|
|
22572
|
+
if (["uv", "poetry", "pdm", "pipenv", "hatch", "rye"].includes(name)) {
|
|
22573
|
+
const at = positional2(words, 1);
|
|
22574
|
+
const command = words[at];
|
|
22575
|
+
if (command === "run")
|
|
22576
|
+
return inner(positional2(words, at + 1));
|
|
22577
|
+
if (command === "pip")
|
|
22578
|
+
return inner(at);
|
|
22579
|
+
return ["sync", "install", "build", "test", "add"].includes(command ?? "") ? label() : null;
|
|
22580
|
+
}
|
|
22581
|
+
if (name === "bundle") {
|
|
22582
|
+
const command = words[positional2(words, 1)];
|
|
22583
|
+
if (command === "exec")
|
|
22584
|
+
return inner(positional2(words, 2));
|
|
22585
|
+
return command === "install" || command === undefined ? label() : null;
|
|
22586
|
+
}
|
|
22587
|
+
if (name === "make" || name === "gmake") {
|
|
22588
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
22589
|
+
if (targets.some((target) => WATCH_SCRIPT.test(target)))
|
|
22590
|
+
return null;
|
|
22591
|
+
return !targets.length || targets.some((target) => HEAVY_SCRIPT.test(target)) ? label() : null;
|
|
22592
|
+
}
|
|
22593
|
+
if (name === "rake") {
|
|
22594
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
22595
|
+
return targets.some((target) => HEAVY_SCRIPT.test(target) || /^spec(?:$|:)/u.test(target)) ? label() : null;
|
|
22596
|
+
}
|
|
22597
|
+
if (name === "nx") {
|
|
22598
|
+
const at = positional2(words, 1);
|
|
22599
|
+
const command = words[at] ?? "";
|
|
22600
|
+
if (["run-many", "affected"].includes(command))
|
|
22601
|
+
return words.some((word) => /^(?:-t|--targets?)(?:=|$)/u.test(word)) && !words.some((word) => WATCH_SCRIPT.test(word.replace(/^--?targets?=/u, ""))) ? label() : null;
|
|
22602
|
+
if (command === "run") {
|
|
22603
|
+
const target = (words[at + 1] ?? "").split(":")[1] ?? "";
|
|
22604
|
+
return HEAVY_SCRIPT.test(target) && !WATCH_SCRIPT.test(target) ? label() : null;
|
|
22605
|
+
}
|
|
22606
|
+
return HEAVY_SCRIPT.test(command) && !WATCH_SCRIPT.test(command) ? label() : null;
|
|
22607
|
+
}
|
|
22608
|
+
if (name === "docker" || name === "podman" || name === "nerdctl") {
|
|
22609
|
+
const at = positional2(words, 1, new Set(["-H", "--host", "--context", "-c", "--config"]));
|
|
22610
|
+
const command = words[at];
|
|
22611
|
+
if (command === "build")
|
|
22612
|
+
return label();
|
|
22613
|
+
if (command === "buildx" || command === "image" || command === "builder")
|
|
22614
|
+
return words[positional2(words, at + 1)] === "build" ? label() : null;
|
|
22615
|
+
if (command === "compose") {
|
|
22616
|
+
const at2 = positional2(words, at + 1, new Set(["-f", "--file", "-p"]));
|
|
22617
|
+
const action = words[at2];
|
|
22618
|
+
if (action === "build")
|
|
22619
|
+
return label();
|
|
22620
|
+
return action === "up" && words.slice(at2 + 1).includes("--build") ? label() : null;
|
|
22621
|
+
}
|
|
22622
|
+
return null;
|
|
22623
|
+
}
|
|
22624
|
+
const rule = TOOLS[name];
|
|
22625
|
+
if (!rule)
|
|
22626
|
+
return null;
|
|
22627
|
+
if (words.slice(1).some((word) => WATCH_FLAG.test(word) || word === "watch" || word === "dev"))
|
|
22628
|
+
return null;
|
|
22629
|
+
if (rule === "any")
|
|
22630
|
+
return label();
|
|
22631
|
+
const command = words.slice(1).find((word) => !word.startsWith("-") && !word.startsWith("+") || rule.has(word));
|
|
22632
|
+
return command !== undefined && rule.has(command) ? label() : null;
|
|
22633
|
+
}
|
|
22634
|
+
var globWord = (word) => new RegExp(`^${word.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*")}$`, "u");
|
|
22635
|
+
function parsePattern(pattern) {
|
|
22636
|
+
const words = pattern.trim().split(/\s+/u).filter(Boolean);
|
|
22637
|
+
return words.length ? words.map(globWord) : null;
|
|
22638
|
+
}
|
|
22639
|
+
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]));
|
|
22640
|
+
var readJson = (file) => {
|
|
22641
|
+
try {
|
|
22642
|
+
return JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
22643
|
+
} catch {
|
|
22644
|
+
return;
|
|
22645
|
+
}
|
|
22646
|
+
};
|
|
22647
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22648
|
+
var stringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
22649
|
+
function machinePatterns(env) {
|
|
22650
|
+
const value = readJson(heavyCommandsFile(env));
|
|
22651
|
+
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
22652
|
+
}
|
|
22653
|
+
function repoPatterns(cwd) {
|
|
22654
|
+
return repoHeavyCommands(gitTop(cwd) ?? path27.resolve(cwd));
|
|
22655
|
+
}
|
|
22656
|
+
function heavyLines(lines, options) {
|
|
22657
|
+
const found = [];
|
|
22658
|
+
let patterns = null;
|
|
22659
|
+
for (const line of lines) {
|
|
22660
|
+
const reason = builtIn(line);
|
|
22661
|
+
if (reason) {
|
|
22662
|
+
found.push({ line, source: "catalog", reason });
|
|
22663
|
+
continue;
|
|
22664
|
+
}
|
|
22665
|
+
patterns ??= (options.patterns ?? [
|
|
22666
|
+
...machinePatterns(options.env ?? process.env).map((pattern) => ({
|
|
22667
|
+
pattern,
|
|
22668
|
+
from: "machine"
|
|
22669
|
+
})),
|
|
22670
|
+
...repoPatterns(options.cwd).map((pattern) => ({ pattern, from: "repo" }))
|
|
22671
|
+
]).flatMap((entry) => {
|
|
22672
|
+
const matchers = parsePattern(entry.pattern);
|
|
22673
|
+
return matchers ? [{ ...entry, matchers }] : [];
|
|
22674
|
+
});
|
|
22675
|
+
const hit = patterns.find((entry) => matches(entry.matchers, line));
|
|
22676
|
+
if (hit)
|
|
22677
|
+
found.push({
|
|
22678
|
+
line,
|
|
22679
|
+
source: { pattern: hit.pattern, from: hit.from },
|
|
22680
|
+
reason: `${hit.from} pattern "${hit.pattern}"`
|
|
22681
|
+
});
|
|
22682
|
+
}
|
|
22683
|
+
return found;
|
|
22684
|
+
}
|
|
22685
|
+
var linesOf = (command, dialect) => {
|
|
22686
|
+
try {
|
|
22687
|
+
return commandLines(command, dialect);
|
|
22688
|
+
} catch {
|
|
22689
|
+
return null;
|
|
22690
|
+
}
|
|
22691
|
+
};
|
|
22692
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/u.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
|
|
22693
|
+
var leaseCommand = (words) => `workit check --heavy -- ${words.map(quoteWord).join(" ")}`;
|
|
22694
|
+
function heavyPlan(command, options) {
|
|
22695
|
+
const dialect = options.dialect ?? "posix";
|
|
22696
|
+
if (dialect !== "posix" || !gitTop(options.cwd))
|
|
22697
|
+
return { kind: "none" };
|
|
22698
|
+
const lines = linesOf(command, dialect);
|
|
22699
|
+
if (!lines)
|
|
22700
|
+
return { kind: "none" };
|
|
22701
|
+
const heavy = heavyLines(lines, options);
|
|
22702
|
+
if (!heavy.length)
|
|
22703
|
+
return { kind: "none" };
|
|
22704
|
+
const simple = simpleCommand(command, dialect);
|
|
22705
|
+
const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
|
|
22706
|
+
const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
|
|
22707
|
+
words: match.line,
|
|
22708
|
+
text: match.line.join(" ")
|
|
22709
|
+
}));
|
|
22710
|
+
if (options.rules !== null)
|
|
22711
|
+
for (const { words, text } of judged) {
|
|
22712
|
+
const verdict = wrapAllowed(options.rules, words, text, { bypass: options.bypass });
|
|
22713
|
+
if (!verdict.ok)
|
|
22714
|
+
return {
|
|
22715
|
+
kind: "passthrough",
|
|
22716
|
+
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.`
|
|
22717
|
+
};
|
|
22718
|
+
}
|
|
22719
|
+
if (single) {
|
|
22720
|
+
const rewrite = leaseRewrite(command);
|
|
22721
|
+
return rewrite ? { kind: "rewrite", command: rewrite, reason: single.reason } : { kind: "none" };
|
|
22722
|
+
}
|
|
22723
|
+
const parts = heavy.map((match) => leaseCommand(match.line));
|
|
22724
|
+
return {
|
|
22725
|
+
kind: "split",
|
|
22726
|
+
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.`,
|
|
22727
|
+
unblock: parts[0] ?? null
|
|
22728
|
+
};
|
|
22729
|
+
}
|
|
22730
|
+
function workitOnPath2(env = process.env) {
|
|
22731
|
+
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
22732
|
+
for (const dir of (env.PATH ?? "").split(path27.delimiter))
|
|
22733
|
+
for (const name of names) {
|
|
22734
|
+
if (!dir)
|
|
22735
|
+
continue;
|
|
22736
|
+
try {
|
|
22737
|
+
fs19.accessSync(path27.join(dir, name), fs19.constants.X_OK);
|
|
22738
|
+
return true;
|
|
22739
|
+
} catch {}
|
|
22740
|
+
}
|
|
22741
|
+
return false;
|
|
22742
|
+
}
|
|
22743
|
+
var ASSIGNMENTS = /^\s*((?:[A-Za-z_]\w*=(?:'[^']*'|"(?:[^"\\]|\\.)*"|[^\s'"])*\s+)*)/u;
|
|
22744
|
+
function leaseRewrite(command) {
|
|
22745
|
+
const text = command.trim();
|
|
22746
|
+
const prefix = ASSIGNMENTS.exec(text)?.[1] ?? "";
|
|
22747
|
+
const rest = text.slice(prefix.length).trim();
|
|
22748
|
+
if (!rest)
|
|
22749
|
+
return null;
|
|
22750
|
+
return `${prefix}workit check --heavy -- ${rest}`;
|
|
22751
|
+
}
|
|
22752
|
+
|
|
22753
|
+
// packages/workit-core/src/core/heavy-words.ts
|
|
22754
|
+
import fs20 from "node:fs";
|
|
22755
|
+
import os8 from "node:os";
|
|
22756
|
+
import path28 from "node:path";
|
|
22757
|
+
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(" "));
|
|
22758
|
+
var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
|
|
22759
|
+
var stateDir2 = (env) => {
|
|
22760
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
22761
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
22762
|
+
if (env.XDG_STATE_HOME)
|
|
22763
|
+
return path28.join(env.XDG_STATE_HOME, "workit");
|
|
22764
|
+
const home = env.HOME || os8.homedir();
|
|
22765
|
+
if (process.platform === "darwin")
|
|
22766
|
+
return path28.join(home, "Library", "Application Support", "workit");
|
|
22767
|
+
if (process.platform === "win32")
|
|
22768
|
+
return path28.join(env.LOCALAPPDATA ?? path28.join(home, "AppData", "Local"), "workit");
|
|
22769
|
+
return path28.join(home, ".local", "state", "workit");
|
|
22770
|
+
};
|
|
22771
|
+
var readJson2 = (file) => {
|
|
22772
|
+
try {
|
|
22773
|
+
return JSON.parse(fs20.readFileSync(file, "utf8"));
|
|
22774
|
+
} catch {
|
|
22775
|
+
return;
|
|
22776
|
+
}
|
|
22777
|
+
};
|
|
22778
|
+
var patternWords = (cwd, env) => {
|
|
22779
|
+
const patterns = [];
|
|
22780
|
+
const machine = readJson2(path28.join(stateDir2(env), "heavy-commands.json"));
|
|
22781
|
+
if (Array.isArray(machine?.patterns))
|
|
22782
|
+
patterns.push(...machine.patterns);
|
|
22783
|
+
for (let dir = path28.resolve(cwd);; dir = path28.dirname(dir)) {
|
|
22784
|
+
if (fs20.existsSync(path28.join(dir, ".git"))) {
|
|
22785
|
+
patterns.push(...repoHeavyCommands(dir));
|
|
22786
|
+
break;
|
|
22787
|
+
}
|
|
22788
|
+
if (path28.dirname(dir) === dir)
|
|
22789
|
+
break;
|
|
22790
|
+
}
|
|
22791
|
+
const words = new Set;
|
|
22792
|
+
for (const pattern of patterns) {
|
|
22793
|
+
if (typeof pattern !== "string")
|
|
22794
|
+
continue;
|
|
22795
|
+
const first = wordsOf(pattern)[0];
|
|
22796
|
+
if (!first || first.includes("*"))
|
|
22797
|
+
return null;
|
|
22798
|
+
words.add(first);
|
|
22799
|
+
}
|
|
22800
|
+
return words;
|
|
22801
|
+
};
|
|
22802
|
+
function maybeHeavy(command, cwd, env = process.env) {
|
|
22803
|
+
try {
|
|
22804
|
+
const words = wordsOf(command);
|
|
22805
|
+
if (words.some((word) => HEAVY_WORDS.has(word) || /^python[\d.]*$/u.test(word)))
|
|
22806
|
+
return true;
|
|
22807
|
+
const extra = patternWords(cwd, env);
|
|
22808
|
+
return extra === null || words.some((word) => extra.has(word));
|
|
22809
|
+
} catch {
|
|
22810
|
+
return true;
|
|
22811
|
+
}
|
|
22812
|
+
}
|
|
22813
|
+
|
|
22814
|
+
// packages/workit-core/src/hooks/heavy-gate.ts
|
|
22815
|
+
var rulesFor = (host, cwd, env) => {
|
|
22816
|
+
switch (host) {
|
|
22817
|
+
case "claude_code":
|
|
22818
|
+
return claudeRules(cwd, env);
|
|
22819
|
+
case "opencode":
|
|
22820
|
+
return opencodeRules(cwd, env);
|
|
22821
|
+
case "codex_cli":
|
|
22822
|
+
case "codex_desktop":
|
|
22823
|
+
return codexRules(cwd, env);
|
|
22824
|
+
case "cursor":
|
|
22825
|
+
return cursorRules(cwd, env);
|
|
22826
|
+
case "pi":
|
|
22827
|
+
return null;
|
|
22828
|
+
}
|
|
22829
|
+
};
|
|
22830
|
+
function heavyGate(cwd, command, options) {
|
|
22831
|
+
const env = options.env ?? process.env;
|
|
22832
|
+
try {
|
|
22833
|
+
if (env.WORKIT_HEAVY_GATE === "0" || !maybeHeavy(command, cwd, env) || !workitOnPath2(env))
|
|
22834
|
+
return { kind: "none" };
|
|
22835
|
+
return heavyPlan(command, {
|
|
22836
|
+
cwd,
|
|
22837
|
+
env,
|
|
22838
|
+
dialect: options.dialect ?? "posix",
|
|
22839
|
+
rules: rulesFor(options.host, cwd, env),
|
|
22840
|
+
bypass: options.permissionMode === "bypassPermissions"
|
|
22841
|
+
});
|
|
22842
|
+
} catch {
|
|
22843
|
+
return { kind: "none" };
|
|
22844
|
+
}
|
|
22845
|
+
}
|
|
22846
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
22847
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
22848
|
+
import os10 from "node:os";
|
|
22849
|
+
import path30 from "node:path";
|
|
21978
22850
|
|
|
21979
22851
|
// packages/workit-core/src/core/live-session.ts
|
|
21980
22852
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21981
22853
|
import { createHash as createHash14 } from "node:crypto";
|
|
21982
|
-
import
|
|
21983
|
-
import
|
|
21984
|
-
import
|
|
22854
|
+
import fs21 from "node:fs";
|
|
22855
|
+
import os9 from "node:os";
|
|
22856
|
+
import path29 from "node:path";
|
|
21985
22857
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21986
22858
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
21987
22859
|
var liveProbe = {
|
|
@@ -22002,9 +22874,9 @@ var processAlive = (pid) => {
|
|
|
22002
22874
|
var processInfo = (pid) => {
|
|
22003
22875
|
try {
|
|
22004
22876
|
if (liveProbe.platform === "linux") {
|
|
22005
|
-
const stat =
|
|
22877
|
+
const stat = fs21.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
22006
22878
|
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
22007
|
-
const command =
|
|
22879
|
+
const command = fs21.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
22008
22880
|
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
22009
22881
|
}
|
|
22010
22882
|
if (liveProbe.platform === "win32")
|
|
@@ -22055,35 +22927,35 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
22055
22927
|
};
|
|
22056
22928
|
};
|
|
22057
22929
|
var checkoutOf = (target) => {
|
|
22058
|
-
let dir =
|
|
22059
|
-
while (!
|
|
22060
|
-
dir =
|
|
22061
|
-
if (
|
|
22062
|
-
dir =
|
|
22063
|
-
const parts = dir.split(
|
|
22930
|
+
let dir = path29.resolve(target);
|
|
22931
|
+
while (!fs21.existsSync(dir) && path29.dirname(dir) !== dir)
|
|
22932
|
+
dir = path29.dirname(dir);
|
|
22933
|
+
if (fs21.existsSync(dir) && !fs21.statSync(dir).isDirectory())
|
|
22934
|
+
dir = path29.dirname(dir);
|
|
22935
|
+
const parts = dir.split(path29.sep);
|
|
22064
22936
|
const dotGit = parts.lastIndexOf(".git");
|
|
22065
22937
|
if (dotGit > 0)
|
|
22066
|
-
dir = parts.slice(0, dotGit).join(
|
|
22938
|
+
dir = parts.slice(0, dotGit).join(path29.sep) || path29.sep;
|
|
22067
22939
|
const location = resolveStore(dir);
|
|
22068
22940
|
if (location instanceof Error || !location.shared || !location.top)
|
|
22069
22941
|
return null;
|
|
22070
22942
|
let top = location.top;
|
|
22071
22943
|
try {
|
|
22072
|
-
top =
|
|
22944
|
+
top = fs21.realpathSync.native(top);
|
|
22073
22945
|
} catch {}
|
|
22074
22946
|
return top;
|
|
22075
22947
|
};
|
|
22076
|
-
var markerFile2 = (top) =>
|
|
22948
|
+
var markerFile2 = (top) => path29.join(resolveStateDir(), "live-sessions", `${createHash14("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
22077
22949
|
var checkoutKey = (top, platform = process.platform) => {
|
|
22078
|
-
let key =
|
|
22950
|
+
let key = path29.resolve(top);
|
|
22079
22951
|
try {
|
|
22080
|
-
key =
|
|
22952
|
+
key = fs21.realpathSync.native(key);
|
|
22081
22953
|
} catch {}
|
|
22082
22954
|
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
22083
22955
|
};
|
|
22084
22956
|
var readMarker = (file) => {
|
|
22085
22957
|
try {
|
|
22086
|
-
const value = JSON.parse(
|
|
22958
|
+
const value = JSON.parse(fs21.readFileSync(file, "utf8"));
|
|
22087
22959
|
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
22088
22960
|
return null;
|
|
22089
22961
|
return {
|
|
@@ -22106,7 +22978,7 @@ var isLive = (marker, deps) => {
|
|
|
22106
22978
|
const age = ageOf2(marker, deps.now ?? new Date);
|
|
22107
22979
|
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
22108
22980
|
return false;
|
|
22109
|
-
if (marker.machine !== (deps.machine ??
|
|
22981
|
+
if (marker.machine !== (deps.machine ?? os9.hostname()) || marker.pid === null)
|
|
22110
22982
|
return true;
|
|
22111
22983
|
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
22112
22984
|
return false;
|
|
@@ -22135,7 +23007,7 @@ var liveConflict = (target, owner, deps = {}) => {
|
|
|
22135
23007
|
const marker = readMarker(file);
|
|
22136
23008
|
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
22137
23009
|
return null;
|
|
22138
|
-
if (sameOwner(marker, owner, deps.machine ??
|
|
23010
|
+
if (sameOwner(marker, owner, deps.machine ?? os9.hostname()))
|
|
22139
23011
|
return null;
|
|
22140
23012
|
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
22141
23013
|
} catch {
|
|
@@ -22149,7 +23021,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
22149
23021
|
return;
|
|
22150
23022
|
const file = markerFile2(checkout);
|
|
22151
23023
|
const now = deps.now ?? new Date;
|
|
22152
|
-
const machine = deps.machine ??
|
|
23024
|
+
const machine = deps.machine ?? os9.hostname();
|
|
22153
23025
|
const current = readMarker(file);
|
|
22154
23026
|
if (current) {
|
|
22155
23027
|
const age = ageOf2(current, now);
|
|
@@ -22170,10 +23042,10 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
22170
23042
|
checkout,
|
|
22171
23043
|
heartbeatAt: now.toISOString()
|
|
22172
23044
|
};
|
|
22173
|
-
|
|
23045
|
+
fs21.mkdirSync(path29.dirname(file), { recursive: true });
|
|
22174
23046
|
const temp = `${file}.${process.pid}.tmp`;
|
|
22175
|
-
|
|
22176
|
-
|
|
23047
|
+
fs21.writeFileSync(temp, JSON.stringify(marker));
|
|
23048
|
+
fs21.renameSync(temp, file);
|
|
22177
23049
|
} catch {}
|
|
22178
23050
|
};
|
|
22179
23051
|
|
|
@@ -22182,27 +23054,42 @@ var NONE3 = { kind: "none" };
|
|
|
22182
23054
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
22183
23055
|
var canonical2 = (file) => {
|
|
22184
23056
|
let rest = "";
|
|
22185
|
-
for (let dir =
|
|
23057
|
+
for (let dir = path30.resolve(file);; dir = path30.dirname(dir)) {
|
|
22186
23058
|
try {
|
|
22187
|
-
return
|
|
23059
|
+
return path30.join(realpathSync6(dir), rest);
|
|
22188
23060
|
} catch {
|
|
22189
|
-
if (
|
|
22190
|
-
return
|
|
22191
|
-
rest = rest ?
|
|
23061
|
+
if (path30.dirname(dir) === dir)
|
|
23062
|
+
return path30.resolve(file);
|
|
23063
|
+
rest = rest ? path30.join(path30.basename(dir), rest) : path30.basename(dir);
|
|
22192
23064
|
}
|
|
22193
23065
|
}
|
|
22194
23066
|
};
|
|
22195
23067
|
var exempt = (cwd, root, file, plans) => {
|
|
22196
23068
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
22197
|
-
const relative =
|
|
22198
|
-
if (relative.startsWith("..") ||
|
|
23069
|
+
const relative = path30.relative(fold(canonical2(root)), fold(canonical2(path30.resolve(cwd, file))));
|
|
23070
|
+
if (relative.startsWith("..") || path30.isAbsolute(relative))
|
|
22199
23071
|
return true;
|
|
22200
|
-
const posix = relative.split(
|
|
23072
|
+
const posix = relative.split(path30.sep).join("/");
|
|
22201
23073
|
const segments = posix.split("/");
|
|
22202
23074
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
22203
23075
|
};
|
|
22204
23076
|
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
22205
23077
|
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
23078
|
+
var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
23079
|
+
var wrappedScript = (input) => {
|
|
23080
|
+
let words = input;
|
|
23081
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
23082
|
+
words = words.slice(1);
|
|
23083
|
+
const tail = workitCheckTail(words);
|
|
23084
|
+
if (!tail)
|
|
23085
|
+
return null;
|
|
23086
|
+
if ("script" in tail)
|
|
23087
|
+
return tail.script;
|
|
23088
|
+
const [shell, flag, script] = tail.argv;
|
|
23089
|
+
if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
|
|
23090
|
+
return script ?? null;
|
|
23091
|
+
return tail.argv.map(quoteWord2).join(" ");
|
|
23092
|
+
};
|
|
22206
23093
|
var editsOf = (input) => {
|
|
22207
23094
|
let words = input;
|
|
22208
23095
|
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
@@ -22279,10 +23166,18 @@ var editsOf = (input) => {
|
|
|
22279
23166
|
return [];
|
|
22280
23167
|
}
|
|
22281
23168
|
};
|
|
22282
|
-
var shellWrites = (command) => {
|
|
23169
|
+
var shellWrites = (command, depth = 0) => {
|
|
22283
23170
|
const targets = [];
|
|
22284
23171
|
let unknown = false;
|
|
22285
23172
|
for (const segment of segmentsOf(command)) {
|
|
23173
|
+
const inner = depth < 4 ? wrappedScript(segment.words) : null;
|
|
23174
|
+
if (inner !== null) {
|
|
23175
|
+
const nested = shellWrites(inner, depth + 1);
|
|
23176
|
+
if (nested.writes && nested.targets === null)
|
|
23177
|
+
unknown = true;
|
|
23178
|
+
else if (nested.targets)
|
|
23179
|
+
targets.push(...nested.targets);
|
|
23180
|
+
}
|
|
22286
23181
|
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
22287
23182
|
const edits = editsOf(segment.words);
|
|
22288
23183
|
if (edits === null)
|
|
@@ -22295,7 +23190,7 @@ var shellWrites = (command) => {
|
|
|
22295
23190
|
return { writes: true, targets: unknown ? null : targets };
|
|
22296
23191
|
};
|
|
22297
23192
|
var cache2 = new Map;
|
|
22298
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
23193
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path30.resolve(root, file)) ?? "-").join("\x00");
|
|
22299
23194
|
var gateFor = (cwd) => {
|
|
22300
23195
|
const store = new TaskStore(cwd);
|
|
22301
23196
|
const listed = store.listTaskIndex();
|
|
@@ -22374,7 +23269,7 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
22374
23269
|
return [];
|
|
22375
23270
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
22376
23271
|
return [];
|
|
22377
|
-
return [
|
|
23272
|
+
return [path30.resolve(cwd, invocation.dir ?? ".")];
|
|
22378
23273
|
});
|
|
22379
23274
|
};
|
|
22380
23275
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
@@ -22384,15 +23279,15 @@ var cdInto = (dir, words) => {
|
|
|
22384
23279
|
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
22385
23280
|
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
22386
23281
|
return;
|
|
22387
|
-
return target === undefined ?
|
|
23282
|
+
return target === undefined ? os10.homedir() : path30.resolve(dir, target);
|
|
22388
23283
|
};
|
|
22389
|
-
var
|
|
22390
|
-
var segmentTargets = (cwd, command, dialect) => {
|
|
23284
|
+
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
23285
|
+
var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
22391
23286
|
const targets = [];
|
|
22392
23287
|
let dir = cwd;
|
|
22393
23288
|
let stack = [];
|
|
22394
23289
|
const groups = [];
|
|
22395
|
-
const at = (file) =>
|
|
23290
|
+
const at = (file) => path30.isAbsolute(file) ? file : dir === undefined ? null : path30.resolve(dir, file);
|
|
22396
23291
|
for (const segment of segmentsOf(command, dialect)) {
|
|
22397
23292
|
if (segment.group === "open") {
|
|
22398
23293
|
groups.push({ dir, stack: [...stack] });
|
|
@@ -22420,6 +23315,9 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
22420
23315
|
dir = stack.length ? stack.pop() : undefined;
|
|
22421
23316
|
continue;
|
|
22422
23317
|
}
|
|
23318
|
+
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
23319
|
+
if (inner !== null && dir !== undefined)
|
|
23320
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
22423
23321
|
for (const file of segment.redirects)
|
|
22424
23322
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
22425
23323
|
const resolved = at(file);
|
|
@@ -22429,15 +23327,15 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
22429
23327
|
const edits = editsOf(segment.words);
|
|
22430
23328
|
if (edits === null) {
|
|
22431
23329
|
if (dir !== undefined)
|
|
22432
|
-
targets.push(
|
|
23330
|
+
targets.push(path30.join(dir, DIR_TARGET));
|
|
22433
23331
|
} else
|
|
22434
23332
|
for (const file of edits) {
|
|
22435
23333
|
const resolved = at(file);
|
|
22436
23334
|
if (resolved)
|
|
22437
23335
|
targets.push(resolved);
|
|
22438
23336
|
}
|
|
22439
|
-
if (dir !== undefined && workitInvocations(segment.words.map(
|
|
22440
|
-
targets.push(
|
|
23337
|
+
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), "")))
|
|
23338
|
+
targets.push(path30.join(dir, DIR_TARGET));
|
|
22441
23339
|
}
|
|
22442
23340
|
return targets;
|
|
22443
23341
|
};
|
|
@@ -22472,7 +23370,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
22472
23370
|
var guarded = (targets) => {
|
|
22473
23371
|
const seen = new Set;
|
|
22474
23372
|
return targets.filter((target) => {
|
|
22475
|
-
const dir =
|
|
23373
|
+
const dir = path30.dirname(target);
|
|
22476
23374
|
if (seen.has(dir))
|
|
22477
23375
|
return false;
|
|
22478
23376
|
seen.add(dir);
|
|
@@ -22493,7 +23391,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
22493
23391
|
for (const target of guarded(targets))
|
|
22494
23392
|
claimLiveSession(target, owner, deps);
|
|
22495
23393
|
};
|
|
22496
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
23394
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path30.resolve(cwd, file)) : [path30.join(cwd, DIR_TARGET)];
|
|
22497
23395
|
function writeGate(cwd, paths, owner, deps) {
|
|
22498
23396
|
if (!owner)
|
|
22499
23397
|
return beforeWriteGate(cwd, paths);
|
|
@@ -22513,7 +23411,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
22513
23411
|
try {
|
|
22514
23412
|
targets = [
|
|
22515
23413
|
...segmentTargets(cwd, command, dialect),
|
|
22516
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
23414
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path30.join(dir, DIR_TARGET))
|
|
22517
23415
|
];
|
|
22518
23416
|
} catch {
|
|
22519
23417
|
targets = [];
|
|
@@ -22955,6 +23853,18 @@ var WRITE_TOOLS = new Set([
|
|
|
22955
23853
|
]);
|
|
22956
23854
|
// packages/workit-core/src/hooks/handle.ts
|
|
22957
23855
|
var offered = new Set;
|
|
23856
|
+
// packages/workit-core/src/hooks/deadline.ts
|
|
23857
|
+
var QUIET = new Set([
|
|
23858
|
+
"stop",
|
|
23859
|
+
"subagent.stop",
|
|
23860
|
+
"shell.post",
|
|
23861
|
+
"compact.pre",
|
|
23862
|
+
"prompt.submit",
|
|
23863
|
+
null
|
|
23864
|
+
]);
|
|
23865
|
+
var TIMED_OUT = Symbol("timed out");
|
|
23866
|
+
var PROCESS_START = performance.timeOrigin;
|
|
23867
|
+
|
|
22958
23868
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
22959
23869
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
22960
23870
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
@@ -22996,7 +23906,7 @@ var OPENCODE_DESCRIPTOR = {
|
|
|
22996
23906
|
"compact.pre": { support: "native", native: 'session.hook("compaction")' },
|
|
22997
23907
|
stop: { support: "partial", native: 'event("session.idle")' }
|
|
22998
23908
|
},
|
|
22999
|
-
shellPolicy: { deny: "native", channel: "effect", failClosed: false },
|
|
23909
|
+
shellPolicy: { deny: "native", channel: "effect", failClosed: false, rewrite: "native" },
|
|
23000
23910
|
context: {
|
|
23001
23911
|
sessionStart: "none",
|
|
23002
23912
|
perTurn: "native",
|
|
@@ -23045,11 +23955,11 @@ var OPENCODE_DESCRIPTOR = {
|
|
|
23045
23955
|
]
|
|
23046
23956
|
};
|
|
23047
23957
|
// packages/workit-opencode/src/shared/init-apply.ts
|
|
23048
|
-
import
|
|
23958
|
+
import path35 from "node:path";
|
|
23049
23959
|
|
|
23050
23960
|
// packages/workit-core/src/core/gitignore.ts
|
|
23051
23961
|
import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
23052
|
-
import
|
|
23962
|
+
import path31 from "node:path";
|
|
23053
23963
|
var GITIGNORE_ENTRIES = [
|
|
23054
23964
|
"# workit: SDD working state (never commit)",
|
|
23055
23965
|
"docs/*/sdd/",
|
|
@@ -23069,7 +23979,7 @@ var GITIGNORE_ENTRIES = [
|
|
|
23069
23979
|
var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
23070
23980
|
if (!confirmed)
|
|
23071
23981
|
return { ok: false, error: "confirmed: true required" };
|
|
23072
|
-
const file =
|
|
23982
|
+
const file = path31.join(workspaceRoot, ".gitignore");
|
|
23073
23983
|
const existing = existsSync9(file) ? readFileSync6(file, "utf8") : "";
|
|
23074
23984
|
const existingLines = new Set(existing.split(`
|
|
23075
23985
|
`).map((l) => l.trim()).filter(Boolean));
|
|
@@ -23097,25 +24007,25 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
|
23097
24007
|
|
|
23098
24008
|
// packages/workit-core/src/core/hygiene.ts
|
|
23099
24009
|
import { existsSync as existsSync10, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
23100
|
-
import
|
|
24010
|
+
import path32 from "node:path";
|
|
23101
24011
|
var repoRoot3 = assetRoot();
|
|
23102
|
-
var templatesDir = () =>
|
|
24012
|
+
var templatesDir = () => path32.join(repoRoot3, "templates", "hygiene");
|
|
23103
24013
|
var packageJson = (root) => {
|
|
23104
|
-
if (!existsSync10(
|
|
24014
|
+
if (!existsSync10(path32.join(root, "package.json")))
|
|
23105
24015
|
return null;
|
|
23106
24016
|
try {
|
|
23107
|
-
return JSON.parse(readFileSync7(
|
|
24017
|
+
return JSON.parse(readFileSync7(path32.join(root, "package.json"), "utf8"));
|
|
23108
24018
|
} catch {
|
|
23109
24019
|
return null;
|
|
23110
24020
|
}
|
|
23111
24021
|
};
|
|
23112
24022
|
var isOpenSource = (root) => {
|
|
23113
|
-
if (existsSync10(
|
|
24023
|
+
if (existsSync10(path32.join(root, "LICENSE")))
|
|
23114
24024
|
return true;
|
|
23115
24025
|
const pkg = packageJson(root);
|
|
23116
24026
|
if (pkg && !pkg.private)
|
|
23117
24027
|
return true;
|
|
23118
|
-
return
|
|
24028
|
+
return path32.basename(root) === "workflow-toolkit";
|
|
23119
24029
|
};
|
|
23120
24030
|
var licenseHolder = (root) => {
|
|
23121
24031
|
const pkg = packageJson(root);
|
|
@@ -23138,13 +24048,13 @@ var planHygieneFiles = (root, opts = {}) => {
|
|
|
23138
24048
|
files.push("LICENSE", "CONTRIBUTING.md");
|
|
23139
24049
|
const planned = [];
|
|
23140
24050
|
for (const file of files) {
|
|
23141
|
-
if (existsSync10(
|
|
24051
|
+
if (existsSync10(path32.join(root, file)))
|
|
23142
24052
|
continue;
|
|
23143
|
-
const tpl =
|
|
24053
|
+
const tpl = path32.join(templatesDir(), file);
|
|
23144
24054
|
if (!existsSync10(tpl))
|
|
23145
24055
|
continue;
|
|
23146
|
-
const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g,
|
|
23147
|
-
planned.push({ path:
|
|
24056
|
+
const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path32.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
24057
|
+
planned.push({ path: path32.join(root, file), content });
|
|
23148
24058
|
}
|
|
23149
24059
|
return planned;
|
|
23150
24060
|
};
|
|
@@ -23154,19 +24064,19 @@ var ensureHygieneFiles = (root, opts) => {
|
|
|
23154
24064
|
const created = [];
|
|
23155
24065
|
for (const planned of planHygieneFiles(root, opts)) {
|
|
23156
24066
|
writeFileSync5(planned.path, planned.content, "utf8");
|
|
23157
|
-
created.push(
|
|
24067
|
+
created.push(path32.basename(planned.path));
|
|
23158
24068
|
}
|
|
23159
24069
|
return { ok: true, created };
|
|
23160
24070
|
};
|
|
23161
24071
|
|
|
23162
24072
|
// packages/workit-core/src/core/init.ts
|
|
23163
|
-
import
|
|
23164
|
-
import
|
|
24073
|
+
import fs22 from "node:fs";
|
|
24074
|
+
import path34 from "node:path";
|
|
23165
24075
|
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
23166
24076
|
|
|
23167
24077
|
// packages/workit-core/src/core/workspace-branch-policy.ts
|
|
23168
24078
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
23169
|
-
import
|
|
24079
|
+
import path33 from "node:path";
|
|
23170
24080
|
import { isDeepStrictEqual } from "node:util";
|
|
23171
24081
|
var readFileSafe = (p) => {
|
|
23172
24082
|
try {
|
|
@@ -23177,7 +24087,7 @@ var readFileSafe = (p) => {
|
|
|
23177
24087
|
};
|
|
23178
24088
|
function applyWorkspaceBranchPolicy(opts) {
|
|
23179
24089
|
const { workspace_root, env = process.env } = opts;
|
|
23180
|
-
const dir =
|
|
24090
|
+
const dir = path33.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
23181
24091
|
const {
|
|
23182
24092
|
status,
|
|
23183
24093
|
path: wsPath,
|
|
@@ -23189,7 +24099,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
23189
24099
|
if (status === "malformed" || status === "invalid")
|
|
23190
24100
|
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
23191
24101
|
const detection = detectBranchPolicy(workspace_root);
|
|
23192
|
-
const name = String(env.WORKFLOW_BP_NAME ??
|
|
24102
|
+
const name = String(env.WORKFLOW_BP_NAME ?? path33.basename(workspace_root));
|
|
23193
24103
|
const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
|
|
23194
24104
|
const policy = {
|
|
23195
24105
|
preset: detection.preset,
|
|
@@ -23238,7 +24148,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
23238
24148
|
ok: false,
|
|
23239
24149
|
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
23240
24150
|
};
|
|
23241
|
-
mkdirSync4(
|
|
24151
|
+
mkdirSync4(path33.dirname(wsPath), { recursive: true });
|
|
23242
24152
|
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
23243
24153
|
try {
|
|
23244
24154
|
writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
|
|
@@ -23271,9 +24181,9 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
23271
24181
|
|
|
23272
24182
|
// packages/workit-core/src/core/init.ts
|
|
23273
24183
|
var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
|
|
23274
|
-
var
|
|
24184
|
+
var readJson3 = (p) => {
|
|
23275
24185
|
try {
|
|
23276
|
-
const parsed = JSON.parse(
|
|
24186
|
+
const parsed = JSON.parse(fs22.readFileSync(p, "utf8"));
|
|
23277
24187
|
return isConfigObject(parsed) ? parsed : null;
|
|
23278
24188
|
} catch {
|
|
23279
24189
|
return null;
|
|
@@ -23303,7 +24213,7 @@ var youtrackJsonContent = (dir) => {
|
|
|
23303
24213
|
const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
|
|
23304
24214
|
return {
|
|
23305
24215
|
baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
|
|
23306
|
-
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ??
|
|
24216
|
+
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path34.join(dir, "youtrack.token"),
|
|
23307
24217
|
...meetingIssue ? {
|
|
23308
24218
|
meetingIssue,
|
|
23309
24219
|
meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
|
|
@@ -23333,19 +24243,19 @@ var vcsJsonContent = () => {
|
|
|
23333
24243
|
};
|
|
23334
24244
|
function initApplyData(action, env = process.env) {
|
|
23335
24245
|
const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
23336
|
-
|
|
24246
|
+
fs22.mkdirSync(dir, { recursive: true });
|
|
23337
24247
|
switch (action) {
|
|
23338
24248
|
case "youtrack_json": {
|
|
23339
|
-
const out =
|
|
23340
|
-
|
|
24249
|
+
const out = path34.join(dir, "youtrack.json");
|
|
24250
|
+
fs22.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
23341
24251
|
`, "utf8");
|
|
23342
24252
|
return { action, ok: true, path: out };
|
|
23343
24253
|
}
|
|
23344
24254
|
case "youtrack_token_placeholder": {
|
|
23345
|
-
const p =
|
|
24255
|
+
const p = path34.join(dir, "youtrack.token");
|
|
23346
24256
|
const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
|
|
23347
24257
|
`, 384) === "preserved";
|
|
23348
|
-
const abs =
|
|
24258
|
+
const abs = path34.resolve(p);
|
|
23349
24259
|
return {
|
|
23350
24260
|
action,
|
|
23351
24261
|
ok: true,
|
|
@@ -23357,19 +24267,19 @@ function initApplyData(action, env = process.env) {
|
|
|
23357
24267
|
};
|
|
23358
24268
|
}
|
|
23359
24269
|
case "youtrack_scaffold": {
|
|
23360
|
-
const jsonOut =
|
|
23361
|
-
const tokenOut =
|
|
23362
|
-
|
|
24270
|
+
const jsonOut = path34.join(dir, "youtrack.json");
|
|
24271
|
+
const tokenOut = path34.join(dir, "youtrack.token");
|
|
24272
|
+
fs22.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
23363
24273
|
`, "utf8");
|
|
23364
24274
|
const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
|
|
23365
24275
|
`, 384) === "preserved";
|
|
23366
|
-
const configPath =
|
|
23367
|
-
const tokenPath =
|
|
24276
|
+
const configPath = path34.resolve(jsonOut);
|
|
24277
|
+
const tokenPath = path34.resolve(tokenOut);
|
|
23368
24278
|
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
23369
24279
|
process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
|
|
23370
24280
|
let tokenCreate = {};
|
|
23371
24281
|
try {
|
|
23372
|
-
const cfg =
|
|
24282
|
+
const cfg = readJson3(configPath) ?? {};
|
|
23373
24283
|
const base = String(cfg.baseUrl ?? "").replace(/\/+$/, "");
|
|
23374
24284
|
const meeting = String(cfg.meetingIssue ?? "");
|
|
23375
24285
|
tokenCreate = youTrackTokenCreateUrl().data;
|
|
@@ -23416,14 +24326,14 @@ function initApplyData(action, env = process.env) {
|
|
|
23416
24326
|
}
|
|
23417
24327
|
}
|
|
23418
24328
|
case "vcs_scaffold": {
|
|
23419
|
-
const jsonOut =
|
|
23420
|
-
|
|
24329
|
+
const jsonOut = path34.join(dir, "vcs.json");
|
|
24330
|
+
fs22.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
|
|
23421
24331
|
`, "utf8");
|
|
23422
|
-
const configPath =
|
|
24332
|
+
const configPath = path34.resolve(jsonOut);
|
|
23423
24333
|
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
23424
24334
|
process.env.WORKFLOW_VCS_CONFIG = configPath;
|
|
23425
24335
|
try {
|
|
23426
|
-
const cfg =
|
|
24336
|
+
const cfg = readJson3(configPath) ?? {};
|
|
23427
24337
|
const rawProvider = cfg.provider;
|
|
23428
24338
|
const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider : null;
|
|
23429
24339
|
return {
|
|
@@ -23538,7 +24448,7 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
23538
24448
|
protectedNames: args.branch_policy_protected
|
|
23539
24449
|
}, current);
|
|
23540
24450
|
writeConfig(next);
|
|
23541
|
-
return output(ok({ action: "config", path:
|
|
24451
|
+
return output(ok({ action: "config", path: path35.join(configDir(), "config.json"), ...next }));
|
|
23542
24452
|
}
|
|
23543
24453
|
const env = Object.fromEntries(Object.entries({
|
|
23544
24454
|
WORKFLOW_YT_BASE_URL: args.base_url,
|
|
@@ -23555,14 +24465,14 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
23555
24465
|
|
|
23556
24466
|
// packages/workit-opencode/src/shared/session.ts
|
|
23557
24467
|
import { realpathSync as realpathSync7 } from "node:fs";
|
|
23558
|
-
import
|
|
24468
|
+
import path36 from "node:path";
|
|
23559
24469
|
var sameWorkspace = (expected, observed) => {
|
|
23560
24470
|
if (typeof observed !== "string" || !observed)
|
|
23561
24471
|
return false;
|
|
23562
24472
|
try {
|
|
23563
24473
|
return realpathSync7(expected) === realpathSync7(observed);
|
|
23564
24474
|
} catch {
|
|
23565
|
-
return
|
|
24475
|
+
return path36.resolve(expected) === path36.resolve(observed);
|
|
23566
24476
|
}
|
|
23567
24477
|
};
|
|
23568
24478
|
|
|
@@ -23634,7 +24544,7 @@ var workitFamilyOf = (toolName) => {
|
|
|
23634
24544
|
|
|
23635
24545
|
// packages/workit-opencode/src/tools/context.ts
|
|
23636
24546
|
import { realpathSync as realpathSync8 } from "node:fs";
|
|
23637
|
-
import
|
|
24547
|
+
import path37 from "node:path";
|
|
23638
24548
|
var createContextTool = () => ({
|
|
23639
24549
|
description: "Read Git, pull request, YouTrack, issue, changelog, release, or affected-file context.",
|
|
23640
24550
|
execute: async (args, context) => {
|
|
@@ -23644,7 +24554,7 @@ var createContextTool = () => ({
|
|
|
23644
24554
|
let root;
|
|
23645
24555
|
try {
|
|
23646
24556
|
const cwd = parsed.data.cwd;
|
|
23647
|
-
root = realpathSync8(cwd ?
|
|
24557
|
+
root = realpathSync8(cwd ? path37.resolve(context.directory, cwd) : context.directory);
|
|
23648
24558
|
} catch {
|
|
23649
24559
|
return JSON.stringify(failure2("invalid_input", "context target directory cannot be resolved"), null, 2);
|
|
23650
24560
|
}
|
|
@@ -23675,19 +24585,19 @@ var workerCoordinatorFor = (task, worker) => {
|
|
|
23675
24585
|
|
|
23676
24586
|
// packages/workit-core/src/fanout.ts
|
|
23677
24587
|
import { createHash as createHash16 } from "node:crypto";
|
|
23678
|
-
import
|
|
23679
|
-
import
|
|
24588
|
+
import fs24 from "node:fs";
|
|
24589
|
+
import path40 from "node:path";
|
|
23680
24590
|
|
|
23681
24591
|
// packages/workit-core/src/stack.ts
|
|
23682
24592
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
23683
24593
|
import { createHash as createHash15, randomBytes as randomBytes4 } from "node:crypto";
|
|
23684
|
-
import
|
|
23685
|
-
import
|
|
24594
|
+
import fs23 from "node:fs";
|
|
24595
|
+
import path39 from "node:path";
|
|
23686
24596
|
|
|
23687
24597
|
// packages/workit-core/src/git/ops.ts
|
|
23688
24598
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
23689
24599
|
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
|
|
23690
|
-
import
|
|
24600
|
+
import path38 from "node:path";
|
|
23691
24601
|
|
|
23692
24602
|
// packages/workit-core/src/forge/redact.ts
|
|
23693
24603
|
var KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
@@ -23731,18 +24641,18 @@ function operationInProgress(cwd) {
|
|
|
23731
24641
|
if (!run.ok)
|
|
23732
24642
|
return null;
|
|
23733
24643
|
const found = run.stdout.split(`
|
|
23734
|
-
`).map((line) =>
|
|
24644
|
+
`).map((line) => path38.resolve(cwd, line.trim()));
|
|
23735
24645
|
const conflicted = () => git5(cwd, ["ls-files", "-u"]).stdout.trim() !== "";
|
|
23736
24646
|
for (const [index, [, operation]] of markers.entries()) {
|
|
23737
24647
|
const marker = found[index];
|
|
23738
24648
|
if (!marker || !existsSync11(marker))
|
|
23739
24649
|
continue;
|
|
23740
|
-
if (index === 1 && existsSync11(
|
|
24650
|
+
if (index === 1 && existsSync11(path38.join(marker, "applying")))
|
|
23741
24651
|
return { operation: "am", commitAllowed: false, branch: null };
|
|
23742
|
-
if (index === 0 && existsSync11(
|
|
24652
|
+
if (index === 0 && existsSync11(path38.join(marker, "amend")) && !conflicted()) {
|
|
23743
24653
|
let headName = "";
|
|
23744
24654
|
try {
|
|
23745
|
-
headName = readFileSync9(
|
|
24655
|
+
headName = readFileSync9(path38.join(marker, "head-name"), "utf8").trim();
|
|
23746
24656
|
} catch {}
|
|
23747
24657
|
const branch = headName.startsWith("refs/heads/") ? headName.slice("refs/heads/".length) : null;
|
|
23748
24658
|
return { operation, commitAllowed: branch !== null, branch };
|
|
@@ -23854,7 +24764,7 @@ function rebasingBranch2(dir) {
|
|
|
23854
24764
|
if (!found.ok)
|
|
23855
24765
|
continue;
|
|
23856
24766
|
try {
|
|
23857
|
-
return
|
|
24767
|
+
return fs23.readFileSync(found.stdout.trim(), "utf8").trim();
|
|
23858
24768
|
} catch {}
|
|
23859
24769
|
}
|
|
23860
24770
|
return null;
|
|
@@ -23862,14 +24772,14 @@ function rebasingBranch2(dir) {
|
|
|
23862
24772
|
function rebaseInProgress(dir) {
|
|
23863
24773
|
for (const name of ["rebase-merge", "rebase-apply"]) {
|
|
23864
24774
|
const found = gitRun3(dir, ["rev-parse", "--path-format=absolute", "--git-path", name]);
|
|
23865
|
-
if (found.ok &&
|
|
24775
|
+
if (found.ok && fs23.existsSync(found.stdout.trim()))
|
|
23866
24776
|
return true;
|
|
23867
24777
|
}
|
|
23868
24778
|
return false;
|
|
23869
24779
|
}
|
|
23870
24780
|
var scratchDir = (cwd) => {
|
|
23871
24781
|
const dir = stacksDir(cwd);
|
|
23872
|
-
return dir.ok ?
|
|
24782
|
+
return dir.ok ? path39.join(dir.data, "worktrees") : null;
|
|
23873
24783
|
};
|
|
23874
24784
|
function gitRestack(cwd, input) {
|
|
23875
24785
|
const owner = worktreeOf(cwd, input.branch);
|
|
@@ -23878,8 +24788,8 @@ function gitRestack(cwd, input) {
|
|
|
23878
24788
|
const scratch = scratchDir(cwd);
|
|
23879
24789
|
if (!scratch)
|
|
23880
24790
|
return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
|
|
23881
|
-
|
|
23882
|
-
dir =
|
|
24791
|
+
fs23.mkdirSync(scratch, { recursive: true });
|
|
24792
|
+
dir = path39.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes4(4).toString("hex")}`);
|
|
23883
24793
|
const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
|
|
23884
24794
|
if (!added.ok)
|
|
23885
24795
|
return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
|
|
@@ -23923,13 +24833,14 @@ function stacksDir(cwd) {
|
|
|
23923
24833
|
const root = storeRoot(cwd);
|
|
23924
24834
|
if (!root.ok)
|
|
23925
24835
|
return stackFail(root.code, root.error, root.unblock);
|
|
23926
|
-
return { ok: true, data:
|
|
24836
|
+
return { ok: true, data: path39.join(root.value.root, "stacks") };
|
|
23927
24837
|
}
|
|
23928
24838
|
var HEARTBEAT_STALE_MS = 2 * 60000;
|
|
23929
24839
|
|
|
23930
24840
|
// packages/workit-core/src/fanout.ts
|
|
23931
24841
|
var FANOUT_VERSION = 1;
|
|
23932
24842
|
var TIERS = ["mundane", "standard", "hard"];
|
|
24843
|
+
var REASONS = ["explore", "parallel", "backstop", "high-risk-verify"];
|
|
23933
24844
|
var fanoutFail = (code, error, unblock, data) => ({
|
|
23934
24845
|
ok: false,
|
|
23935
24846
|
code,
|
|
@@ -23985,7 +24896,7 @@ function normalizePattern(raw) {
|
|
|
23985
24896
|
return { error: "has unbalanced or nested braces" };
|
|
23986
24897
|
return { pattern };
|
|
23987
24898
|
}
|
|
23988
|
-
var
|
|
24899
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23989
24900
|
function findCycle(slices) {
|
|
23990
24901
|
const byId = new Map(slices.map((slice) => [slice.id, slice]));
|
|
23991
24902
|
const state = new Map;
|
|
@@ -24021,6 +24932,7 @@ function sliceHash(slice) {
|
|
|
24021
24932
|
hash: _hash,
|
|
24022
24933
|
hashSince: _since,
|
|
24023
24934
|
tier: _tier,
|
|
24935
|
+
reason: _reason,
|
|
24024
24936
|
timebox: _timebox,
|
|
24025
24937
|
context: _context,
|
|
24026
24938
|
issues: _issues,
|
|
@@ -24032,10 +24944,10 @@ function fanoutsDir(cwd) {
|
|
|
24032
24944
|
const root = storeRoot(cwd);
|
|
24033
24945
|
if (!root.ok)
|
|
24034
24946
|
return fanoutFail(root.code, root.error, root.unblock);
|
|
24035
|
-
return { ok: true, data:
|
|
24947
|
+
return { ok: true, data: path40.join(root.value.root, "fanouts") };
|
|
24036
24948
|
}
|
|
24037
24949
|
function parseStored(raw) {
|
|
24038
|
-
if (!
|
|
24950
|
+
if (!isRecord7(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
|
|
24039
24951
|
return null;
|
|
24040
24952
|
if (!Array.isArray(raw.slices))
|
|
24041
24953
|
return null;
|
|
@@ -24052,7 +24964,7 @@ function parseStored(raw) {
|
|
|
24052
24964
|
});
|
|
24053
24965
|
const slices = [];
|
|
24054
24966
|
for (const item of raw.slices) {
|
|
24055
|
-
if (!
|
|
24967
|
+
if (!isRecord7(item) || typeof item.id !== "string" || typeof item.branch !== "string")
|
|
24056
24968
|
return null;
|
|
24057
24969
|
slices.push({
|
|
24058
24970
|
id: item.id,
|
|
@@ -24060,6 +24972,7 @@ function parseStored(raw) {
|
|
|
24060
24972
|
base: text(item.base) || raw.trunk,
|
|
24061
24973
|
worktree: text(item.worktree),
|
|
24062
24974
|
tier: TIERS.includes(item.tier) ? item.tier : "standard",
|
|
24975
|
+
reason: REASONS.includes(item.reason) ? item.reason : null,
|
|
24063
24976
|
dependsOn: list(item.dependsOn),
|
|
24064
24977
|
scope: globs(item.scope),
|
|
24065
24978
|
owns: globs(item.owns),
|
|
@@ -24099,10 +25012,10 @@ function readFanout(cwd, name) {
|
|
|
24099
25012
|
const dir = fanoutsDir(cwd);
|
|
24100
25013
|
if (!dir.ok)
|
|
24101
25014
|
return dir;
|
|
24102
|
-
const file =
|
|
25015
|
+
const file = path40.join(dir.data, stackFileName(name));
|
|
24103
25016
|
let parsed = null;
|
|
24104
25017
|
try {
|
|
24105
|
-
parsed = parseStored(JSON.parse(
|
|
25018
|
+
parsed = parseStored(JSON.parse(fs24.readFileSync(file, "utf8")));
|
|
24106
25019
|
} catch (error) {
|
|
24107
25020
|
if (error.code === "ENOENT")
|
|
24108
25021
|
return { ok: true, data: null };
|
|
@@ -24116,7 +25029,7 @@ function listFanouts(cwd) {
|
|
|
24116
25029
|
return dir;
|
|
24117
25030
|
let names = [];
|
|
24118
25031
|
try {
|
|
24119
|
-
names =
|
|
25032
|
+
names = fs24.readdirSync(dir.data).filter((entry) => entry.endsWith(".json"));
|
|
24120
25033
|
} catch {
|
|
24121
25034
|
return { ok: true, data: [] };
|
|
24122
25035
|
}
|
|
@@ -24124,7 +25037,7 @@ function listFanouts(cwd) {
|
|
|
24124
25037
|
for (const entry of names) {
|
|
24125
25038
|
let parsed = null;
|
|
24126
25039
|
try {
|
|
24127
|
-
parsed = parseStored(JSON.parse(
|
|
25040
|
+
parsed = parseStored(JSON.parse(fs24.readFileSync(path40.join(dir.data, entry), "utf8")));
|
|
24128
25041
|
} catch {
|
|
24129
25042
|
parsed = null;
|
|
24130
25043
|
}
|
|
@@ -24755,6 +25668,11 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
|
|
|
24755
25668
|
const gated = shellGate(cwd, resource, owner);
|
|
24756
25669
|
if (gated.kind === "deny")
|
|
24757
25670
|
return deny(gated.reason);
|
|
25671
|
+
if (event.effect === "allow") {
|
|
25672
|
+
const plan = heavyGate(cwd, resource, { host: "opencode" });
|
|
25673
|
+
if (plan.kind === "split")
|
|
25674
|
+
return deny(plan.reason);
|
|
25675
|
+
}
|
|
24758
25676
|
}
|
|
24759
25677
|
};
|
|
24760
25678
|
|
|
@@ -24778,7 +25696,7 @@ var childTaskMissing = (store, input) => {
|
|
|
24778
25696
|
var childDenial = (identity) => `OpenCode child session has no validated Workit worker: as ${identity} it may only inspect the task summary (workit_task action "inspect") or record evidence or findings (workit_evidence/workit_finding action "record") on an existing task; for anything else the lead assigns it a worker with workit_worker before launching it`;
|
|
24779
25697
|
|
|
24780
25698
|
// packages/workit-opencode/src/v2/shell.ts
|
|
24781
|
-
import
|
|
25699
|
+
import path41 from "node:path";
|
|
24782
25700
|
var commandOf = (input) => {
|
|
24783
25701
|
if (input === null || typeof input !== "object")
|
|
24784
25702
|
return null;
|
|
@@ -24787,12 +25705,28 @@ var commandOf = (input) => {
|
|
|
24787
25705
|
};
|
|
24788
25706
|
var hookInput = (root, sessionID, id, shell) => ({
|
|
24789
25707
|
host: "opencode",
|
|
24790
|
-
cwd: shell.workdir ?
|
|
25708
|
+
cwd: shell.workdir ? path41.resolve(root, shell.workdir) : root,
|
|
24791
25709
|
session: { id: sessionID, agentId: null, agentType: null, parentId: null },
|
|
24792
25710
|
permissionMode: null,
|
|
24793
25711
|
transcriptPath: null,
|
|
24794
25712
|
event: { kind: "shell.pre", command: shell.command, toolUseId: id }
|
|
24795
25713
|
});
|
|
25714
|
+
var leaseRewrite2 = (input) => {
|
|
25715
|
+
if (input.event.kind !== "shell.pre")
|
|
25716
|
+
return null;
|
|
25717
|
+
const { command } = input.event;
|
|
25718
|
+
const plan = heavyGate(input.cwd, command, { host: "opencode" });
|
|
25719
|
+
if (plan.kind !== "rewrite")
|
|
25720
|
+
return null;
|
|
25721
|
+
if (shellPolicy(input.cwd, command, "posix").kind === "deny")
|
|
25722
|
+
return null;
|
|
25723
|
+
if (rawGitPre(input, command, { nudge: false }).kind === "deny")
|
|
25724
|
+
return null;
|
|
25725
|
+
const owner = liveOwner({ host: "opencode", session: { id: input.session.id, parentId: null } }, "in-process-plugin");
|
|
25726
|
+
if (shellGate(input.cwd, command, owner).kind === "deny")
|
|
25727
|
+
return null;
|
|
25728
|
+
return plan.command;
|
|
25729
|
+
};
|
|
24796
25730
|
var observeShellBefore = (root, event, workdirs) => {
|
|
24797
25731
|
try {
|
|
24798
25732
|
if (event.tool !== "shell")
|
|
@@ -24801,6 +25735,12 @@ var observeShellBefore = (root, event, workdirs) => {
|
|
|
24801
25735
|
if (!shell)
|
|
24802
25736
|
return;
|
|
24803
25737
|
const sessionID = String(event.sessionID);
|
|
25738
|
+
const original = hookInput(root, sessionID, String(event.id), shell);
|
|
25739
|
+
const heavy = leaseRewrite2(original);
|
|
25740
|
+
if (heavy) {
|
|
25741
|
+
event.input.command = heavy;
|
|
25742
|
+
shell.command = heavy;
|
|
25743
|
+
}
|
|
24804
25744
|
const input = hookInput(root, sessionID, String(event.id), shell);
|
|
24805
25745
|
workdirs.set(sessionID, input.cwd);
|
|
24806
25746
|
if (workdirs.size > 256)
|
|
@@ -24972,37 +25912,37 @@ var createStopControl = (root, port) => {
|
|
|
24972
25912
|
|
|
24973
25913
|
// packages/workit-opencode/src/v2/registry.ts
|
|
24974
25914
|
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
|
|
24975
|
-
import
|
|
25915
|
+
import path43 from "node:path";
|
|
24976
25916
|
|
|
24977
25917
|
// packages/workit-opencode/src/shared/assets.ts
|
|
24978
25918
|
import { existsSync as existsSync12, realpathSync as realpathSync9 } from "node:fs";
|
|
24979
|
-
import
|
|
25919
|
+
import path42 from "node:path";
|
|
24980
25920
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
24981
25921
|
var moduleDir = () => {
|
|
24982
25922
|
try {
|
|
24983
|
-
return realpathSync9(
|
|
25923
|
+
return realpathSync9(path42.dirname(fileURLToPath2(import.meta.url)));
|
|
24984
25924
|
} catch {
|
|
24985
|
-
return
|
|
25925
|
+
return path42.dirname(fileURLToPath2(import.meta.url));
|
|
24986
25926
|
}
|
|
24987
25927
|
};
|
|
24988
25928
|
var packageRoot2 = () => {
|
|
24989
25929
|
const dir = moduleDir();
|
|
24990
25930
|
for (const candidate of [
|
|
24991
|
-
|
|
24992
|
-
|
|
24993
|
-
|
|
25931
|
+
path42.resolve(dir, ".."),
|
|
25932
|
+
path42.resolve(dir, "..", ".."),
|
|
25933
|
+
path42.resolve(dir, "..", "..", "..")
|
|
24994
25934
|
]) {
|
|
24995
|
-
if (existsSync12(
|
|
25935
|
+
if (existsSync12(path42.join(candidate, "package.json")))
|
|
24996
25936
|
return candidate;
|
|
24997
25937
|
}
|
|
24998
|
-
return
|
|
25938
|
+
return path42.resolve(dir, "..");
|
|
24999
25939
|
};
|
|
25000
25940
|
var skillsRoot = () => {
|
|
25001
25941
|
const root = packageRoot2();
|
|
25002
|
-
const canonical =
|
|
25003
|
-
if (existsSync12(
|
|
25942
|
+
const canonical = path42.resolve(root, "..", "workit-core", "skills");
|
|
25943
|
+
if (existsSync12(path42.join(root, "src")) && existsSync12(canonical))
|
|
25004
25944
|
return canonical;
|
|
25005
|
-
return
|
|
25945
|
+
return path42.join(root, "assets", "skills");
|
|
25006
25946
|
};
|
|
25007
25947
|
|
|
25008
25948
|
// packages/workit-opencode/src/v2/registry.ts
|
|
@@ -25029,11 +25969,11 @@ var registerSkills = async (ctx) => {
|
|
|
25029
25969
|
if (!existsSync13(skillsDir))
|
|
25030
25970
|
return new Set;
|
|
25031
25971
|
const listed = await ctx.skill.list().catch(() => ({ data: [] }));
|
|
25032
|
-
const own = (skill) => typeof skill.path === "string" &&
|
|
25972
|
+
const own = (skill) => typeof skill.path === "string" && path43.resolve(skill.path).startsWith(skillsDir + path43.sep);
|
|
25033
25973
|
const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
|
|
25034
25974
|
const packaged = new Map;
|
|
25035
25975
|
for (const id of WORKIT_METHOD_SKILLS) {
|
|
25036
|
-
const file =
|
|
25976
|
+
const file = path43.join(skillsDir, id, "SKILL.md");
|
|
25037
25977
|
if (!userSkills.has(id) && existsSync13(file))
|
|
25038
25978
|
packaged.set(id, file);
|
|
25039
25979
|
}
|
|
@@ -25081,22 +26021,22 @@ var registerCommands = async (ctx, registeredSkills) => {
|
|
|
25081
26021
|
|
|
25082
26022
|
// packages/workit-opencode/src/stale-sources.ts
|
|
25083
26023
|
import { existsSync as existsSync14, readdirSync as readdirSync6 } from "node:fs";
|
|
25084
|
-
import
|
|
26024
|
+
import path44 from "node:path";
|
|
25085
26025
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
25086
26026
|
var typescriptFiles = (directory) => {
|
|
25087
26027
|
if (!existsSync14(directory))
|
|
25088
26028
|
return [];
|
|
25089
26029
|
return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
25090
|
-
const file =
|
|
26030
|
+
const file = path44.join(directory, entry.name);
|
|
25091
26031
|
return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
|
|
25092
26032
|
});
|
|
25093
26033
|
};
|
|
25094
26034
|
var pluginSourceFiles = [
|
|
25095
26035
|
...new Set([
|
|
25096
26036
|
fileURLToPath3(import.meta.url),
|
|
25097
|
-
...typescriptFiles(
|
|
25098
|
-
|
|
25099
|
-
...typescriptFiles(
|
|
26037
|
+
...typescriptFiles(path44.join(packageRoot2(), "src")),
|
|
26038
|
+
path44.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
|
|
26039
|
+
...typescriptFiles(path44.join(packageRoot2(), "..", "workit-core", "src", "core"))
|
|
25100
26040
|
])
|
|
25101
26041
|
].filter((file) => existsSync14(file)).toSorted();
|
|
25102
26042
|
|
|
@@ -25213,8 +26153,11 @@ var setup = async (ctx) => {
|
|
|
25213
26153
|
const pendingShells = new Map;
|
|
25214
26154
|
await ctx.tool.hook("execute.before", async (event) => {
|
|
25215
26155
|
if (event.tool === "shell") {
|
|
25216
|
-
|
|
25217
|
-
return
|
|
26156
|
+
observeShellBefore(root, event, shellWorkdirs);
|
|
26157
|
+
return noteShellCall(pendingShells, {
|
|
26158
|
+
sessionID: String(event.sessionID),
|
|
26159
|
+
input: event.input
|
|
26160
|
+
});
|
|
25218
26161
|
}
|
|
25219
26162
|
if (event.tool === "skill")
|
|
25220
26163
|
return observeSkillLoad(root, event);
|