@brainervirus/workit-pi 8.16.0 → 8.17.1

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