@brainervirus/workit-codex 8.15.1 → 8.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +197 -3
- package/dist/{workit-hook-v4a1y79w.js → workit-hook-23t8ttjv.js} +1487 -737
- package/dist/workit-hook-rbb0m9zp.js +282 -0
- package/dist/workit-hook.js +5 -3
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +7 -7
- package/skills/workit-fanout/references/brief.md +3 -1
- package/skills/workit-implement/SKILL.md +8 -1
- package/skills/workit-review/SKILL.md +2 -1
- package/skills/workit-verify-app/SKILL.md +2 -0
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CHECKS_FILE,
|
|
4
|
+
repoHeavyCommands,
|
|
5
|
+
loadCheckConfig,
|
|
6
|
+
matchesNamedCheck,
|
|
7
|
+
gateCheckNames,
|
|
8
|
+
checkCommand,
|
|
9
|
+
maybeHeavy
|
|
10
|
+
} from "./workit-hook-rbb0m9zp.js";
|
|
2
11
|
// packages/workit-core/src/hooks/descriptor.ts
|
|
3
12
|
var support = (descriptor, axis) => {
|
|
4
13
|
if (axis.startsWith("event:"))
|
|
@@ -25,8 +34,8 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
25
34
|
});
|
|
26
35
|
// packages/workit-core/src/hooks/context.ts
|
|
27
36
|
import { createHash as createHash11 } from "node:crypto";
|
|
28
|
-
import
|
|
29
|
-
import
|
|
37
|
+
import fs13 from "node:fs";
|
|
38
|
+
import path17 from "node:path";
|
|
30
39
|
|
|
31
40
|
// packages/workit-core/src/core/methods.ts
|
|
32
41
|
var METHODS = {
|
|
@@ -8804,225 +8813,20 @@ function requirementId(input) {
|
|
|
8804
8813
|
|
|
8805
8814
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
8806
8815
|
import { createHash as createHash10 } from "node:crypto";
|
|
8807
|
-
import * as
|
|
8808
|
-
import { spawnSync as
|
|
8809
|
-
import
|
|
8810
|
-
|
|
8811
|
-
// packages/workit-core/src/check-config.ts
|
|
8812
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
8813
|
-
import fs2 from "node:fs";
|
|
8814
|
-
import path2 from "node:path";
|
|
8815
|
-
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
8816
|
-
var CHECKS_FILE = "workit.checks.json";
|
|
8817
|
-
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8818
|
-
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
8819
|
-
function splitCommand(command) {
|
|
8820
|
-
const argv = [];
|
|
8821
|
-
let current = "";
|
|
8822
|
-
let quote = null;
|
|
8823
|
-
let started = false;
|
|
8824
|
-
for (const char of command) {
|
|
8825
|
-
if (quote) {
|
|
8826
|
-
if (char === quote)
|
|
8827
|
-
quote = null;
|
|
8828
|
-
else
|
|
8829
|
-
current += char;
|
|
8830
|
-
continue;
|
|
8831
|
-
}
|
|
8832
|
-
if (char === '"' || char === "'") {
|
|
8833
|
-
quote = char;
|
|
8834
|
-
started = true;
|
|
8835
|
-
continue;
|
|
8836
|
-
}
|
|
8837
|
-
if (/\s/u.test(char)) {
|
|
8838
|
-
if (started)
|
|
8839
|
-
argv.push(current);
|
|
8840
|
-
current = "";
|
|
8841
|
-
started = false;
|
|
8842
|
-
continue;
|
|
8843
|
-
}
|
|
8844
|
-
if (SHELL_META.test(char))
|
|
8845
|
-
return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
|
|
8846
|
-
current += char;
|
|
8847
|
-
started = true;
|
|
8848
|
-
}
|
|
8849
|
-
if (quote)
|
|
8850
|
-
return new Error(`"${command}" has an unterminated quote`);
|
|
8851
|
-
if (started)
|
|
8852
|
-
argv.push(current);
|
|
8853
|
-
return argv.length ? argv : new Error("empty command");
|
|
8854
|
-
}
|
|
8855
|
-
var git = (cwd, args) => {
|
|
8856
|
-
const run = spawnSync2("git", args, {
|
|
8857
|
-
cwd,
|
|
8858
|
-
encoding: "utf8",
|
|
8859
|
-
timeout: 20000,
|
|
8860
|
-
killSignal: "SIGKILL",
|
|
8861
|
-
windowsHide: true
|
|
8862
|
-
});
|
|
8863
|
-
const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
|
|
8864
|
-
return value || null;
|
|
8865
|
-
};
|
|
8866
|
-
function checkRoot(cwd) {
|
|
8867
|
-
return path2.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
8868
|
-
}
|
|
8869
|
-
var packageManager = (root) => {
|
|
8870
|
-
const has = (file) => fs2.existsSync(path2.join(root, file));
|
|
8871
|
-
if (has("bun.lock") || has("bun.lockb"))
|
|
8872
|
-
return "bun";
|
|
8873
|
-
if (has("pnpm-lock.yaml"))
|
|
8874
|
-
return "pnpm";
|
|
8875
|
-
if (has("yarn.lock"))
|
|
8876
|
-
return "yarn";
|
|
8877
|
-
return "npm";
|
|
8878
|
-
};
|
|
8879
|
-
var scriptCheck = (pm, name) => {
|
|
8880
|
-
const argv = [pm, "run", name];
|
|
8881
|
-
const accepts = [argv];
|
|
8882
|
-
if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
|
|
8883
|
-
accepts.push([pm, name]);
|
|
8884
|
-
return { name, argv, accepts };
|
|
8885
|
-
};
|
|
8886
|
-
var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
8887
|
-
function detectedChecks(root) {
|
|
8888
|
-
const read = (file) => {
|
|
8889
|
-
try {
|
|
8890
|
-
return fs2.readFileSync(path2.join(root, file), "utf8");
|
|
8891
|
-
} catch {
|
|
8892
|
-
return null;
|
|
8893
|
-
}
|
|
8894
|
-
};
|
|
8895
|
-
const checks = [];
|
|
8896
|
-
const add = (check) => {
|
|
8897
|
-
if (!checks.some((item) => item.name === check.name))
|
|
8898
|
-
checks.push(check);
|
|
8899
|
-
};
|
|
8900
|
-
const manifestText = read("package.json");
|
|
8901
|
-
if (manifestText !== null) {
|
|
8902
|
-
let manifest = null;
|
|
8903
|
-
try {
|
|
8904
|
-
manifest = JSON.parse(manifestText);
|
|
8905
|
-
} catch {}
|
|
8906
|
-
const scripts = isRecord(manifest) && isRecord(manifest.scripts) ? manifest.scripts : {};
|
|
8907
|
-
const pm = packageManager(root);
|
|
8908
|
-
for (const name of SCRIPT_CHECKS) {
|
|
8909
|
-
const script = scripts[name];
|
|
8910
|
-
if (typeof script === "string" && script.trim() !== "")
|
|
8911
|
-
add(scriptCheck(pm, name));
|
|
8912
|
-
}
|
|
8913
|
-
}
|
|
8914
|
-
const plain = (name, argv) => ({ name, argv, accepts: [argv] });
|
|
8915
|
-
if (read("go.mod") !== null)
|
|
8916
|
-
add(plain("test", ["go", "test", "./..."]));
|
|
8917
|
-
if (read("Cargo.toml") !== null)
|
|
8918
|
-
add(plain("test", ["cargo", "test"]));
|
|
8919
|
-
if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
|
|
8920
|
-
add(plain("test", ["pytest"]));
|
|
8921
|
-
const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
|
|
8922
|
-
if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
|
|
8923
|
-
add(plain("test", ["make", "test"]));
|
|
8924
|
-
return checks;
|
|
8925
|
-
}
|
|
8926
|
-
var GATES = ["testing", "verification"];
|
|
8927
|
-
function loadCheckConfig(cwd) {
|
|
8928
|
-
const root = checkRoot(cwd);
|
|
8929
|
-
const file = path2.join(root, CHECKS_FILE);
|
|
8930
|
-
const none = (error = null) => ({
|
|
8931
|
-
source: error ? CHECKS_FILE : "none",
|
|
8932
|
-
root,
|
|
8933
|
-
checks: [],
|
|
8934
|
-
gates: {},
|
|
8935
|
-
error
|
|
8936
|
-
});
|
|
8937
|
-
let text = null;
|
|
8938
|
-
try {
|
|
8939
|
-
text = fs2.readFileSync(file, "utf8");
|
|
8940
|
-
} catch (error) {
|
|
8941
|
-
if (error.code !== "ENOENT")
|
|
8942
|
-
return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
|
|
8943
|
-
}
|
|
8944
|
-
if (text === null) {
|
|
8945
|
-
const checks = detectedChecks(root);
|
|
8946
|
-
return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
|
|
8947
|
-
}
|
|
8948
|
-
let parsed;
|
|
8949
|
-
try {
|
|
8950
|
-
parsed = JSON.parse(text);
|
|
8951
|
-
} catch {
|
|
8952
|
-
return none(`${CHECKS_FILE} is not valid JSON`);
|
|
8953
|
-
}
|
|
8954
|
-
if (!isRecord(parsed) || !isRecord(parsed.checks))
|
|
8955
|
-
return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
8956
|
-
const checks = [];
|
|
8957
|
-
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
8958
|
-
if (!CHECK_NAME.test(name))
|
|
8959
|
-
return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
8960
|
-
const argv = typeof value === "string" ? splitCommand(value) : Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0) ? value : new Error("expected a command string or a non-empty argv array");
|
|
8961
|
-
if (argv instanceof Error)
|
|
8962
|
-
return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
|
|
8963
|
-
checks.push({ name, argv, accepts: [argv] });
|
|
8964
|
-
}
|
|
8965
|
-
const gates = {};
|
|
8966
|
-
if (parsed.gates !== undefined) {
|
|
8967
|
-
if (!isRecord(parsed.gates))
|
|
8968
|
-
return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
8969
|
-
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
8970
|
-
if (!GATES.includes(gate))
|
|
8971
|
-
return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
8972
|
-
const names = typeof value === "string" ? [value] : value;
|
|
8973
|
-
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
8974
|
-
return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
|
|
8975
|
-
gates[gate] = names;
|
|
8976
|
-
}
|
|
8977
|
-
}
|
|
8978
|
-
return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
|
|
8979
|
-
}
|
|
8980
|
-
var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
8981
|
-
function matchesNamedCheck(config, name, argv, cwd) {
|
|
8982
|
-
if (config.error || name === null || cwd !== ".")
|
|
8983
|
-
return false;
|
|
8984
|
-
const check = config.checks.find((item) => item.name === name);
|
|
8985
|
-
return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
|
|
8986
|
-
}
|
|
8987
|
-
function gateCheckNames(config, dimension) {
|
|
8988
|
-
const explicit = config.gates[dimension];
|
|
8989
|
-
if (explicit)
|
|
8990
|
-
return explicit;
|
|
8991
|
-
const names = config.checks.map((check) => check.name);
|
|
8992
|
-
if (dimension === "testing")
|
|
8993
|
-
return names.includes("test") ? ["test"] : [];
|
|
8994
|
-
return names;
|
|
8995
|
-
}
|
|
8996
|
-
var workitOnPath;
|
|
8997
|
-
function workitAvailable(env = process.env) {
|
|
8998
|
-
if (workitOnPath !== undefined)
|
|
8999
|
-
return workitOnPath;
|
|
9000
|
-
const windows = process.platform === "win32";
|
|
9001
|
-
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
9002
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(path2.delimiter).filter(Boolean);
|
|
9003
|
-
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
9004
|
-
try {
|
|
9005
|
-
return fs2.statSync(path2.join(dir, `workit${ext}`)).isFile();
|
|
9006
|
-
} catch {
|
|
9007
|
-
return false;
|
|
9008
|
-
}
|
|
9009
|
-
}));
|
|
9010
|
-
return workitOnPath;
|
|
9011
|
-
}
|
|
9012
|
-
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
9013
|
-
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
9014
|
-
}
|
|
8816
|
+
import * as fs12 from "node:fs";
|
|
8817
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
8818
|
+
import path15 from "node:path";
|
|
9015
8819
|
|
|
9016
8820
|
// packages/workit-core/src/git/rev.ts
|
|
9017
|
-
import { spawnSync as
|
|
8821
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9018
8822
|
import { createHash as createHash3 } from "node:crypto";
|
|
9019
|
-
import
|
|
8823
|
+
import fs2 from "node:fs";
|
|
9020
8824
|
import os from "node:os";
|
|
9021
|
-
import
|
|
8825
|
+
import path2 from "node:path";
|
|
9022
8826
|
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
9023
8827
|
var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
9024
|
-
var
|
|
9025
|
-
const result =
|
|
8828
|
+
var git = (cwd, args, options = {}) => {
|
|
8829
|
+
const result = spawnSync2("git", args, {
|
|
9026
8830
|
cwd,
|
|
9027
8831
|
encoding: "utf8",
|
|
9028
8832
|
env: options.env ?? process.env,
|
|
@@ -9048,30 +8852,30 @@ var line = (run) => {
|
|
|
9048
8852
|
};
|
|
9049
8853
|
var safeArg = (value) => value.length > 0 && !value.startsWith("-");
|
|
9050
8854
|
function headSha(cwd) {
|
|
9051
|
-
return line(
|
|
8855
|
+
return line(git(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
9052
8856
|
}
|
|
9053
|
-
var emptyTree = (cwd) => line(
|
|
8857
|
+
var emptyTree = (cwd) => line(git(cwd, ["mktree"], { input: "" }));
|
|
9054
8858
|
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
9055
8859
|
var seedIndex = (from, to) => {
|
|
9056
|
-
|
|
9057
|
-
const { atimeNs, mtimeNs } =
|
|
8860
|
+
fs2.copyFileSync(from, to);
|
|
8861
|
+
const { atimeNs, mtimeNs } = fs2.statSync(from, { bigint: true });
|
|
9058
8862
|
const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
|
|
9059
|
-
|
|
8863
|
+
fs2.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
|
|
9060
8864
|
};
|
|
9061
8865
|
function worktreeTree(cwd, options = {}) {
|
|
9062
|
-
const top = line(
|
|
9063
|
-
const indexPath = line(
|
|
8866
|
+
const top = line(git(cwd, ["rev-parse", "--show-toplevel"]));
|
|
8867
|
+
const indexPath = line(git(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
|
|
9064
8868
|
if (!top || !indexPath)
|
|
9065
8869
|
return null;
|
|
9066
8870
|
const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
|
|
9067
8871
|
const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
|
|
9068
|
-
const scratch =
|
|
8872
|
+
const scratch = fs2.mkdtempSync(path2.join(os.tmpdir(), "workit-tree-"));
|
|
9069
8873
|
try {
|
|
9070
|
-
const tempIndex =
|
|
9071
|
-
if (
|
|
8874
|
+
const tempIndex = path2.join(scratch, "index");
|
|
8875
|
+
if (fs2.existsSync(indexPath))
|
|
9072
8876
|
seedIndex(indexPath, tempIndex);
|
|
9073
8877
|
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
9074
|
-
const untracked =
|
|
8878
|
+
const untracked = git(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
|
|
9075
8879
|
env,
|
|
9076
8880
|
timeoutMs
|
|
9077
8881
|
});
|
|
@@ -9080,34 +8884,34 @@ function worktreeTree(cwd, options = {}) {
|
|
|
9080
8884
|
const skipped = [];
|
|
9081
8885
|
for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
|
|
9082
8886
|
try {
|
|
9083
|
-
const stat =
|
|
8887
|
+
const stat = fs2.statSync(path2.join(top, file));
|
|
9084
8888
|
if (stat.isFile() && stat.size > cap)
|
|
9085
8889
|
skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
|
|
9086
8890
|
} catch {}
|
|
9087
8891
|
}
|
|
9088
8892
|
skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
9089
|
-
const added = skipped.length ?
|
|
8893
|
+
const added = skipped.length ? git(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
|
|
9090
8894
|
env,
|
|
9091
8895
|
timeoutMs,
|
|
9092
8896
|
input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
|
|
9093
|
-
}) :
|
|
8897
|
+
}) : git(top, ["add", "-A"], { env, timeoutMs });
|
|
9094
8898
|
if (!added.ok)
|
|
9095
8899
|
return null;
|
|
9096
|
-
const tree = line(
|
|
8900
|
+
const tree = line(git(top, ["write-tree"], { env, timeoutMs }));
|
|
9097
8901
|
if (!tree)
|
|
9098
8902
|
return null;
|
|
9099
|
-
const base = line(
|
|
8903
|
+
const base = line(git(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
|
|
9100
8904
|
const key = skipped.length ? `sha256:${createHash3("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
|
|
9101
8905
|
return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
|
|
9102
8906
|
} finally {
|
|
9103
|
-
|
|
8907
|
+
fs2.rmSync(scratch, { recursive: true, force: true });
|
|
9104
8908
|
}
|
|
9105
8909
|
}
|
|
9106
8910
|
function worktreeSignal(cwd, options = {}) {
|
|
9107
|
-
const top = line(
|
|
8911
|
+
const top = line(git(cwd, ["rev-parse", "--show-toplevel"]));
|
|
9108
8912
|
if (!top)
|
|
9109
8913
|
return null;
|
|
9110
|
-
const status =
|
|
8914
|
+
const status = git(top, [
|
|
9111
8915
|
"status",
|
|
9112
8916
|
"--porcelain=v1",
|
|
9113
8917
|
"-z",
|
|
@@ -9126,10 +8930,10 @@ function worktreeSignal(cwd, options = {}) {
|
|
|
9126
8930
|
if (entry.length < 4)
|
|
9127
8931
|
continue;
|
|
9128
8932
|
const file = entry.slice(3);
|
|
9129
|
-
const absolute =
|
|
8933
|
+
const absolute = path2.join(top, file);
|
|
9130
8934
|
let stat = "-";
|
|
9131
8935
|
try {
|
|
9132
|
-
const info =
|
|
8936
|
+
const info = fs2.lstatSync(absolute, { bigint: true });
|
|
9133
8937
|
stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
|
|
9134
8938
|
if (info.isDirectory())
|
|
9135
8939
|
stat += `:${headSha(absolute) ?? "-"}`;
|
|
@@ -9141,12 +8945,12 @@ function worktreeSignal(cwd, options = {}) {
|
|
|
9141
8945
|
function mergeBase(cwd, a, b) {
|
|
9142
8946
|
if (!safeArg(a) || !safeArg(b))
|
|
9143
8947
|
return null;
|
|
9144
|
-
return line(
|
|
8948
|
+
return line(git(cwd, ["merge-base", a, b]));
|
|
9145
8949
|
}
|
|
9146
8950
|
function patchId(cwd, base, head) {
|
|
9147
8951
|
if (!safeArg(base) || !safeArg(head))
|
|
9148
8952
|
return null;
|
|
9149
|
-
const diff =
|
|
8953
|
+
const diff = git(cwd, [
|
|
9150
8954
|
"diff",
|
|
9151
8955
|
"-U3",
|
|
9152
8956
|
"--diff-algorithm=myers",
|
|
@@ -9163,20 +8967,20 @@ function patchId(cwd, base, head) {
|
|
|
9163
8967
|
]);
|
|
9164
8968
|
if (!diff.ok || !diff.stdout.trim())
|
|
9165
8969
|
return null;
|
|
9166
|
-
const id =
|
|
8970
|
+
const id = git(cwd, ["patch-id", "--stable"], { input: diff.stdout });
|
|
9167
8971
|
return line(id)?.split(/\s+/u)[0] ?? null;
|
|
9168
8972
|
}
|
|
9169
8973
|
function remoteNames(cwd) {
|
|
9170
|
-
return (
|
|
8974
|
+
return (git(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
9171
8975
|
}
|
|
9172
8976
|
function resolveRef(cwd, ref) {
|
|
9173
8977
|
if (!safeArg(ref))
|
|
9174
8978
|
return null;
|
|
9175
|
-
return line(
|
|
8979
|
+
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
9176
8980
|
}
|
|
9177
|
-
var config2 = (cwd, key) => line(
|
|
8981
|
+
var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
|
|
9178
8982
|
function currentBranch(cwd) {
|
|
9179
|
-
return line(
|
|
8983
|
+
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
9180
8984
|
}
|
|
9181
8985
|
function pushRemoteName(cwd, branch) {
|
|
9182
8986
|
const name = branch ?? currentBranch(cwd);
|
|
@@ -9196,8 +9000,8 @@ function pushRemoteName(cwd, branch) {
|
|
|
9196
9000
|
|
|
9197
9001
|
// packages/workit-core/src/core/task-store.ts
|
|
9198
9002
|
import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
|
|
9199
|
-
import * as
|
|
9200
|
-
import
|
|
9003
|
+
import * as fs9 from "node:fs";
|
|
9004
|
+
import path9 from "node:path";
|
|
9201
9005
|
|
|
9202
9006
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
9203
9007
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -9248,10 +9052,10 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
9248
9052
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
9249
9053
|
import { randomBytes } from "node:crypto";
|
|
9250
9054
|
import fsSync from "node:fs";
|
|
9251
|
-
import
|
|
9055
|
+
import path3 from "node:path";
|
|
9252
9056
|
|
|
9253
9057
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
9254
|
-
import
|
|
9058
|
+
import fs3 from "node:fs";
|
|
9255
9059
|
|
|
9256
9060
|
// node_modules/@openclaw/fs-safe/dist/byte-budget.js
|
|
9257
9061
|
function normalizeMaxBytes(value, options = {}) {
|
|
@@ -9288,7 +9092,7 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
|
9288
9092
|
let total = 0;
|
|
9289
9093
|
while (true) {
|
|
9290
9094
|
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
9291
|
-
const bytesRead =
|
|
9095
|
+
const bytesRead = fs3.readSync(fd, scratch, 0, length, null);
|
|
9292
9096
|
if (bytesRead === 0) {
|
|
9293
9097
|
return Buffer.concat(chunks, total);
|
|
9294
9098
|
}
|
|
@@ -9297,9 +9101,9 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
|
9297
9101
|
}
|
|
9298
9102
|
|
|
9299
9103
|
// node_modules/@openclaw/fs-safe/dist/read-open-flags.js
|
|
9300
|
-
import
|
|
9104
|
+
import fs4 from "node:fs";
|
|
9301
9105
|
function resolveReadOpenFlags(options) {
|
|
9302
|
-
const constants = options?.constants ??
|
|
9106
|
+
const constants = options?.constants ?? fs4.constants;
|
|
9303
9107
|
const noFollow = process.platform !== "win32" && options?.followSymlinks !== true && typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
9304
9108
|
const nonBlocking = process.platform !== "win32" && typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0;
|
|
9305
9109
|
return constants.O_RDONLY | noFollow | nonBlocking;
|
|
@@ -9338,13 +9142,13 @@ ${ownershipToken}
|
|
|
9338
9142
|
return { raw, ownershipToken };
|
|
9339
9143
|
}
|
|
9340
9144
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
9341
|
-
const resolved =
|
|
9342
|
-
const lexicalRelative =
|
|
9343
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
9344
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
9145
|
+
const resolved = path3.resolve(lockPath);
|
|
9146
|
+
const lexicalRelative = path3.relative(lockRoot.rootDir, resolved);
|
|
9147
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path3.sep}`) && !path3.isAbsolute(lexicalRelative) ? lexicalRelative : path3.relative(lockRoot.rootReal, resolved);
|
|
9148
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative)) {
|
|
9345
9149
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
9346
9150
|
}
|
|
9347
|
-
return relative.split(
|
|
9151
|
+
return relative.split(path3.sep).join(path3.posix.sep);
|
|
9348
9152
|
}
|
|
9349
9153
|
function parseSidecarLockPayload(raw, parser) {
|
|
9350
9154
|
if (parser) {
|
|
@@ -9490,8 +9294,8 @@ function getFsSafeLockConfig() {
|
|
|
9490
9294
|
}
|
|
9491
9295
|
|
|
9492
9296
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
9493
|
-
import
|
|
9494
|
-
import
|
|
9297
|
+
import fs5 from "node:fs";
|
|
9298
|
+
import path4 from "node:path";
|
|
9495
9299
|
|
|
9496
9300
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
9497
9301
|
function sleepSync(ms) {
|
|
@@ -9521,7 +9325,7 @@ function releaseAllSyncHeldLocks() {
|
|
|
9521
9325
|
}
|
|
9522
9326
|
try {
|
|
9523
9327
|
if (held.fd !== undefined)
|
|
9524
|
-
|
|
9328
|
+
fs5.closeSync(held.fd);
|
|
9525
9329
|
} catch {}
|
|
9526
9330
|
try {
|
|
9527
9331
|
removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
|
|
@@ -9556,7 +9360,7 @@ function releaseSyncHeldLock(held) {
|
|
|
9556
9360
|
if (held.fd !== undefined) {
|
|
9557
9361
|
const fd = held.fd;
|
|
9558
9362
|
held.fd = undefined;
|
|
9559
|
-
|
|
9363
|
+
fs5.closeSync(fd);
|
|
9560
9364
|
}
|
|
9561
9365
|
removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
|
|
9562
9366
|
heldLocks.delete(held.normalizedTargetPath);
|
|
@@ -9579,29 +9383,29 @@ function createSyncHeldLockHandle(held) {
|
|
|
9579
9383
|
};
|
|
9580
9384
|
}
|
|
9581
9385
|
function canonicalLockParentSync(parent) {
|
|
9582
|
-
return process.platform === "win32" ?
|
|
9386
|
+
return process.platform === "win32" ? fs5.realpathSync.native(parent) : fs5.realpathSync(parent);
|
|
9583
9387
|
}
|
|
9584
9388
|
function normalizeTargetPath(targetPath) {
|
|
9585
|
-
const resolved =
|
|
9586
|
-
|
|
9389
|
+
const resolved = path4.resolve(targetPath);
|
|
9390
|
+
fs5.mkdirSync(path4.dirname(resolved), { recursive: true });
|
|
9587
9391
|
try {
|
|
9588
|
-
return
|
|
9392
|
+
return path4.join(canonicalLockParentSync(path4.dirname(resolved)), path4.basename(resolved));
|
|
9589
9393
|
} catch {
|
|
9590
9394
|
return resolved;
|
|
9591
9395
|
}
|
|
9592
9396
|
}
|
|
9593
9397
|
function boundedLockPath(lockPath, lockRoot) {
|
|
9594
|
-
const resolved =
|
|
9398
|
+
const resolved = path4.resolve(lockPath);
|
|
9595
9399
|
if (!lockRoot)
|
|
9596
9400
|
return resolved;
|
|
9597
9401
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
9598
|
-
const parent =
|
|
9402
|
+
const parent = path4.dirname(resolved);
|
|
9599
9403
|
const parentReal = canonicalLockParentSync(parent);
|
|
9600
|
-
const parentRelative =
|
|
9601
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
9404
|
+
const parentRelative = path4.relative(lockRoot.rootReal, parentReal);
|
|
9405
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path4.sep}`) || path4.isAbsolute(parentRelative)) {
|
|
9602
9406
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
9603
9407
|
}
|
|
9604
|
-
return
|
|
9408
|
+
return path4.join(parentReal, path4.basename(resolved));
|
|
9605
9409
|
}
|
|
9606
9410
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
9607
9411
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -9611,7 +9415,7 @@ function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
|
9611
9415
|
}
|
|
9612
9416
|
function reclaimGuardExists(reclaimGuardPath) {
|
|
9613
9417
|
try {
|
|
9614
|
-
|
|
9418
|
+
fs5.lstatSync(reclaimGuardPath);
|
|
9615
9419
|
return true;
|
|
9616
9420
|
} catch (error) {
|
|
9617
9421
|
if (error.code === "ENOENT")
|
|
@@ -9675,19 +9479,19 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9675
9479
|
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
9676
9480
|
let lockFileCreateDenied = false;
|
|
9677
9481
|
try {
|
|
9678
|
-
const noFollow = process.platform !== "win32" && typeof
|
|
9482
|
+
const noFollow = process.platform !== "win32" && typeof fs5.constants.O_NOFOLLOW === "number" ? fs5.constants.O_NOFOLLOW : 0;
|
|
9679
9483
|
try {
|
|
9680
|
-
fd =
|
|
9484
|
+
fd = fs5.openSync(lockPath, fs5.constants.O_WRONLY | fs5.constants.O_CREAT | fs5.constants.O_EXCL | noFollow, 384);
|
|
9681
9485
|
} catch (error) {
|
|
9682
9486
|
lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
|
|
9683
9487
|
throw error;
|
|
9684
9488
|
}
|
|
9685
|
-
|
|
9686
|
-
|
|
9489
|
+
fs5.writeFileSync(fd, raw, "utf8");
|
|
9490
|
+
fs5.fsyncSync(fd);
|
|
9687
9491
|
const snapshot = {
|
|
9688
9492
|
raw,
|
|
9689
9493
|
payload,
|
|
9690
|
-
stat:
|
|
9494
|
+
stat: fs5.fstatSync(fd),
|
|
9691
9495
|
ownershipToken
|
|
9692
9496
|
};
|
|
9693
9497
|
const createdHeld = {
|
|
@@ -9722,8 +9526,8 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9722
9526
|
return returnedHandle;
|
|
9723
9527
|
} catch (error) {
|
|
9724
9528
|
if (fd !== undefined) {
|
|
9725
|
-
const failed = { payload: null, stat:
|
|
9726
|
-
|
|
9529
|
+
const failed = { payload: null, stat: fs5.fstatSync(fd) };
|
|
9530
|
+
fs5.closeSync(fd);
|
|
9727
9531
|
fd = undefined;
|
|
9728
9532
|
removeSidecarLockIfUnchangedSync(lockPath, failed);
|
|
9729
9533
|
}
|
|
@@ -9772,7 +9576,7 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9772
9576
|
})) {
|
|
9773
9577
|
let ownsReclaimGuard = false;
|
|
9774
9578
|
try {
|
|
9775
|
-
|
|
9579
|
+
fs5.mkdirSync(reclaimGuardPath);
|
|
9776
9580
|
ownsReclaimGuard = true;
|
|
9777
9581
|
if (removeSidecarLockIfUnchangedSync(lockPath, snapshot))
|
|
9778
9582
|
continue;
|
|
@@ -9785,7 +9589,7 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9785
9589
|
} finally {
|
|
9786
9590
|
if (ownsReclaimGuard) {
|
|
9787
9591
|
try {
|
|
9788
|
-
|
|
9592
|
+
fs5.rmdirSync(reclaimGuardPath);
|
|
9789
9593
|
} catch {}
|
|
9790
9594
|
}
|
|
9791
9595
|
}
|
|
@@ -9802,12 +9606,12 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9802
9606
|
}
|
|
9803
9607
|
// packages/workit-core/src/host-session.ts
|
|
9804
9608
|
import { createHash as createHash4, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
9805
|
-
import
|
|
9806
|
-
import
|
|
9609
|
+
import fs6 from "node:fs";
|
|
9610
|
+
import path6 from "node:path";
|
|
9807
9611
|
|
|
9808
9612
|
// packages/workit-core/src/core/logger.ts
|
|
9809
9613
|
import os2 from "node:os";
|
|
9810
|
-
import
|
|
9614
|
+
import path5 from "node:path";
|
|
9811
9615
|
var SENSITIVE_WORDS = new Set([
|
|
9812
9616
|
"token",
|
|
9813
9617
|
"secret",
|
|
@@ -9852,14 +9656,14 @@ var resolveStateDir = () => {
|
|
|
9852
9656
|
return override;
|
|
9853
9657
|
const home = os2.homedir();
|
|
9854
9658
|
if (process.env.XDG_STATE_HOME)
|
|
9855
|
-
return
|
|
9659
|
+
return path5.join(process.env.XDG_STATE_HOME, "workit");
|
|
9856
9660
|
switch (os2.platform()) {
|
|
9857
9661
|
case "darwin":
|
|
9858
|
-
return
|
|
9662
|
+
return path5.join(home, "Library", "Application Support", "workit");
|
|
9859
9663
|
case "win32":
|
|
9860
|
-
return
|
|
9664
|
+
return path5.join(process.env.LOCALAPPDATA ?? path5.join(home, "AppData", "Local"), "workit");
|
|
9861
9665
|
default:
|
|
9862
|
-
return
|
|
9666
|
+
return path5.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
9863
9667
|
}
|
|
9864
9668
|
};
|
|
9865
9669
|
var homePattern = new RegExp(`${escapeRegExp(os2.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
@@ -9895,8 +9699,8 @@ function hostSessionFromEnv(env) {
|
|
|
9895
9699
|
var ISSUED_PER_LEAD = 64;
|
|
9896
9700
|
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
|
|
9897
9701
|
var hash = (value) => createHash4("sha256").update(value).digest("hex").slice(0, 32);
|
|
9898
|
-
var userIssuedRoot = () =>
|
|
9899
|
-
var userTokenRoot = () =>
|
|
9702
|
+
var userIssuedRoot = () => path6.join(resolveStateDir(), "issued");
|
|
9703
|
+
var userTokenRoot = () => path6.join(resolveStateDir(), "issued-tokens");
|
|
9900
9704
|
function migrateRepoIssued(cwd) {
|
|
9901
9705
|
if (cwd === undefined)
|
|
9902
9706
|
return;
|
|
@@ -9905,27 +9709,27 @@ function migrateRepoIssued(cwd) {
|
|
|
9905
9709
|
const location = resolveStore(cwd);
|
|
9906
9710
|
if (location instanceof Error)
|
|
9907
9711
|
return;
|
|
9908
|
-
old =
|
|
9909
|
-
if (!
|
|
9712
|
+
old = path6.join(location.dir, "hooks", "issued");
|
|
9713
|
+
if (!fs6.existsSync(old))
|
|
9910
9714
|
return;
|
|
9911
9715
|
} catch {
|
|
9912
9716
|
return;
|
|
9913
9717
|
}
|
|
9914
9718
|
for (const lead of safeList(old)) {
|
|
9915
9719
|
try {
|
|
9916
|
-
const from =
|
|
9917
|
-
if (!
|
|
9720
|
+
const from = path6.join(old, lead);
|
|
9721
|
+
if (!fs6.statSync(from).isDirectory())
|
|
9918
9722
|
continue;
|
|
9919
|
-
const to =
|
|
9920
|
-
|
|
9921
|
-
for (const id of
|
|
9723
|
+
const to = path6.join(userIssuedRoot(), lead);
|
|
9724
|
+
fs6.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9725
|
+
for (const id of fs6.readdirSync(from)) {
|
|
9922
9726
|
try {
|
|
9923
|
-
|
|
9727
|
+
fs6.copyFileSync(path6.join(from, id), path6.join(to, id), fs6.constants.COPYFILE_EXCL);
|
|
9924
9728
|
} catch (error) {
|
|
9925
9729
|
if (error.code !== "EEXIST")
|
|
9926
9730
|
throw error;
|
|
9927
9731
|
}
|
|
9928
|
-
|
|
9732
|
+
fs6.rmSync(path6.join(from, id), { force: true });
|
|
9929
9733
|
}
|
|
9930
9734
|
removeIfEmpty(from);
|
|
9931
9735
|
} catch {}
|
|
@@ -9934,14 +9738,14 @@ function migrateRepoIssued(cwd) {
|
|
|
9934
9738
|
}
|
|
9935
9739
|
var safeList = (dir) => {
|
|
9936
9740
|
try {
|
|
9937
|
-
return
|
|
9741
|
+
return fs6.readdirSync(dir);
|
|
9938
9742
|
} catch {
|
|
9939
9743
|
return [];
|
|
9940
9744
|
}
|
|
9941
9745
|
};
|
|
9942
9746
|
var removeIfEmpty = (dir) => {
|
|
9943
9747
|
try {
|
|
9944
|
-
|
|
9748
|
+
fs6.rmdirSync(dir);
|
|
9945
9749
|
} catch {}
|
|
9946
9750
|
};
|
|
9947
9751
|
var tokenHash = (token) => createHash4("sha256").update(token).digest("hex");
|
|
@@ -9950,13 +9754,13 @@ function recordIssuedSession(cwd, lead, issued) {
|
|
|
9950
9754
|
try {
|
|
9951
9755
|
migrateRepoIssued(cwd);
|
|
9952
9756
|
const own = hash(issued);
|
|
9953
|
-
const tokens =
|
|
9954
|
-
|
|
9955
|
-
|
|
9757
|
+
const tokens = path6.join(userTokenRoot(), hash(lead));
|
|
9758
|
+
fs6.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
9759
|
+
fs6.writeFileSync(path6.join(tokens, own), `${tokenHash(token)}
|
|
9956
9760
|
`, { mode: 384, flag: "wx" });
|
|
9957
|
-
const dir =
|
|
9958
|
-
|
|
9959
|
-
|
|
9761
|
+
const dir = path6.join(userIssuedRoot(), hash(lead));
|
|
9762
|
+
fs6.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9763
|
+
fs6.writeFileSync(path6.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
9960
9764
|
`, { mode: 384 });
|
|
9961
9765
|
pruneIssued(dir, own, tokens);
|
|
9962
9766
|
return token;
|
|
@@ -9966,13 +9770,13 @@ function recordIssuedSession(cwd, lead, issued) {
|
|
|
9966
9770
|
}
|
|
9967
9771
|
function pruneIssued(dir, own, tokens) {
|
|
9968
9772
|
try {
|
|
9969
|
-
const entries =
|
|
9773
|
+
const entries = fs6.readdirSync(dir);
|
|
9970
9774
|
if (entries.length <= ISSUED_PER_LEAD)
|
|
9971
9775
|
return;
|
|
9972
|
-
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at:
|
|
9776
|
+
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs6.statSync(path6.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
9973
9777
|
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
|
|
9974
|
-
|
|
9975
|
-
|
|
9778
|
+
fs6.rmSync(path6.join(dir, name), { force: true });
|
|
9779
|
+
fs6.rmSync(path6.join(tokens, name), { force: true });
|
|
9976
9780
|
}
|
|
9977
9781
|
} catch {}
|
|
9978
9782
|
}
|
|
@@ -9982,7 +9786,7 @@ var issuedRecord = (cwd, claimed) => {
|
|
|
9982
9786
|
return null;
|
|
9983
9787
|
migrateRepoIssued(cwd);
|
|
9984
9788
|
try {
|
|
9985
|
-
return
|
|
9789
|
+
return fs6.readFileSync(path6.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
9986
9790
|
} catch {
|
|
9987
9791
|
return null;
|
|
9988
9792
|
}
|
|
@@ -9992,9 +9796,9 @@ var issuedToken = (claimed) => {
|
|
|
9992
9796
|
const cut = claimed.lastIndexOf(":");
|
|
9993
9797
|
if (cut <= 0)
|
|
9994
9798
|
return { stored: null, leadBound: false };
|
|
9995
|
-
const dir =
|
|
9799
|
+
const dir = path6.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
9996
9800
|
try {
|
|
9997
|
-
const text =
|
|
9801
|
+
const text = fs6.readFileSync(path6.join(dir, hash(claimed)), "utf8");
|
|
9998
9802
|
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
9999
9803
|
} catch {
|
|
10000
9804
|
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
@@ -10013,21 +9817,21 @@ var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
|
10013
9817
|
|
|
10014
9818
|
// packages/workit-core/src/core/package-root.ts
|
|
10015
9819
|
import { existsSync } from "node:fs";
|
|
10016
|
-
import
|
|
9820
|
+
import path7 from "node:path";
|
|
10017
9821
|
import { fileURLToPath } from "node:url";
|
|
10018
9822
|
var packageRoot = () => {
|
|
10019
|
-
let dir =
|
|
9823
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
10020
9824
|
while (true) {
|
|
10021
|
-
const parent =
|
|
10022
|
-
if (existsSync(
|
|
9825
|
+
const parent = path7.dirname(dir);
|
|
9826
|
+
if (existsSync(path7.join(dir, "package.json")) || parent === dir)
|
|
10023
9827
|
return dir;
|
|
10024
9828
|
dir = parent;
|
|
10025
9829
|
}
|
|
10026
9830
|
};
|
|
10027
9831
|
|
|
10028
9832
|
// packages/workit-core/src/core/store-lock.ts
|
|
10029
|
-
import { spawnSync as
|
|
10030
|
-
import
|
|
9833
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
9834
|
+
import fs7 from "node:fs";
|
|
10031
9835
|
import { hostname } from "node:os";
|
|
10032
9836
|
var metadataLockSchema = object({
|
|
10033
9837
|
pid: number2().int().nonnegative().safe(),
|
|
@@ -10071,14 +9875,14 @@ var parseProcStatStart = (stat) => {
|
|
|
10071
9875
|
var processStartOf = (pid) => {
|
|
10072
9876
|
if (process.platform === "linux") {
|
|
10073
9877
|
try {
|
|
10074
|
-
return parseProcStatStart(
|
|
9878
|
+
return parseProcStatStart(fs7.readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
10075
9879
|
} catch {
|
|
10076
9880
|
return null;
|
|
10077
9881
|
}
|
|
10078
9882
|
}
|
|
10079
9883
|
if (process.platform === "darwin" || process.platform === "freebsd") {
|
|
10080
9884
|
try {
|
|
10081
|
-
const run =
|
|
9885
|
+
const run = spawnSync3("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
10082
9886
|
encoding: "utf8",
|
|
10083
9887
|
timeout: 1000
|
|
10084
9888
|
});
|
|
@@ -10092,7 +9896,7 @@ var processStartOf = (pid) => {
|
|
|
10092
9896
|
};
|
|
10093
9897
|
var readTrimmed = (file) => {
|
|
10094
9898
|
try {
|
|
10095
|
-
return
|
|
9899
|
+
return fs7.readFileSync(file, "utf8").trim() || null;
|
|
10096
9900
|
} catch {
|
|
10097
9901
|
return null;
|
|
10098
9902
|
}
|
|
@@ -10103,7 +9907,7 @@ var localLockHost = () => {
|
|
|
10103
9907
|
return cachedLockHost;
|
|
10104
9908
|
let pidns = null;
|
|
10105
9909
|
try {
|
|
10106
|
-
pidns = /\[(\d+)\]/.exec(
|
|
9910
|
+
pidns = /\[(\d+)\]/.exec(fs7.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
|
|
10107
9911
|
} catch {}
|
|
10108
9912
|
const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
|
|
10109
9913
|
cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
|
|
@@ -10149,7 +9953,7 @@ var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options =
|
|
|
10149
9953
|
};
|
|
10150
9954
|
var ageOf = (file, nowMs) => {
|
|
10151
9955
|
try {
|
|
10152
|
-
return nowMs -
|
|
9956
|
+
return nowMs - fs7.lstatSync(file).mtimeMs;
|
|
10153
9957
|
} catch {
|
|
10154
9958
|
return null;
|
|
10155
9959
|
}
|
|
@@ -10160,7 +9964,7 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
10160
9964
|
if (age === null || age <= RECLAIM_GUARD_TTL_MS)
|
|
10161
9965
|
return false;
|
|
10162
9966
|
try {
|
|
10163
|
-
|
|
9967
|
+
fs7.rmdirSync(guard);
|
|
10164
9968
|
return true;
|
|
10165
9969
|
} catch {
|
|
10166
9970
|
return false;
|
|
@@ -10169,8 +9973,8 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
10169
9973
|
|
|
10170
9974
|
// packages/workit-core/src/store/event-log.ts
|
|
10171
9975
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10172
|
-
import
|
|
10173
|
-
import
|
|
9976
|
+
import fs8 from "node:fs";
|
|
9977
|
+
import path8 from "node:path";
|
|
10174
9978
|
var EVENT_VERSION = 1;
|
|
10175
9979
|
|
|
10176
9980
|
class LogDamage extends Error {
|
|
@@ -10199,7 +10003,7 @@ var parseEvent = (line, file, offset) => {
|
|
|
10199
10003
|
function readLog(file, from = 0, expectSeq = null) {
|
|
10200
10004
|
let fd;
|
|
10201
10005
|
try {
|
|
10202
|
-
fd =
|
|
10006
|
+
fd = fs8.openSync(file, "r");
|
|
10203
10007
|
} catch (error) {
|
|
10204
10008
|
if (error.code === "ENOENT")
|
|
10205
10009
|
return null;
|
|
@@ -10208,13 +10012,13 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10208
10012
|
let bytes;
|
|
10209
10013
|
let size;
|
|
10210
10014
|
try {
|
|
10211
|
-
size =
|
|
10015
|
+
size = fs8.fstatSync(fd).size;
|
|
10212
10016
|
if (from > size)
|
|
10213
10017
|
throw new LogDamage(`${file}: shorter than its snapshot`);
|
|
10214
10018
|
bytes = Buffer.alloc(size - from);
|
|
10215
10019
|
let read = 0;
|
|
10216
10020
|
while (read < bytes.length) {
|
|
10217
|
-
const got =
|
|
10021
|
+
const got = fs8.readSync(fd, bytes, read, bytes.length - read, from + read);
|
|
10218
10022
|
if (got === 0)
|
|
10219
10023
|
break;
|
|
10220
10024
|
read += got;
|
|
@@ -10222,7 +10026,7 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10222
10026
|
bytes = bytes.subarray(0, read);
|
|
10223
10027
|
size = from + read;
|
|
10224
10028
|
} finally {
|
|
10225
|
-
|
|
10029
|
+
fs8.closeSync(fd);
|
|
10226
10030
|
}
|
|
10227
10031
|
const events = [];
|
|
10228
10032
|
let start = 0;
|
|
@@ -10247,11 +10051,11 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10247
10051
|
}
|
|
10248
10052
|
var fsyncDirectory = (dir) => {
|
|
10249
10053
|
try {
|
|
10250
|
-
const fd =
|
|
10054
|
+
const fd = fs8.openSync(dir, "r");
|
|
10251
10055
|
try {
|
|
10252
|
-
|
|
10056
|
+
fs8.fsyncSync(fd);
|
|
10253
10057
|
} finally {
|
|
10254
|
-
|
|
10058
|
+
fs8.closeSync(fd);
|
|
10255
10059
|
}
|
|
10256
10060
|
} catch (error) {
|
|
10257
10061
|
if (process.platform !== "win32")
|
|
@@ -10261,46 +10065,46 @@ var fsyncDirectory = (dir) => {
|
|
|
10261
10065
|
function appendEvent(file, event, validEnd) {
|
|
10262
10066
|
const line = Buffer.from(`${JSON.stringify(event)}
|
|
10263
10067
|
`, "utf8");
|
|
10264
|
-
const created = !
|
|
10265
|
-
if (!created &&
|
|
10266
|
-
const cut =
|
|
10068
|
+
const created = !fs8.existsSync(file);
|
|
10069
|
+
if (!created && fs8.statSync(file).size !== validEnd) {
|
|
10070
|
+
const cut = fs8.openSync(file, "r+");
|
|
10267
10071
|
try {
|
|
10268
|
-
|
|
10269
|
-
|
|
10072
|
+
fs8.ftruncateSync(cut, validEnd);
|
|
10073
|
+
fs8.fsyncSync(cut);
|
|
10270
10074
|
} finally {
|
|
10271
|
-
|
|
10075
|
+
fs8.closeSync(cut);
|
|
10272
10076
|
}
|
|
10273
10077
|
}
|
|
10274
|
-
const fd =
|
|
10078
|
+
const fd = fs8.openSync(file, "a", 384);
|
|
10275
10079
|
try {
|
|
10276
10080
|
let written = 0;
|
|
10277
10081
|
while (written < line.length)
|
|
10278
|
-
written +=
|
|
10279
|
-
|
|
10082
|
+
written += fs8.writeSync(fd, line, written, line.length - written);
|
|
10083
|
+
fs8.fsyncSync(fd);
|
|
10280
10084
|
} finally {
|
|
10281
|
-
|
|
10085
|
+
fs8.closeSync(fd);
|
|
10282
10086
|
}
|
|
10283
10087
|
if (created)
|
|
10284
|
-
fsyncDirectory(
|
|
10088
|
+
fsyncDirectory(path8.dirname(file));
|
|
10285
10089
|
return validEnd;
|
|
10286
10090
|
}
|
|
10287
10091
|
function replaceFile(file, bytes, durable) {
|
|
10288
10092
|
const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
|
|
10289
10093
|
try {
|
|
10290
|
-
const fd =
|
|
10094
|
+
const fd = fs8.openSync(temporary, "wx", 384);
|
|
10291
10095
|
try {
|
|
10292
|
-
|
|
10096
|
+
fs8.writeSync(fd, bytes);
|
|
10293
10097
|
if (durable)
|
|
10294
|
-
|
|
10098
|
+
fs8.fsyncSync(fd);
|
|
10295
10099
|
} finally {
|
|
10296
|
-
|
|
10100
|
+
fs8.closeSync(fd);
|
|
10297
10101
|
}
|
|
10298
10102
|
renameWithRetry(temporary, file);
|
|
10299
10103
|
if (durable)
|
|
10300
|
-
fsyncDirectory(
|
|
10104
|
+
fsyncDirectory(path8.dirname(file));
|
|
10301
10105
|
} catch (error) {
|
|
10302
10106
|
try {
|
|
10303
|
-
|
|
10107
|
+
fs8.unlinkSync(temporary);
|
|
10304
10108
|
} catch {}
|
|
10305
10109
|
throw error;
|
|
10306
10110
|
}
|
|
@@ -10325,7 +10129,7 @@ var retryTransient = (run) => {
|
|
|
10325
10129
|
}
|
|
10326
10130
|
}
|
|
10327
10131
|
};
|
|
10328
|
-
var renameWithRetry = (from, to) => retryTransient(() =>
|
|
10132
|
+
var renameWithRetry = (from, to) => retryTransient(() => fs8.renameSync(from, to));
|
|
10329
10133
|
|
|
10330
10134
|
// packages/workit-core/src/store/patch.ts
|
|
10331
10135
|
var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -10622,7 +10426,7 @@ var indexEntry = (task, key, legacy, file) => {
|
|
|
10622
10426
|
};
|
|
10623
10427
|
var fileSignature = (file) => {
|
|
10624
10428
|
try {
|
|
10625
|
-
const stat =
|
|
10429
|
+
const stat = fs9.statSync(file, { bigint: true });
|
|
10626
10430
|
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
10627
10431
|
} catch {
|
|
10628
10432
|
return null;
|
|
@@ -10640,7 +10444,7 @@ var cachedRuntimeVersion = null;
|
|
|
10640
10444
|
var runtimeVersion = () => {
|
|
10641
10445
|
if (cachedRuntimeVersion === null) {
|
|
10642
10446
|
try {
|
|
10643
|
-
const pkg = JSON.parse(
|
|
10447
|
+
const pkg = JSON.parse(fs9.readFileSync(path9.join(packageRoot(), "package.json"), "utf8"));
|
|
10644
10448
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
10645
10449
|
} catch {
|
|
10646
10450
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -10666,14 +10470,14 @@ var isNewerVersion = (candidate, current) => {
|
|
|
10666
10470
|
var validId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
|
|
10667
10471
|
var readText = (file) => {
|
|
10668
10472
|
try {
|
|
10669
|
-
return
|
|
10473
|
+
return fs9.readFileSync(file, "utf8");
|
|
10670
10474
|
} catch {
|
|
10671
10475
|
return "";
|
|
10672
10476
|
}
|
|
10673
10477
|
};
|
|
10674
10478
|
var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
|
|
10675
10479
|
var branchStart = (location, branch, aliases) => {
|
|
10676
|
-
const first = readText(
|
|
10480
|
+
const first = readText(path9.join(path9.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
10677
10481
|
`, 1)[0];
|
|
10678
10482
|
const created = REFLOG_LINE.exec(first);
|
|
10679
10483
|
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
@@ -10683,7 +10487,7 @@ var branchStart = (location, branch, aliases) => {
|
|
|
10683
10487
|
return start;
|
|
10684
10488
|
if (!location.git)
|
|
10685
10489
|
return null;
|
|
10686
|
-
for (const line of readText(
|
|
10490
|
+
for (const line of readText(path9.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
10687
10491
|
`)) {
|
|
10688
10492
|
const entry = REFLOG_LINE.exec(line);
|
|
10689
10493
|
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
@@ -10693,23 +10497,23 @@ var branchStart = (location, branch, aliases) => {
|
|
|
10693
10497
|
}
|
|
10694
10498
|
return null;
|
|
10695
10499
|
};
|
|
10696
|
-
var refExists = (common, ref) =>
|
|
10500
|
+
var refExists = (common, ref) => fs9.existsSync(path9.join(common, ref)) || readText(path9.join(common, "packed-refs")).includes(` ${ref}
|
|
10697
10501
|
`);
|
|
10698
10502
|
var origins = new Map;
|
|
10699
10503
|
var sameDirectoryIdentity = (left, right) => {
|
|
10700
|
-
if (!
|
|
10504
|
+
if (!path9.isAbsolute(left) || !path9.isAbsolute(right))
|
|
10701
10505
|
return false;
|
|
10702
|
-
const normalizedLeft =
|
|
10703
|
-
const normalizedRight =
|
|
10506
|
+
const normalizedLeft = path9.resolve(left);
|
|
10507
|
+
const normalizedRight = path9.resolve(right);
|
|
10704
10508
|
try {
|
|
10705
|
-
const a =
|
|
10706
|
-
const b =
|
|
10509
|
+
const a = fs9.statSync(normalizedLeft, { bigint: true });
|
|
10510
|
+
const b = fs9.statSync(normalizedRight, { bigint: true });
|
|
10707
10511
|
if (!a.isDirectory() || !b.isDirectory())
|
|
10708
10512
|
return false;
|
|
10709
10513
|
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
10710
10514
|
return true;
|
|
10711
|
-
const canonicalLeft =
|
|
10712
|
-
const canonicalRight =
|
|
10515
|
+
const canonicalLeft = fs9.realpathSync(normalizedLeft);
|
|
10516
|
+
const canonicalRight = fs9.realpathSync(normalizedRight);
|
|
10713
10517
|
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
10714
10518
|
return true;
|
|
10715
10519
|
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
@@ -10746,8 +10550,8 @@ class TaskStore {
|
|
|
10746
10550
|
migrationChecked = false;
|
|
10747
10551
|
compactDue = new Set;
|
|
10748
10552
|
constructor(root, options = {}) {
|
|
10749
|
-
const resolved =
|
|
10750
|
-
this.origin =
|
|
10553
|
+
const resolved = path9.resolve(root);
|
|
10554
|
+
this.origin = fs9.existsSync(resolved) ? fs9.realpathSync(resolved) : resolved;
|
|
10751
10555
|
this.located = resolveStore(this.origin);
|
|
10752
10556
|
this.root = checkoutRootOf(this.origin, this.located);
|
|
10753
10557
|
this.lockTimeoutMs = options.lockTimeoutMs ?? defaultLockTimeout();
|
|
@@ -10755,7 +10559,7 @@ class TaskStore {
|
|
|
10755
10559
|
this.migrateOnRead = options.migrateOnRead;
|
|
10756
10560
|
}
|
|
10757
10561
|
location() {
|
|
10758
|
-
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared &&
|
|
10562
|
+
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs9.existsSync(path9.join(this.root, ".git"))) {
|
|
10759
10563
|
this.located = undefined;
|
|
10760
10564
|
this.migrationChecked = false;
|
|
10761
10565
|
}
|
|
@@ -10819,7 +10623,7 @@ class TaskStore {
|
|
|
10819
10623
|
const tasksDir = this.paths().tasks;
|
|
10820
10624
|
let names;
|
|
10821
10625
|
try {
|
|
10822
|
-
names =
|
|
10626
|
+
names = fs9.existsSync(tasksDir) ? fs9.readdirSync(tasksDir).filter((name) => validId(name)) : [];
|
|
10823
10627
|
} catch (error) {
|
|
10824
10628
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
10825
10629
|
path: tasksDir
|
|
@@ -10860,7 +10664,7 @@ class TaskStore {
|
|
|
10860
10664
|
const file = this.paths().workspace;
|
|
10861
10665
|
let bytes;
|
|
10862
10666
|
try {
|
|
10863
|
-
bytes = retryTransient(() =>
|
|
10667
|
+
bytes = retryTransient(() => fs9.readFileSync(file, "utf8"));
|
|
10864
10668
|
} catch (error) {
|
|
10865
10669
|
if (error.code === "ENOENT")
|
|
10866
10670
|
return success(null, null, null);
|
|
@@ -10911,7 +10715,7 @@ class TaskStore {
|
|
|
10911
10715
|
const moved = this.bindLocked(again.data.id, key, input.now);
|
|
10912
10716
|
return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
|
|
10913
10717
|
}
|
|
10914
|
-
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" :
|
|
10718
|
+
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path9.basename(this.root) || this.root;
|
|
10915
10719
|
const intent = {
|
|
10916
10720
|
objective: input.objective?.trim() || `Work on ${label}`,
|
|
10917
10721
|
scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
|
|
@@ -10977,14 +10781,14 @@ class TaskStore {
|
|
|
10977
10781
|
const branch = key.ok ? key.data.branch : null;
|
|
10978
10782
|
if (!branch || !location.ok)
|
|
10979
10783
|
return success(null, null, null);
|
|
10980
|
-
const common =
|
|
10981
|
-
const heads =
|
|
10982
|
-
const headLog = location.data.git ?
|
|
10784
|
+
const common = path9.dirname(location.data.dir);
|
|
10785
|
+
const heads = path9.join(common, "logs", "refs", "heads");
|
|
10786
|
+
const headLog = location.data.git ? path9.join(location.data.git.gitDir, "logs", "HEAD") : "";
|
|
10983
10787
|
const signatures = [
|
|
10984
|
-
|
|
10788
|
+
path9.join(heads, branch),
|
|
10985
10789
|
headLog,
|
|
10986
10790
|
heads,
|
|
10987
|
-
|
|
10791
|
+
path9.join(common, "packed-refs")
|
|
10988
10792
|
].map((file) => fileSignature(file) ?? "-");
|
|
10989
10793
|
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
10990
10794
|
let resolved = origins.get(slot);
|
|
@@ -10996,7 +10800,7 @@ class TaskStore {
|
|
|
10996
10800
|
aliases.push(rename.from);
|
|
10997
10801
|
const start = branchStart(location.data, branch, aliases);
|
|
10998
10802
|
const slash = start?.indexOf("/") ?? -1;
|
|
10999
|
-
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(
|
|
10803
|
+
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path9.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
|
|
11000
10804
|
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
11001
10805
|
resolved = {
|
|
11002
10806
|
signature: signatures.join("|"),
|
|
@@ -11234,13 +11038,13 @@ class TaskStore {
|
|
|
11234
11038
|
if (!ready.ok)
|
|
11235
11039
|
return ready;
|
|
11236
11040
|
const dirs = this.paths();
|
|
11237
|
-
const recovery =
|
|
11238
|
-
if (
|
|
11041
|
+
const recovery = path9.join(this.root, ".workit", "recovery");
|
|
11042
|
+
if (fs9.existsSync(recovery)) {
|
|
11239
11043
|
let files = 0;
|
|
11240
11044
|
let bytes = 0;
|
|
11241
11045
|
try {
|
|
11242
|
-
for (const name of
|
|
11243
|
-
const stat =
|
|
11046
|
+
for (const name of fs9.readdirSync(recovery)) {
|
|
11047
|
+
const stat = fs9.lstatSync(path9.join(recovery, name));
|
|
11244
11048
|
if (!stat.isFile())
|
|
11245
11049
|
continue;
|
|
11246
11050
|
files += 1;
|
|
@@ -11249,17 +11053,17 @@ class TaskStore {
|
|
|
11249
11053
|
} catch {}
|
|
11250
11054
|
const remove = options.pruneRecovery === true && !dryRun;
|
|
11251
11055
|
if (remove)
|
|
11252
|
-
|
|
11056
|
+
fs9.rmSync(recovery, { recursive: true, force: true });
|
|
11253
11057
|
report.legacyRecovery = { path: recovery, files, bytes, removed: remove };
|
|
11254
11058
|
}
|
|
11255
|
-
if (!
|
|
11059
|
+
if (!fs9.existsSync(dirs.tasks))
|
|
11256
11060
|
return success(null, null, report);
|
|
11257
|
-
const names =
|
|
11061
|
+
const names = fs9.readdirSync(dirs.tasks).filter((name) => validId(name));
|
|
11258
11062
|
for (const name of names) {
|
|
11259
11063
|
const file = this.eventsPath(name);
|
|
11260
11064
|
let size = 0;
|
|
11261
11065
|
try {
|
|
11262
|
-
size =
|
|
11066
|
+
size = fs9.statSync(file).size;
|
|
11263
11067
|
} catch {
|
|
11264
11068
|
continue;
|
|
11265
11069
|
}
|
|
@@ -11287,16 +11091,16 @@ class TaskStore {
|
|
|
11287
11091
|
const references = new Set;
|
|
11288
11092
|
for (const name of names)
|
|
11289
11093
|
try {
|
|
11290
|
-
for (const match of
|
|
11094
|
+
for (const match of fs9.readFileSync(this.eventsPath(name), "utf8").matchAll(/"\$blob":"([0-9a-f]{64})"/g))
|
|
11291
11095
|
references.add(match[1]);
|
|
11292
11096
|
} catch {}
|
|
11293
|
-
if (
|
|
11294
|
-
for (const name of
|
|
11097
|
+
if (fs9.existsSync(dirs.blobs))
|
|
11098
|
+
for (const name of fs9.readdirSync(dirs.blobs)) {
|
|
11295
11099
|
const digest = name.replace(/\.json$/, "");
|
|
11296
|
-
const file =
|
|
11100
|
+
const file = path9.join(dirs.blobs, name);
|
|
11297
11101
|
let stat;
|
|
11298
11102
|
try {
|
|
11299
|
-
stat =
|
|
11103
|
+
stat = fs9.lstatSync(file);
|
|
11300
11104
|
} catch {
|
|
11301
11105
|
continue;
|
|
11302
11106
|
}
|
|
@@ -11307,11 +11111,11 @@ class TaskStore {
|
|
|
11307
11111
|
report.blobs.removed += 1;
|
|
11308
11112
|
report.blobs.removedBytes += stat.size;
|
|
11309
11113
|
if (!dryRun)
|
|
11310
|
-
|
|
11114
|
+
fs9.rmSync(file, { force: true });
|
|
11311
11115
|
}
|
|
11312
11116
|
return success(null, null, null);
|
|
11313
11117
|
};
|
|
11314
|
-
const swept = dryRun ? sweep() : this.withLock(
|
|
11118
|
+
const swept = dryRun ? sweep() : this.withLock(path9.join(dirs.dir, "gc.lock"), sweep);
|
|
11315
11119
|
if (!swept.ok)
|
|
11316
11120
|
return swept;
|
|
11317
11121
|
const temporaryDirs = [
|
|
@@ -11319,17 +11123,17 @@ class TaskStore {
|
|
|
11319
11123
|
dirs.tasks,
|
|
11320
11124
|
dirs.blobs,
|
|
11321
11125
|
dirs.checkout,
|
|
11322
|
-
...names.map((name) =>
|
|
11126
|
+
...names.map((name) => path9.join(dirs.tasks, name))
|
|
11323
11127
|
];
|
|
11324
11128
|
for (const directory of temporaryDirs) {
|
|
11325
|
-
if (!
|
|
11129
|
+
if (!fs9.existsSync(directory))
|
|
11326
11130
|
continue;
|
|
11327
|
-
for (const name of
|
|
11131
|
+
for (const name of fs9.readdirSync(directory)) {
|
|
11328
11132
|
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
11329
11133
|
continue;
|
|
11330
|
-
const file =
|
|
11134
|
+
const file = path9.join(directory, name);
|
|
11331
11135
|
try {
|
|
11332
|
-
const stat =
|
|
11136
|
+
const stat = fs9.lstatSync(file);
|
|
11333
11137
|
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
11334
11138
|
continue;
|
|
11335
11139
|
} catch {
|
|
@@ -11337,37 +11141,37 @@ class TaskStore {
|
|
|
11337
11141
|
}
|
|
11338
11142
|
report.temporary.removed += 1;
|
|
11339
11143
|
if (!dryRun)
|
|
11340
|
-
|
|
11144
|
+
fs9.rmSync(file, { force: true });
|
|
11341
11145
|
}
|
|
11342
11146
|
}
|
|
11343
11147
|
return success(null, null, report);
|
|
11344
11148
|
}
|
|
11345
11149
|
paths() {
|
|
11346
11150
|
const location = this.location();
|
|
11347
|
-
const dir = location.ok ? location.data.dir :
|
|
11348
|
-
const checkout =
|
|
11151
|
+
const dir = location.ok ? location.data.dir : path9.join(this.root, ".workit");
|
|
11152
|
+
const checkout = path9.join(dir, "checkouts", checkoutSlug(this.root));
|
|
11349
11153
|
return {
|
|
11350
11154
|
dir,
|
|
11351
11155
|
shared: location.ok ? location.data.shared : false,
|
|
11352
|
-
marker:
|
|
11156
|
+
marker: path9.join(dir, "store.json"),
|
|
11353
11157
|
checkout,
|
|
11354
|
-
workspace:
|
|
11355
|
-
checkoutLock:
|
|
11356
|
-
tasks:
|
|
11357
|
-
index:
|
|
11358
|
-
blobs:
|
|
11359
|
-
legacy:
|
|
11360
|
-
v2:
|
|
11158
|
+
workspace: path9.join(checkout, "workspace.json"),
|
|
11159
|
+
checkoutLock: path9.join(checkout, "metadata.lock"),
|
|
11160
|
+
tasks: path9.join(dir, "tasks"),
|
|
11161
|
+
index: path9.join(dir, "task-index.json"),
|
|
11162
|
+
blobs: path9.join(dir, "blobs", "candidates"),
|
|
11163
|
+
legacy: path9.join(dir, "legacy", checkoutSlug(this.root), "v2"),
|
|
11164
|
+
v2: path9.join(this.root, ".workit")
|
|
11361
11165
|
};
|
|
11362
11166
|
}
|
|
11363
11167
|
eventsPath(taskId) {
|
|
11364
|
-
return
|
|
11168
|
+
return path9.join(this.paths().tasks, taskId, "events.jsonl");
|
|
11365
11169
|
}
|
|
11366
11170
|
snapshotPath(taskId) {
|
|
11367
|
-
return
|
|
11171
|
+
return path9.join(this.paths().tasks, taskId, "snapshot.json");
|
|
11368
11172
|
}
|
|
11369
11173
|
taskLockPath(taskId) {
|
|
11370
|
-
return
|
|
11174
|
+
return path9.join(this.paths().tasks, taskId, "lock");
|
|
11371
11175
|
}
|
|
11372
11176
|
ready(write = false) {
|
|
11373
11177
|
const location = this.location();
|
|
@@ -11377,7 +11181,7 @@ class TaskStore {
|
|
|
11377
11181
|
return success(null, null, null);
|
|
11378
11182
|
const dirs = this.paths();
|
|
11379
11183
|
try {
|
|
11380
|
-
const marker = JSON.parse(
|
|
11184
|
+
const marker = JSON.parse(fs9.readFileSync(dirs.marker, "utf8"));
|
|
11381
11185
|
if (typeof marker.version === "number" && marker.version > STORE_FORMAT)
|
|
11382
11186
|
return failure2("recovery_required", `the workit store at ${dirs.dir} has format ${marker.version}; upgrade Workit to use it`, { path: dirs.marker });
|
|
11383
11187
|
} catch {}
|
|
@@ -11406,21 +11210,21 @@ class TaskStore {
|
|
|
11406
11210
|
return {
|
|
11407
11211
|
read: (digest) => {
|
|
11408
11212
|
try {
|
|
11409
|
-
return JSON.parse(
|
|
11213
|
+
return JSON.parse(fs9.readFileSync(path9.join(dir, `${digest}.json`), "utf8"));
|
|
11410
11214
|
} catch (error) {
|
|
11411
11215
|
throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
|
|
11412
11216
|
}
|
|
11413
11217
|
},
|
|
11414
11218
|
write: (digest, value) => {
|
|
11415
|
-
const file =
|
|
11416
|
-
if (
|
|
11219
|
+
const file = path9.join(dir, `${digest}.json`);
|
|
11220
|
+
if (fs9.existsSync(file)) {
|
|
11417
11221
|
const stamp = new Date;
|
|
11418
11222
|
try {
|
|
11419
|
-
|
|
11223
|
+
fs9.utimesSync(file, stamp, stamp);
|
|
11420
11224
|
return;
|
|
11421
11225
|
} catch {}
|
|
11422
11226
|
}
|
|
11423
|
-
|
|
11227
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
11424
11228
|
replaceFile(file, JSON.stringify(value), true);
|
|
11425
11229
|
}
|
|
11426
11230
|
};
|
|
@@ -11502,7 +11306,7 @@ class TaskStore {
|
|
|
11502
11306
|
}
|
|
11503
11307
|
readSnapshot(taskId) {
|
|
11504
11308
|
try {
|
|
11505
|
-
const value = JSON.parse(
|
|
11309
|
+
const value = JSON.parse(fs9.readFileSync(this.snapshotPath(taskId), "utf8"));
|
|
11506
11310
|
if (!isObject4(value) || value.v !== 1 || value.task !== taskId || typeof value.seq !== "number" || typeof value.lastId !== "string" || typeof value.lastStart !== "number" || typeof value.end !== "number" || !isObject4(value.record))
|
|
11507
11311
|
return null;
|
|
11508
11312
|
return value;
|
|
@@ -11604,8 +11408,8 @@ class TaskStore {
|
|
|
11604
11408
|
}
|
|
11605
11409
|
}
|
|
11606
11410
|
openTask(record, type, key, legacy = null) {
|
|
11607
|
-
const dir =
|
|
11608
|
-
|
|
11411
|
+
const dir = path9.join(this.paths().tasks, record.id);
|
|
11412
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
11609
11413
|
const blobs = this.blobStore();
|
|
11610
11414
|
const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
|
|
11611
11415
|
const event = this.event(record.id, 1, type, {
|
|
@@ -11657,11 +11461,11 @@ class TaskStore {
|
|
|
11657
11461
|
} catch {}
|
|
11658
11462
|
if (!latest || latest.events[0]?.id !== last.id)
|
|
11659
11463
|
return failure2("busy", "the task log changed during compaction; retry", { taskId });
|
|
11660
|
-
const appended =
|
|
11464
|
+
const appended = fs9.readFileSync(file).subarray(log.end, latest.end);
|
|
11661
11465
|
const bytes = Buffer.concat([Buffer.from(prefix, "utf8"), appended]);
|
|
11662
11466
|
replaceFile(file, bytes.toString("utf8"), true);
|
|
11663
11467
|
try {
|
|
11664
|
-
|
|
11468
|
+
fs9.rmSync(this.snapshotPath(taskId), { force: true });
|
|
11665
11469
|
} catch {}
|
|
11666
11470
|
const reloaded = this.load(taskId);
|
|
11667
11471
|
if (reloaded.ok && reloaded.data)
|
|
@@ -11674,7 +11478,7 @@ class TaskStore {
|
|
|
11674
11478
|
}
|
|
11675
11479
|
countEvents(file) {
|
|
11676
11480
|
try {
|
|
11677
|
-
const bytes =
|
|
11481
|
+
const bytes = fs9.readFileSync(file);
|
|
11678
11482
|
let count = 0;
|
|
11679
11483
|
for (let index = bytes.indexOf(10);index >= 0; index = bytes.indexOf(10, index + 1))
|
|
11680
11484
|
count += 1;
|
|
@@ -11697,7 +11501,7 @@ class TaskStore {
|
|
|
11697
11501
|
}
|
|
11698
11502
|
withKeyLock(key, operation) {
|
|
11699
11503
|
const digest = createHash6("sha256").update(key).digest("hex").slice(0, 32);
|
|
11700
|
-
return this.withLock(
|
|
11504
|
+
return this.withLock(path9.join(this.paths().dir, "keys", `${digest}.lock`), operation);
|
|
11701
11505
|
}
|
|
11702
11506
|
bindLocked(taskId, key, timestamp) {
|
|
11703
11507
|
const current = this.readWorkspace();
|
|
@@ -11838,7 +11642,7 @@ class TaskStore {
|
|
|
11838
11642
|
writeWorkspace(record) {
|
|
11839
11643
|
const file = this.paths().workspace;
|
|
11840
11644
|
try {
|
|
11841
|
-
|
|
11645
|
+
fs9.mkdirSync(path9.dirname(file), { recursive: true });
|
|
11842
11646
|
replaceFile(file, `${canonicalJson(record)}
|
|
11843
11647
|
`, true);
|
|
11844
11648
|
return success(null, null, null);
|
|
@@ -11848,7 +11652,7 @@ class TaskStore {
|
|
|
11848
11652
|
}
|
|
11849
11653
|
readIndex() {
|
|
11850
11654
|
try {
|
|
11851
|
-
const value = JSON.parse(
|
|
11655
|
+
const value = JSON.parse(fs9.readFileSync(this.paths().index, "utf8"));
|
|
11852
11656
|
if (!isObject4(value) || value.version !== INDEX_VERSION || !isObject4(value.tasks))
|
|
11853
11657
|
return {};
|
|
11854
11658
|
const tasks = {};
|
|
@@ -11862,7 +11666,7 @@ class TaskStore {
|
|
|
11862
11666
|
}
|
|
11863
11667
|
writeIndex(entries) {
|
|
11864
11668
|
try {
|
|
11865
|
-
|
|
11669
|
+
fs9.mkdirSync(this.paths().tasks, { recursive: true });
|
|
11866
11670
|
replaceFile(this.paths().index, JSON.stringify({
|
|
11867
11671
|
version: INDEX_VERSION,
|
|
11868
11672
|
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
@@ -11916,23 +11720,23 @@ class TaskStore {
|
|
|
11916
11720
|
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
11917
11721
|
}
|
|
11918
11722
|
v2Dirs() {
|
|
11919
|
-
return [...new Set([this.root, this.origin])].map((dir) =>
|
|
11723
|
+
return [...new Set([this.root, this.origin])].map((dir) => path9.join(dir, ".workit"));
|
|
11920
11724
|
}
|
|
11921
11725
|
pendingMigrations() {
|
|
11922
11726
|
return this.v2Dirs().filter((dir) => {
|
|
11923
|
-
const raw = this.readV2Workspace(
|
|
11727
|
+
const raw = this.readV2Workspace(path9.join(dir, "workspace.json"));
|
|
11924
11728
|
return raw !== null && raw !== TOMBSTONE;
|
|
11925
11729
|
});
|
|
11926
11730
|
}
|
|
11927
11731
|
migrateV2(v2) {
|
|
11928
11732
|
const dirs = this.paths();
|
|
11929
|
-
const v2Workspace =
|
|
11930
|
-
const v2Tasks =
|
|
11931
|
-
const legacy =
|
|
11733
|
+
const v2Workspace = path9.join(v2, "workspace.json");
|
|
11734
|
+
const v2Tasks = path9.join(v2, "tasks");
|
|
11735
|
+
const legacy = path9.join(dirs.dir, "legacy", checkoutSlug(path9.dirname(v2)), "v2");
|
|
11932
11736
|
const raw = this.readV2Workspace(v2Workspace);
|
|
11933
11737
|
if (raw === null || raw === TOMBSTONE)
|
|
11934
11738
|
return success(null, null, null);
|
|
11935
|
-
return this.withLock(dirs.checkoutLock, () => this.withLock(
|
|
11739
|
+
return this.withLock(dirs.checkoutLock, () => this.withLock(path9.join(v2, "metadata.lock"), () => {
|
|
11936
11740
|
const workspace = this.readV2Workspace(v2Workspace);
|
|
11937
11741
|
if (workspace === null || workspace === TOMBSTONE)
|
|
11938
11742
|
return success(null, null, null);
|
|
@@ -11941,7 +11745,7 @@ class TaskStore {
|
|
|
11941
11745
|
return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
|
|
11942
11746
|
let files = [];
|
|
11943
11747
|
try {
|
|
11944
|
-
files =
|
|
11748
|
+
files = fs9.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path9.join(v2Tasks, name)));
|
|
11945
11749
|
} catch {}
|
|
11946
11750
|
const current = this.readWorkspace();
|
|
11947
11751
|
if (!current.ok)
|
|
@@ -11959,22 +11763,22 @@ class TaskStore {
|
|
|
11959
11763
|
let skipped = 0;
|
|
11960
11764
|
const done = new Map;
|
|
11961
11765
|
try {
|
|
11962
|
-
|
|
11766
|
+
fs9.mkdirSync(path9.join(legacy, "tasks"), { recursive: true });
|
|
11963
11767
|
for (const name of ["workspace.json", "index.json"])
|
|
11964
|
-
if (
|
|
11965
|
-
|
|
11768
|
+
if (fs9.existsSync(path9.join(v2, name)))
|
|
11769
|
+
fs9.copyFileSync(path9.join(v2, name), path9.join(legacy, name));
|
|
11966
11770
|
for (const name of files) {
|
|
11967
11771
|
const id = name.slice(0, -5);
|
|
11968
|
-
const file =
|
|
11969
|
-
const bytes =
|
|
11772
|
+
const file = path9.join(v2Tasks, name);
|
|
11773
|
+
const bytes = fs9.readFileSync(file, "utf8");
|
|
11970
11774
|
const digest = sha256(bytes);
|
|
11971
|
-
|
|
11775
|
+
fs9.writeFileSync(path9.join(legacy, "tasks", name), bytes);
|
|
11972
11776
|
const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
|
|
11973
11777
|
if (!parsed.ok || parsed.data.id !== id)
|
|
11974
11778
|
return failure2("recovery_required", `cannot migrate 2.x task ${id}: ${parsed.ok ? "file name and record ID differ" : parsed.error}; move ${file} aside to continue`, { path: file });
|
|
11975
11779
|
const record = { ...parsed.data, workspaceId: target.id };
|
|
11976
|
-
const origin = { path:
|
|
11977
|
-
if (!
|
|
11780
|
+
const origin = { path: path9.join(legacy, "tasks", name), digest };
|
|
11781
|
+
if (!fs9.existsSync(this.eventsPath(id))) {
|
|
11978
11782
|
this.openTask(record, "migrated.from_v2", null, origin);
|
|
11979
11783
|
migrated += 1;
|
|
11980
11784
|
} else {
|
|
@@ -12009,8 +11813,8 @@ class TaskStore {
|
|
|
12009
11813
|
try {
|
|
12010
11814
|
let complete = true;
|
|
12011
11815
|
for (const [name, digest] of done) {
|
|
12012
|
-
const file =
|
|
12013
|
-
if (sha256(
|
|
11816
|
+
const file = path9.join(v2Tasks, name);
|
|
11817
|
+
if (sha256(fs9.readFileSync(file, "utf8")) !== digest) {
|
|
12014
11818
|
complete = false;
|
|
12015
11819
|
continue;
|
|
12016
11820
|
}
|
|
@@ -12020,7 +11824,7 @@ class TaskStore {
|
|
|
12020
11824
|
return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
|
|
12021
11825
|
path: v2
|
|
12022
11826
|
});
|
|
12023
|
-
|
|
11827
|
+
fs9.rmSync(path9.join(v2, "index.json"), { force: true });
|
|
12024
11828
|
this.writeTombstone(v2, target.id);
|
|
12025
11829
|
} catch (error) {
|
|
12026
11830
|
return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
|
|
@@ -12040,45 +11844,45 @@ class TaskStore {
|
|
|
12040
11844
|
}
|
|
12041
11845
|
adoptLocalStore() {
|
|
12042
11846
|
const dirs = this.paths();
|
|
12043
|
-
const marker =
|
|
12044
|
-
if (!dirs.shared || !
|
|
11847
|
+
const marker = path9.join(dirs.v2, "store.json");
|
|
11848
|
+
if (!dirs.shared || !fs9.existsSync(marker))
|
|
12045
11849
|
return success(null, null, null);
|
|
12046
11850
|
return this.withLock(dirs.checkoutLock, () => {
|
|
12047
|
-
if (!
|
|
11851
|
+
if (!fs9.existsSync(marker))
|
|
12048
11852
|
return success(null, null, null);
|
|
12049
11853
|
let tasks = 0;
|
|
12050
11854
|
try {
|
|
12051
11855
|
const merge = (from, to) => {
|
|
12052
|
-
if (!
|
|
12053
|
-
|
|
11856
|
+
if (!fs9.existsSync(to)) {
|
|
11857
|
+
fs9.mkdirSync(path9.dirname(to), { recursive: true });
|
|
12054
11858
|
try {
|
|
12055
|
-
|
|
11859
|
+
fs9.renameSync(from, to);
|
|
12056
11860
|
} catch {
|
|
12057
|
-
|
|
12058
|
-
|
|
11861
|
+
fs9.cpSync(from, to, { recursive: true, errorOnExist: true, force: false });
|
|
11862
|
+
fs9.rmSync(from, { recursive: true, force: true });
|
|
12059
11863
|
}
|
|
12060
11864
|
return;
|
|
12061
11865
|
}
|
|
12062
|
-
if (
|
|
12063
|
-
for (const name of
|
|
12064
|
-
merge(
|
|
11866
|
+
if (fs9.lstatSync(from).isDirectory() && fs9.lstatSync(to).isDirectory())
|
|
11867
|
+
for (const name of fs9.readdirSync(from))
|
|
11868
|
+
merge(path9.join(from, name), path9.join(to, name));
|
|
12065
11869
|
};
|
|
12066
11870
|
for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
|
|
12067
|
-
const from =
|
|
12068
|
-
if (!
|
|
11871
|
+
const from = path9.join(dirs.v2, sub);
|
|
11872
|
+
if (!fs9.existsSync(from))
|
|
12069
11873
|
continue;
|
|
12070
11874
|
if (sub === "tasks") {
|
|
12071
|
-
for (const name of
|
|
12072
|
-
if (validId(name) && !
|
|
11875
|
+
for (const name of fs9.readdirSync(from))
|
|
11876
|
+
if (validId(name) && !fs9.existsSync(path9.join(dirs.dir, sub, name)))
|
|
12073
11877
|
tasks += 1;
|
|
12074
11878
|
}
|
|
12075
|
-
merge(from,
|
|
11879
|
+
merge(from, path9.join(dirs.dir, sub));
|
|
12076
11880
|
}
|
|
12077
|
-
const ledger =
|
|
12078
|
-
if (
|
|
12079
|
-
merge(ledger,
|
|
12080
|
-
|
|
12081
|
-
|
|
11881
|
+
const ledger = path9.join(dirs.v2, "ledger");
|
|
11882
|
+
if (fs9.existsSync(ledger) && !fs9.existsSync(path9.join(dirs.dir, "ledger")))
|
|
11883
|
+
merge(ledger, path9.join(dirs.dir, "ledger"));
|
|
11884
|
+
fs9.rmSync(path9.join(dirs.v2, "task-index.json"), { force: true });
|
|
11885
|
+
fs9.renameSync(marker, path9.join(dirs.v2, "store.moved.json"));
|
|
12082
11886
|
} catch (error) {
|
|
12083
11887
|
return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
|
|
12084
11888
|
path: dirs.v2
|
|
@@ -12098,7 +11902,7 @@ class TaskStore {
|
|
|
12098
11902
|
readV2Workspace(file) {
|
|
12099
11903
|
let bytes;
|
|
12100
11904
|
try {
|
|
12101
|
-
bytes =
|
|
11905
|
+
bytes = fs9.readFileSync(file, "utf8");
|
|
12102
11906
|
} catch {
|
|
12103
11907
|
return null;
|
|
12104
11908
|
}
|
|
@@ -12116,7 +11920,7 @@ class TaskStore {
|
|
|
12116
11920
|
schemaVersion: SCHEMA_VERSION,
|
|
12117
11921
|
id: workspaceId ?? newId(),
|
|
12118
11922
|
revision: newRevision(),
|
|
12119
|
-
root:
|
|
11923
|
+
root: path9.dirname(v2),
|
|
12120
11924
|
runtime: { createdWith: version, updatedWith: version },
|
|
12121
11925
|
writer: null,
|
|
12122
11926
|
store: {
|
|
@@ -12127,12 +11931,12 @@ class TaskStore {
|
|
|
12127
11931
|
},
|
|
12128
11932
|
critical: ["store"]
|
|
12129
11933
|
};
|
|
12130
|
-
|
|
12131
|
-
const ignore =
|
|
12132
|
-
if (!
|
|
12133
|
-
|
|
11934
|
+
fs9.mkdirSync(v2, { recursive: true });
|
|
11935
|
+
const ignore = path9.join(v2, ".gitignore");
|
|
11936
|
+
if (!fs9.existsSync(ignore))
|
|
11937
|
+
fs9.writeFileSync(ignore, `*
|
|
12134
11938
|
`);
|
|
12135
|
-
replaceFile(
|
|
11939
|
+
replaceFile(path9.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
|
|
12136
11940
|
`, true);
|
|
12137
11941
|
}
|
|
12138
11942
|
stubFor(taskId) {
|
|
@@ -12148,7 +11952,7 @@ class TaskStore {
|
|
|
12148
11952
|
}
|
|
12149
11953
|
isStub(file) {
|
|
12150
11954
|
try {
|
|
12151
|
-
const value = JSON.parse(
|
|
11955
|
+
const value = JSON.parse(fs9.readFileSync(file, "utf8"));
|
|
12152
11956
|
return isObject4(value) && isObject4(value.store) && value.store.format === "workit-store";
|
|
12153
11957
|
} catch {
|
|
12154
11958
|
return false;
|
|
@@ -12156,32 +11960,32 @@ class TaskStore {
|
|
|
12156
11960
|
}
|
|
12157
11961
|
writeTombstoneIfMissing() {
|
|
12158
11962
|
try {
|
|
12159
|
-
const v2 =
|
|
12160
|
-
if (
|
|
11963
|
+
const v2 = path9.join(this.root, ".workit");
|
|
11964
|
+
if (fs9.existsSync(path9.join(v2, "workspace.json")))
|
|
12161
11965
|
return;
|
|
12162
11966
|
let id = null;
|
|
12163
11967
|
try {
|
|
12164
|
-
id = JSON.parse(
|
|
11968
|
+
id = JSON.parse(fs9.readFileSync(this.paths().workspace, "utf8")).id ?? null;
|
|
12165
11969
|
} catch {}
|
|
12166
11970
|
this.writeTombstone(v2, id);
|
|
12167
11971
|
} catch {}
|
|
12168
11972
|
}
|
|
12169
11973
|
initializeStorage() {
|
|
12170
11974
|
const dirs = this.paths();
|
|
12171
|
-
retryTransient(() =>
|
|
12172
|
-
retryTransient(() =>
|
|
12173
|
-
if (!
|
|
11975
|
+
retryTransient(() => fs9.mkdirSync(dirs.checkout, { recursive: true }));
|
|
11976
|
+
retryTransient(() => fs9.mkdirSync(dirs.tasks, { recursive: true }));
|
|
11977
|
+
if (!fs9.existsSync(dirs.marker))
|
|
12174
11978
|
replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
|
|
12175
11979
|
`, false);
|
|
12176
11980
|
if (!dirs.shared) {
|
|
12177
|
-
const ignore =
|
|
11981
|
+
const ignore = path9.join(dirs.dir, ".gitignore");
|
|
12178
11982
|
let current = null;
|
|
12179
11983
|
try {
|
|
12180
|
-
current =
|
|
11984
|
+
current = fs9.readFileSync(ignore, "utf8");
|
|
12181
11985
|
} catch {}
|
|
12182
11986
|
if (current !== `*
|
|
12183
11987
|
`)
|
|
12184
|
-
retryTransient(() =>
|
|
11988
|
+
retryTransient(() => fs9.writeFileSync(ignore, `*
|
|
12185
11989
|
`));
|
|
12186
11990
|
}
|
|
12187
11991
|
this.writeTombstoneIfMissing();
|
|
@@ -12197,7 +12001,7 @@ class TaskStore {
|
|
|
12197
12001
|
shouldReclaim: ({ payload, nowMs }) => {
|
|
12198
12002
|
let ageMs = null;
|
|
12199
12003
|
try {
|
|
12200
|
-
ageMs = nowMs -
|
|
12004
|
+
ageMs = nowMs - fs9.lstatSync(lockPath).mtimeMs;
|
|
12201
12005
|
} catch {}
|
|
12202
12006
|
return classifyLockOwner(payload, ageMs, localLockHost(), TASK_STORE_LOCK).state === "stale";
|
|
12203
12007
|
},
|
|
@@ -12234,7 +12038,7 @@ class TaskStore {
|
|
|
12234
12038
|
withLock(lockPath, operation) {
|
|
12235
12039
|
try {
|
|
12236
12040
|
this.initializeStorage();
|
|
12237
|
-
|
|
12041
|
+
fs9.mkdirSync(path9.dirname(lockPath), { recursive: true });
|
|
12238
12042
|
} catch (error) {
|
|
12239
12043
|
return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
12240
12044
|
path: this.paths().dir
|
|
@@ -12274,18 +12078,18 @@ class TaskStore {
|
|
|
12274
12078
|
lockDirWritable(lockPath) {
|
|
12275
12079
|
const probe = `${lockPath}.${process.pid}.${randomUUID3()}.probe`;
|
|
12276
12080
|
try {
|
|
12277
|
-
|
|
12081
|
+
fs9.closeSync(fs9.openSync(probe, "wx", 384));
|
|
12278
12082
|
} catch {
|
|
12279
12083
|
return false;
|
|
12280
12084
|
}
|
|
12281
12085
|
try {
|
|
12282
|
-
|
|
12086
|
+
fs9.rmSync(probe, { force: true });
|
|
12283
12087
|
} catch {}
|
|
12284
12088
|
return true;
|
|
12285
12089
|
}
|
|
12286
12090
|
lockHolderPresent(lockPath) {
|
|
12287
12091
|
try {
|
|
12288
|
-
|
|
12092
|
+
fs9.lstatSync(lockPath);
|
|
12289
12093
|
return true;
|
|
12290
12094
|
} catch (error) {
|
|
12291
12095
|
return error?.code !== "ENOENT";
|
|
@@ -12297,12 +12101,12 @@ class TaskStore {
|
|
|
12297
12101
|
if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
|
|
12298
12102
|
return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
|
|
12299
12103
|
path: lockPath,
|
|
12300
|
-
guidance: `Check permissions and the read-only attribute on ${
|
|
12104
|
+
guidance: `Check permissions and the read-only attribute on ${path9.dirname(lockPath)}.`
|
|
12301
12105
|
});
|
|
12302
12106
|
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
12303
12107
|
let lock = null;
|
|
12304
12108
|
try {
|
|
12305
|
-
lock = parseMetadataLockOrNull(
|
|
12109
|
+
lock = parseMetadataLockOrNull(fs9.readFileSync(lockPath, "utf8"));
|
|
12306
12110
|
} catch {}
|
|
12307
12111
|
return failure2("busy", `workit lock is held by another Workit call${lock ? ` (pid ${lock.pid} on ${lock.host})` : ""}; retry shortly`, {
|
|
12308
12112
|
outcome: "not_started",
|
|
@@ -12317,18 +12121,18 @@ class TaskStore {
|
|
|
12317
12121
|
|
|
12318
12122
|
// packages/workit-core/src/core/policy/derive.ts
|
|
12319
12123
|
import { existsSync as existsSync4 } from "node:fs";
|
|
12320
|
-
import
|
|
12124
|
+
import path14 from "node:path";
|
|
12321
12125
|
|
|
12322
12126
|
// packages/workit-core/src/ledger.ts
|
|
12323
|
-
import { spawnSync as
|
|
12127
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12324
12128
|
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
12325
|
-
import fs12 from "node:fs";
|
|
12326
|
-
import path14 from "node:path";
|
|
12327
|
-
|
|
12328
|
-
// packages/workit-core/src/core/vcs-config.ts
|
|
12329
12129
|
import fs11 from "node:fs";
|
|
12330
12130
|
import path13 from "node:path";
|
|
12331
|
-
|
|
12131
|
+
|
|
12132
|
+
// packages/workit-core/src/core/vcs-config.ts
|
|
12133
|
+
import fs10 from "node:fs";
|
|
12134
|
+
import path12 from "node:path";
|
|
12135
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
12332
12136
|
|
|
12333
12137
|
// packages/workit-core/src/core/config.ts
|
|
12334
12138
|
import {
|
|
@@ -12341,7 +12145,7 @@ import {
|
|
|
12341
12145
|
writeFileSync as writeFileSync2
|
|
12342
12146
|
} from "node:fs";
|
|
12343
12147
|
import os3 from "node:os";
|
|
12344
|
-
import
|
|
12148
|
+
import path10 from "node:path";
|
|
12345
12149
|
|
|
12346
12150
|
// packages/workit-core/src/core/boundary.ts
|
|
12347
12151
|
var EVENT = {
|
|
@@ -12385,7 +12189,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
12385
12189
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
12386
12190
|
};
|
|
12387
12191
|
};
|
|
12388
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
12192
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path10.join(process.env.XDG_CONFIG_HOME || path10.join(os3.homedir(), ".config"), "workit");
|
|
12389
12193
|
var migratedDir = null;
|
|
12390
12194
|
var migrationFailed = false;
|
|
12391
12195
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -12395,7 +12199,7 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
12395
12199
|
migratedDir = dir;
|
|
12396
12200
|
return dir;
|
|
12397
12201
|
}
|
|
12398
|
-
const legacy =
|
|
12202
|
+
const legacy = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os3.homedir(), ".config"), "workflow-toolkit");
|
|
12399
12203
|
if (!existsSync3(legacy)) {
|
|
12400
12204
|
migratedDir = dir;
|
|
12401
12205
|
return dir;
|
|
@@ -12408,8 +12212,8 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
12408
12212
|
mkdirSync2(dir, { recursive: true });
|
|
12409
12213
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
12410
12214
|
for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
|
|
12411
|
-
const src =
|
|
12412
|
-
const dest =
|
|
12215
|
+
const src = path10.join(legacy, entry.name);
|
|
12216
|
+
const dest = path10.join(dir, entry.name);
|
|
12413
12217
|
if (existsSync3(dest))
|
|
12414
12218
|
continue;
|
|
12415
12219
|
try {
|
|
@@ -12481,7 +12285,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
12481
12285
|
return { status: "valid", path: file, config };
|
|
12482
12286
|
};
|
|
12483
12287
|
var readConfigTyped = (dir) => {
|
|
12484
|
-
const file =
|
|
12288
|
+
const file = path10.join(dir ?? configDir(), "config.json");
|
|
12485
12289
|
return parseConfigResult(readSafe(file), file);
|
|
12486
12290
|
};
|
|
12487
12291
|
var readConfig = () => {
|
|
@@ -12490,7 +12294,7 @@ var readConfig = () => {
|
|
|
12490
12294
|
throw new Error(result.error);
|
|
12491
12295
|
return result.config ?? DEFAULTS;
|
|
12492
12296
|
};
|
|
12493
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
12297
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
12494
12298
|
var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
12495
12299
|
var resolveBranchPolicy = (config, workspace, repoPreset) => {
|
|
12496
12300
|
const wp = workspace?.branchPolicy ?? {};
|
|
@@ -12531,13 +12335,13 @@ var resolveCommitPolicy = (config, workspace) => {
|
|
|
12531
12335
|
};
|
|
12532
12336
|
|
|
12533
12337
|
// packages/workit-core/src/core/workspaces.ts
|
|
12534
|
-
import { spawnSync as
|
|
12338
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
12535
12339
|
import { createHash as createHash7 } from "node:crypto";
|
|
12536
12340
|
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
12537
|
-
import
|
|
12341
|
+
import path11 from "node:path";
|
|
12538
12342
|
|
|
12539
12343
|
// packages/workit-core/src/core/branch-policy.ts
|
|
12540
|
-
import { spawnSync as
|
|
12344
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
12541
12345
|
var PREFIXES = {
|
|
12542
12346
|
feature: "feature/*",
|
|
12543
12347
|
bugfix: "bugfix/*",
|
|
@@ -12545,7 +12349,7 @@ var PREFIXES = {
|
|
|
12545
12349
|
hotfix: "hotfix/*"
|
|
12546
12350
|
};
|
|
12547
12351
|
var branchNames = (workspaceRoot) => {
|
|
12548
|
-
const r =
|
|
12352
|
+
const r = spawnSync4("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/remotes/origin"], { cwd: workspaceRoot, encoding: "utf8" });
|
|
12549
12353
|
if (r.status !== 0)
|
|
12550
12354
|
return new Set;
|
|
12551
12355
|
return new Set((r.stdout ?? "").split(`
|
|
@@ -12604,7 +12408,7 @@ var repoBranchPreset = (workspaceRoot) => {
|
|
|
12604
12408
|
var releaseTagPrefixOf = (workspaceRoot, policyPrefix) => {
|
|
12605
12409
|
if (typeof policyPrefix === "string")
|
|
12606
12410
|
return policyPrefix.trim();
|
|
12607
|
-
const r =
|
|
12411
|
+
const r = spawnSync4("git", ["config", "--get", "gitflow.prefix.versiontag"], {
|
|
12608
12412
|
cwd: workspaceRoot,
|
|
12609
12413
|
encoding: "utf8"
|
|
12610
12414
|
});
|
|
@@ -12669,7 +12473,7 @@ var releaseTrackSchema = object({
|
|
|
12669
12473
|
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
12670
12474
|
}
|
|
12671
12475
|
}
|
|
12672
|
-
if (track.versionSource?.kind === "package-json" && (
|
|
12476
|
+
if (track.versionSource?.kind === "package-json" && (path11.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
|
|
12673
12477
|
context.addIssue({
|
|
12674
12478
|
code: "custom",
|
|
12675
12479
|
path: ["versionSource", "path"],
|
|
@@ -12689,6 +12493,7 @@ var workspaceConfigSchema = object({
|
|
|
12689
12493
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
12690
12494
|
defaultEndpoint: unknown().optional(),
|
|
12691
12495
|
verification: _enum(["self", "independent"]).optional(),
|
|
12496
|
+
delegation: unknown().optional(),
|
|
12692
12497
|
trailers: boolean2().optional(),
|
|
12693
12498
|
liveSessionGuard: boolean2().optional(),
|
|
12694
12499
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
@@ -12769,7 +12574,7 @@ var validateWorkspacesDocument = (parsed, file) => {
|
|
|
12769
12574
|
};
|
|
12770
12575
|
};
|
|
12771
12576
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
12772
|
-
const file =
|
|
12577
|
+
const file = path11.join(dir, "workspaces.json");
|
|
12773
12578
|
let raw;
|
|
12774
12579
|
try {
|
|
12775
12580
|
raw = readFileSync3(file, "utf8");
|
|
@@ -12868,7 +12673,7 @@ var mainCheckoutOf = (cwd) => {
|
|
|
12868
12673
|
for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
|
|
12869
12674
|
delete env[name];
|
|
12870
12675
|
const git = (args) => {
|
|
12871
|
-
const run =
|
|
12676
|
+
const run = spawnSync5("git", args, {
|
|
12872
12677
|
cwd,
|
|
12873
12678
|
env,
|
|
12874
12679
|
encoding: "utf8",
|
|
@@ -12880,9 +12685,9 @@ var mainCheckoutOf = (cwd) => {
|
|
|
12880
12685
|
let main = null;
|
|
12881
12686
|
const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
|
|
12882
12687
|
const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
|
|
12883
|
-
if (gitDir && common &&
|
|
12688
|
+
if (gitDir && common && path11.resolve(gitDir) !== path11.resolve(common)) {
|
|
12884
12689
|
const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
|
|
12885
|
-
main = listed?.trim() || (
|
|
12690
|
+
main = listed?.trim() || (path11.basename(common) === ".git" ? path11.dirname(common) : common);
|
|
12886
12691
|
}
|
|
12887
12692
|
mainCheckouts.set(cwd, main);
|
|
12888
12693
|
return main;
|
|
@@ -12973,7 +12778,7 @@ var runtimeWorkspaceIndexSchema = object({
|
|
|
12973
12778
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
12974
12779
|
}).passthrough();
|
|
12975
12780
|
var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
|
|
12976
|
-
const file =
|
|
12781
|
+
const file = path11.join(dir, "workspaces.json");
|
|
12977
12782
|
let raw;
|
|
12978
12783
|
try {
|
|
12979
12784
|
raw = readFileSync3(file, "utf8");
|
|
@@ -13082,7 +12887,7 @@ function workspaceDefaultTarget(cwd, globalDefault) {
|
|
|
13082
12887
|
|
|
13083
12888
|
// packages/workit-core/src/core/release-tracks.ts
|
|
13084
12889
|
import { createHash as createHash8 } from "node:crypto";
|
|
13085
|
-
import { spawnSync as
|
|
12890
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
13086
12891
|
class ReleaseTrackError extends Error {
|
|
13087
12892
|
}
|
|
13088
12893
|
var KNOWN_FIELDS = new Set([
|
|
@@ -13101,7 +12906,7 @@ var KNOWN_FIELDS = new Set([
|
|
|
13101
12906
|
var BRANCH_FIELDS = ["productionBranch", "integrationBranch", "baseBranch", "pullRequestTarget"];
|
|
13102
12907
|
var PLACEHOLDER = /\{[^}]*\}/gu;
|
|
13103
12908
|
var HAS_PLACEHOLDER = /\{[^}]*\}/u;
|
|
13104
|
-
var
|
|
12909
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13105
12910
|
var branchName = (value) => typeof value === "string" && value.trim() && !value.trim().startsWith("-") ? value.trim() : null;
|
|
13106
12911
|
var namedBranches = (entry) => [
|
|
13107
12912
|
...BRANCH_FIELDS.map((field) => branchName(entry[field])),
|
|
@@ -13112,7 +12917,7 @@ function readReleaseTracks(raw) {
|
|
|
13112
12917
|
const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
|
|
13113
12918
|
if (raw === undefined || raw === null)
|
|
13114
12919
|
return out;
|
|
13115
|
-
if (!
|
|
12920
|
+
if (!isRecord(raw)) {
|
|
13116
12921
|
out.issues.push("releaseTracks is not an object; ignored");
|
|
13117
12922
|
return out;
|
|
13118
12923
|
}
|
|
@@ -13120,7 +12925,7 @@ function readReleaseTracks(raw) {
|
|
|
13120
12925
|
const protectedNames = new Set;
|
|
13121
12926
|
for (const [name, entry] of Object.entries(raw).toSorted(([a], [b]) => a < b ? -1 : 1)) {
|
|
13122
12927
|
const label = `release track ${JSON.stringify(name)}`;
|
|
13123
|
-
if (!
|
|
12928
|
+
if (!isRecord(entry)) {
|
|
13124
12929
|
out.issues.push(`${label} is not an object; ignored`);
|
|
13125
12930
|
continue;
|
|
13126
12931
|
}
|
|
@@ -13171,7 +12976,7 @@ function readReleaseTracks(raw) {
|
|
|
13171
12976
|
}
|
|
13172
12977
|
const naming = {};
|
|
13173
12978
|
if (entry.naming !== undefined) {
|
|
13174
|
-
if (!
|
|
12979
|
+
if (!isRecord(entry.naming))
|
|
13175
12980
|
problem("naming", "not an object; ignored");
|
|
13176
12981
|
else
|
|
13177
12982
|
for (const kind of ["feature", "release", "hotfix"]) {
|
|
@@ -13366,8 +13171,8 @@ function resolveReleaseTrack(input) {
|
|
|
13366
13171
|
const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
|
|
13367
13172
|
return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
|
|
13368
13173
|
}
|
|
13369
|
-
var
|
|
13370
|
-
const run =
|
|
13174
|
+
var git2 = (cwd, args) => {
|
|
13175
|
+
const run = spawnSync6("git", args, {
|
|
13371
13176
|
cwd,
|
|
13372
13177
|
encoding: "utf8",
|
|
13373
13178
|
timeout: 20000,
|
|
@@ -13378,7 +13183,7 @@ var git3 = (cwd, args) => {
|
|
|
13378
13183
|
};
|
|
13379
13184
|
var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
|
|
13380
13185
|
var escapeRegExp2 = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
13381
|
-
var reflog = (cwd, ref) => (
|
|
13186
|
+
var reflog = (cwd, ref) => (git2(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
|
|
13382
13187
|
`).map((line) => {
|
|
13383
13188
|
const [sha = "", selector = "", ...subject] = line.split("\t");
|
|
13384
13189
|
return {
|
|
@@ -13396,9 +13201,9 @@ function gitTrackProbe(cwd) {
|
|
|
13396
13201
|
};
|
|
13397
13202
|
return {
|
|
13398
13203
|
remotes: () => remotes ??= remoteNames(cwd),
|
|
13399
|
-
recordedBase: (branch) =>
|
|
13204
|
+
recordedBase: (branch) => git2(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
|
|
13400
13205
|
createdFrom: (branch) => {
|
|
13401
|
-
const log =
|
|
13206
|
+
const log = git2(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
|
|
13402
13207
|
const oldest = log?.split(`
|
|
13403
13208
|
`).findLast(Boolean) ?? "";
|
|
13404
13209
|
const match = /^branch: Created from (\S+)$/u.exec(oldest);
|
|
@@ -13417,7 +13222,7 @@ function gitTrackProbe(cwd) {
|
|
|
13417
13222
|
const base = tip(trackBranch);
|
|
13418
13223
|
return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
|
|
13419
13224
|
},
|
|
13420
|
-
isAncestor: (ancestor, descendant) =>
|
|
13225
|
+
isAncestor: (ancestor, descendant) => git2(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
|
|
13421
13226
|
};
|
|
13422
13227
|
}
|
|
13423
13228
|
function workspaceReleaseTracks(cwd) {
|
|
@@ -13444,15 +13249,15 @@ function resolveTrackFor(cwd, options) {
|
|
|
13444
13249
|
};
|
|
13445
13250
|
let key = null;
|
|
13446
13251
|
if (!options.probe) {
|
|
13447
|
-
const refs =
|
|
13252
|
+
const refs = git2(cwd, [
|
|
13448
13253
|
"for-each-ref",
|
|
13449
13254
|
"--format=%(refname) %(objectname)",
|
|
13450
13255
|
"refs/heads",
|
|
13451
13256
|
"refs/remotes"
|
|
13452
13257
|
]) ?? "";
|
|
13453
|
-
const head =
|
|
13258
|
+
const head = git2(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
|
|
13454
13259
|
const own = branch ? [
|
|
13455
|
-
|
|
13260
|
+
git2(cwd, [
|
|
13456
13261
|
"log",
|
|
13457
13262
|
"-g",
|
|
13458
13263
|
"-n",
|
|
@@ -13462,7 +13267,7 @@ function resolveTrackFor(cwd, options) {
|
|
|
13462
13267
|
`refs/heads/${branch}`,
|
|
13463
13268
|
"--"
|
|
13464
13269
|
]) ?? "",
|
|
13465
|
-
|
|
13270
|
+
git2(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
13466
13271
|
] : [];
|
|
13467
13272
|
key = createHash8("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
13468
13273
|
const hit = memo2.get(key);
|
|
@@ -13479,10 +13284,10 @@ function resolveTrackFor(cwd, options) {
|
|
|
13479
13284
|
}
|
|
13480
13285
|
|
|
13481
13286
|
// packages/workit-core/src/core/vcs-config.ts
|
|
13482
|
-
var workspacesPath = () =>
|
|
13287
|
+
var workspacesPath = () => path12.join(configDir(), "workspaces.json");
|
|
13483
13288
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
13484
13289
|
var remoteProvider = (cwd) => {
|
|
13485
|
-
const r =
|
|
13290
|
+
const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
13486
13291
|
if (r.status !== 0)
|
|
13487
13292
|
return null;
|
|
13488
13293
|
const url = (r.stdout ?? "").trim();
|
|
@@ -13496,7 +13301,7 @@ var readVcsConfig = () => {
|
|
|
13496
13301
|
const file = vcsConfigPath();
|
|
13497
13302
|
let raw;
|
|
13498
13303
|
try {
|
|
13499
|
-
raw =
|
|
13304
|
+
raw = fs10.readFileSync(file, "utf8");
|
|
13500
13305
|
} catch {
|
|
13501
13306
|
return { status: "missing", path: file, config: {} };
|
|
13502
13307
|
}
|
|
@@ -13621,7 +13426,7 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
13621
13426
|
const prov = cfg[provider] ?? {};
|
|
13622
13427
|
const out = {
|
|
13623
13428
|
ok: true,
|
|
13624
|
-
configPath:
|
|
13429
|
+
configPath: path12.resolve(cfgPath),
|
|
13625
13430
|
provider,
|
|
13626
13431
|
defaultTargetBranch: workspaceDefault,
|
|
13627
13432
|
pr: cfg.pr ?? {},
|
|
@@ -13662,7 +13467,7 @@ var err = (code, error, unblock) => ({
|
|
|
13662
13467
|
...unblock ? { unblock } : {}
|
|
13663
13468
|
});
|
|
13664
13469
|
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
13665
|
-
const run =
|
|
13470
|
+
const run = spawnSync8("git", args, {
|
|
13666
13471
|
cwd,
|
|
13667
13472
|
encoding: "utf8",
|
|
13668
13473
|
timeout: timeoutMs,
|
|
@@ -13683,7 +13488,7 @@ var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
|
13683
13488
|
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
13684
13489
|
};
|
|
13685
13490
|
};
|
|
13686
|
-
var
|
|
13491
|
+
var git3 = (cwd, args) => {
|
|
13687
13492
|
const run = gitRun2(cwd, args);
|
|
13688
13493
|
const value = run.ok ? run.stdout.trim() : "";
|
|
13689
13494
|
return value ? value : null;
|
|
@@ -13692,7 +13497,7 @@ var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
|
13692
13497
|
function resolveCommit(cwd, ref) {
|
|
13693
13498
|
if (!safeRef(ref))
|
|
13694
13499
|
return null;
|
|
13695
|
-
return
|
|
13500
|
+
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
13696
13501
|
}
|
|
13697
13502
|
function branchHead(cwd, branch) {
|
|
13698
13503
|
if (!safeRef(branch))
|
|
@@ -13717,7 +13522,7 @@ function defaultBase(cwd) {
|
|
|
13717
13522
|
if (resolveCommit(cwd, candidate))
|
|
13718
13523
|
return candidate;
|
|
13719
13524
|
}
|
|
13720
|
-
const originHead =
|
|
13525
|
+
const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
13721
13526
|
if (originHead && resolveCommit(cwd, originHead))
|
|
13722
13527
|
return originHead;
|
|
13723
13528
|
for (const candidate of [
|
|
@@ -13765,14 +13570,14 @@ function diffHash(cwd, base, head) {
|
|
|
13765
13570
|
return `sha256:${hash.digest("hex")}`;
|
|
13766
13571
|
}
|
|
13767
13572
|
function storeRoot(cwd) {
|
|
13768
|
-
const location = resolveStore(
|
|
13573
|
+
const location = resolveStore(path13.resolve(cwd));
|
|
13769
13574
|
if (location instanceof Error)
|
|
13770
13575
|
return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
|
|
13771
13576
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
13772
13577
|
}
|
|
13773
13578
|
function ledgerPath(cwd) {
|
|
13774
13579
|
const root = storeRoot(cwd);
|
|
13775
|
-
return root.ok ? { ok: true, value:
|
|
13580
|
+
return root.ok ? { ok: true, value: path13.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
13776
13581
|
}
|
|
13777
13582
|
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
13778
13583
|
var NETWORK_FS_TYPES = new Set([
|
|
@@ -13792,7 +13597,7 @@ var onNetworkFs = (dir) => {
|
|
|
13792
13597
|
if (process.platform !== "linux")
|
|
13793
13598
|
return false;
|
|
13794
13599
|
try {
|
|
13795
|
-
return isNetworkFsType(
|
|
13600
|
+
return isNetworkFsType(fs11.statfsSync(dir).type);
|
|
13796
13601
|
} catch {
|
|
13797
13602
|
return false;
|
|
13798
13603
|
}
|
|
@@ -13802,15 +13607,15 @@ var LOCK_STALE_MS = 30000;
|
|
|
13802
13607
|
var LOCK_OWNER = "owner";
|
|
13803
13608
|
var lockToken = (lock) => {
|
|
13804
13609
|
try {
|
|
13805
|
-
return
|
|
13610
|
+
return fs11.readFileSync(path13.join(lock, LOCK_OWNER), "utf8");
|
|
13806
13611
|
} catch {
|
|
13807
13612
|
return null;
|
|
13808
13613
|
}
|
|
13809
13614
|
};
|
|
13810
13615
|
var lockAgeMs = (lock) => {
|
|
13811
|
-
for (const target of [
|
|
13616
|
+
for (const target of [path13.join(lock, LOCK_OWNER), lock]) {
|
|
13812
13617
|
try {
|
|
13813
|
-
return Date.now() -
|
|
13618
|
+
return Date.now() - fs11.statSync(target).mtimeMs;
|
|
13814
13619
|
} catch {}
|
|
13815
13620
|
}
|
|
13816
13621
|
return null;
|
|
@@ -13818,14 +13623,14 @@ var lockAgeMs = (lock) => {
|
|
|
13818
13623
|
var ledgerLock = {
|
|
13819
13624
|
acquire(lock) {
|
|
13820
13625
|
try {
|
|
13821
|
-
|
|
13626
|
+
fs11.mkdirSync(lock);
|
|
13822
13627
|
} catch (error) {
|
|
13823
13628
|
if (error.code === "EEXIST")
|
|
13824
13629
|
return null;
|
|
13825
13630
|
throw error;
|
|
13826
13631
|
}
|
|
13827
13632
|
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
13828
|
-
|
|
13633
|
+
fs11.writeFileSync(path13.join(lock, LOCK_OWNER), token);
|
|
13829
13634
|
return token;
|
|
13830
13635
|
},
|
|
13831
13636
|
release(lock, token) {
|
|
@@ -13833,35 +13638,35 @@ var ledgerLock = {
|
|
|
13833
13638
|
return false;
|
|
13834
13639
|
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
13835
13640
|
try {
|
|
13836
|
-
|
|
13641
|
+
fs11.renameSync(lock, tomb);
|
|
13837
13642
|
} catch {
|
|
13838
13643
|
return false;
|
|
13839
13644
|
}
|
|
13840
13645
|
if (lockToken(tomb) !== token) {
|
|
13841
13646
|
try {
|
|
13842
|
-
|
|
13647
|
+
fs11.renameSync(tomb, lock);
|
|
13843
13648
|
} catch {}
|
|
13844
13649
|
return false;
|
|
13845
13650
|
}
|
|
13846
|
-
|
|
13651
|
+
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
13847
13652
|
return true;
|
|
13848
13653
|
},
|
|
13849
13654
|
token: lockToken,
|
|
13850
13655
|
reap(lock, observed) {
|
|
13851
13656
|
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
13852
13657
|
try {
|
|
13853
|
-
|
|
13658
|
+
fs11.renameSync(lock, tomb);
|
|
13854
13659
|
} catch {
|
|
13855
13660
|
return false;
|
|
13856
13661
|
}
|
|
13857
13662
|
if (lockToken(tomb) === observed) {
|
|
13858
|
-
|
|
13663
|
+
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
13859
13664
|
return true;
|
|
13860
13665
|
}
|
|
13861
13666
|
try {
|
|
13862
|
-
|
|
13667
|
+
fs11.renameSync(tomb, lock);
|
|
13863
13668
|
} catch {
|
|
13864
|
-
|
|
13669
|
+
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
13865
13670
|
}
|
|
13866
13671
|
return false;
|
|
13867
13672
|
}
|
|
@@ -13895,7 +13700,7 @@ var TAIL_BYTES = 64 * 1024;
|
|
|
13895
13700
|
var lastLine = (fd, size) => {
|
|
13896
13701
|
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
13897
13702
|
const buffer = Buffer.alloc(span);
|
|
13898
|
-
|
|
13703
|
+
fs11.readSync(fd, buffer, 0, span, size - span);
|
|
13899
13704
|
const end = buffer.lastIndexOf(10);
|
|
13900
13705
|
if (end < 0) {
|
|
13901
13706
|
if (span === size)
|
|
@@ -13926,21 +13731,21 @@ var lastLine = (fd, size) => {
|
|
|
13926
13731
|
};
|
|
13927
13732
|
var tighten = (fd) => {
|
|
13928
13733
|
try {
|
|
13929
|
-
if ((
|
|
13930
|
-
|
|
13734
|
+
if ((fs11.fstatSync(fd).mode & 63) !== 0)
|
|
13735
|
+
fs11.fchmodSync(fd, 384);
|
|
13931
13736
|
} catch {}
|
|
13932
13737
|
};
|
|
13933
13738
|
function appendLine(file, build, options) {
|
|
13934
13739
|
const write = () => {
|
|
13935
|
-
const fd =
|
|
13740
|
+
const fd = fs11.openSync(file, "a+", 384);
|
|
13936
13741
|
try {
|
|
13937
13742
|
if (process.platform !== "win32")
|
|
13938
13743
|
tighten(fd);
|
|
13939
|
-
const size =
|
|
13744
|
+
const size = fs11.fstatSync(fd).size;
|
|
13940
13745
|
let fence = "";
|
|
13941
13746
|
if (size > 0) {
|
|
13942
13747
|
const last = Buffer.alloc(1);
|
|
13943
|
-
|
|
13748
|
+
fs11.readSync(fd, last, 0, 1, size - 1);
|
|
13944
13749
|
if (last[0] !== 10)
|
|
13945
13750
|
fence = `
|
|
13946
13751
|
`;
|
|
@@ -13949,17 +13754,17 @@ function appendLine(file, build, options) {
|
|
|
13949
13754
|
if (!line.ok)
|
|
13950
13755
|
return line;
|
|
13951
13756
|
const bytes = Buffer.from(fence + line.value);
|
|
13952
|
-
const written =
|
|
13757
|
+
const written = fs11.writeSync(fd, bytes);
|
|
13953
13758
|
if (written !== bytes.length)
|
|
13954
13759
|
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
13955
13760
|
return { ok: true, value: undefined };
|
|
13956
13761
|
} finally {
|
|
13957
|
-
|
|
13762
|
+
fs11.closeSync(fd);
|
|
13958
13763
|
}
|
|
13959
13764
|
};
|
|
13960
13765
|
try {
|
|
13961
|
-
|
|
13962
|
-
if (!options.forceLock && !onNetworkFs(
|
|
13766
|
+
fs11.mkdirSync(path13.dirname(file), { recursive: true });
|
|
13767
|
+
if (!options.forceLock && !onNetworkFs(path13.dirname(file)))
|
|
13963
13768
|
return write();
|
|
13964
13769
|
const locked = withLedgerLock(file, write);
|
|
13965
13770
|
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
@@ -14002,10 +13807,10 @@ function appendObserved(cwd, row, options = {}) {
|
|
|
14002
13807
|
function appendHookObserved(cwd, row) {
|
|
14003
13808
|
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
14004
13809
|
}
|
|
14005
|
-
var
|
|
13810
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14006
13811
|
var str = (value) => typeof value === "string" ? value : null;
|
|
14007
13812
|
var normalizeActor = (value) => {
|
|
14008
|
-
const actor =
|
|
13813
|
+
const actor = isRecord2(value) ? value : {};
|
|
14009
13814
|
return {
|
|
14010
13815
|
host: str(actor.host) ?? "unknown",
|
|
14011
13816
|
session: str(actor.session),
|
|
@@ -14032,8 +13837,8 @@ function readLedger(cwd, options = {}) {
|
|
|
14032
13837
|
let text = "";
|
|
14033
13838
|
let stat = null;
|
|
14034
13839
|
try {
|
|
14035
|
-
stat =
|
|
14036
|
-
text =
|
|
13840
|
+
stat = fs11.statSync(file.value);
|
|
13841
|
+
text = fs11.readFileSync(file.value, "utf8");
|
|
14037
13842
|
} catch (error) {
|
|
14038
13843
|
if (error.code === "ENOENT")
|
|
14039
13844
|
return {
|
|
@@ -14068,7 +13873,7 @@ function readLedger(cwd, options = {}) {
|
|
|
14068
13873
|
skipped += 1;
|
|
14069
13874
|
continue;
|
|
14070
13875
|
}
|
|
14071
|
-
if (!
|
|
13876
|
+
if (!isRecord2(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
|
|
14072
13877
|
skipped += 1;
|
|
14073
13878
|
continue;
|
|
14074
13879
|
}
|
|
@@ -14168,7 +13973,7 @@ function ledgerTampering(cwd) {
|
|
|
14168
13973
|
if (!file.ok)
|
|
14169
13974
|
return [];
|
|
14170
13975
|
try {
|
|
14171
|
-
const stat =
|
|
13976
|
+
const stat = fs11.statSync(file.value);
|
|
14172
13977
|
const cached = tamperCache.get(file.value);
|
|
14173
13978
|
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
14174
13979
|
return cached.breaks;
|
|
@@ -14195,7 +14000,7 @@ var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.resul
|
|
|
14195
14000
|
function trailerSessions(cwd, base, head) {
|
|
14196
14001
|
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
14197
14002
|
return [];
|
|
14198
|
-
const out =
|
|
14003
|
+
const out = git3(cwd, [
|
|
14199
14004
|
"log",
|
|
14200
14005
|
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
14201
14006
|
`${base}..${head}`
|
|
@@ -14207,7 +14012,7 @@ function branchScope(cwd, branch) {
|
|
|
14207
14012
|
const renamedAt = new Map;
|
|
14208
14013
|
let since = Number.NEGATIVE_INFINITY;
|
|
14209
14014
|
if (safeRef(branch)) {
|
|
14210
|
-
const log =
|
|
14015
|
+
const log = git3(cwd, [
|
|
14211
14016
|
"log",
|
|
14212
14017
|
"-g",
|
|
14213
14018
|
"--date=unix",
|
|
@@ -14647,7 +14452,7 @@ var productDecision = (task, ledger) => {
|
|
|
14647
14452
|
};
|
|
14648
14453
|
};
|
|
14649
14454
|
var planRecorded = (task, root) => {
|
|
14650
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(
|
|
14455
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path14.resolve(root, ref.path)));
|
|
14651
14456
|
if (cited)
|
|
14652
14457
|
return {
|
|
14653
14458
|
met: true,
|
|
@@ -14707,14 +14512,14 @@ function writeBlockers(task, root) {
|
|
|
14707
14512
|
|
|
14708
14513
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14709
14514
|
var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
|
|
14710
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
14711
|
-
var relative = (root, candidate) =>
|
|
14515
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path15.sep}`);
|
|
14516
|
+
var relative = (root, candidate) => path15.relative(root, candidate).split(path15.sep).join("/") || ".";
|
|
14712
14517
|
var canonicalPath = (value) => {
|
|
14713
14518
|
if (typeof value !== "string" || !value)
|
|
14714
14519
|
return null;
|
|
14715
14520
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14716
14521
|
return null;
|
|
14717
|
-
const normalized =
|
|
14522
|
+
const normalized = path15.posix.normalize(value);
|
|
14718
14523
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14719
14524
|
return null;
|
|
14720
14525
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14750,7 +14555,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
14750
14555
|
return left.length - right.length;
|
|
14751
14556
|
};
|
|
14752
14557
|
var gitPaths = (root) => {
|
|
14753
|
-
const result =
|
|
14558
|
+
const result = spawnSync9("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
14754
14559
|
cwd: root,
|
|
14755
14560
|
encoding: "buffer"
|
|
14756
14561
|
});
|
|
@@ -14762,21 +14567,21 @@ var gitPaths = (root) => {
|
|
|
14762
14567
|
git: !/not a git repository/i.test(stderr)
|
|
14763
14568
|
};
|
|
14764
14569
|
}
|
|
14765
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14766
|
-
const stagedDeleted =
|
|
14570
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")) : [];
|
|
14571
|
+
const stagedDeleted = spawnSync9("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14767
14572
|
if (stagedDeleted.status !== 0) {
|
|
14768
14573
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14769
14574
|
if (!/not a git repository/i.test(stderr))
|
|
14770
14575
|
return { paths, uncertain: true, git: true };
|
|
14771
14576
|
} else if (stagedDeleted.stdout) {
|
|
14772
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14577
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")));
|
|
14773
14578
|
}
|
|
14774
14579
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14775
14580
|
};
|
|
14776
14581
|
var walk = (root, directory, output) => {
|
|
14777
14582
|
let names;
|
|
14778
14583
|
try {
|
|
14779
|
-
names =
|
|
14584
|
+
names = fs12.readdirSync(directory);
|
|
14780
14585
|
} catch {
|
|
14781
14586
|
return true;
|
|
14782
14587
|
}
|
|
@@ -14784,10 +14589,10 @@ var walk = (root, directory, output) => {
|
|
|
14784
14589
|
for (const name of names) {
|
|
14785
14590
|
if (name === ".git" || name === ".workit")
|
|
14786
14591
|
continue;
|
|
14787
|
-
const target =
|
|
14592
|
+
const target = path15.join(directory, name);
|
|
14788
14593
|
let stat;
|
|
14789
14594
|
try {
|
|
14790
|
-
stat =
|
|
14595
|
+
stat = fs12.lstatSync(target);
|
|
14791
14596
|
} catch {
|
|
14792
14597
|
uncertain = true;
|
|
14793
14598
|
continue;
|
|
@@ -14802,14 +14607,14 @@ var walk = (root, directory, output) => {
|
|
|
14802
14607
|
var scopeRoots = (root, scope) => {
|
|
14803
14608
|
const roots = [];
|
|
14804
14609
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14805
|
-
const target =
|
|
14610
|
+
const target = path15.resolve(root, item);
|
|
14806
14611
|
if (!inside(root, target))
|
|
14807
14612
|
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
14808
14613
|
let ancestor = target;
|
|
14809
|
-
while (!
|
|
14810
|
-
ancestor =
|
|
14614
|
+
while (!fs12.existsSync(ancestor) && ancestor !== root)
|
|
14615
|
+
ancestor = path15.dirname(ancestor);
|
|
14811
14616
|
try {
|
|
14812
|
-
if (!inside(root,
|
|
14617
|
+
if (!inside(root, fs12.realpathSync(ancestor)))
|
|
14813
14618
|
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
14814
14619
|
} catch {
|
|
14815
14620
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -14821,7 +14626,7 @@ var scopeRoots = (root, scope) => {
|
|
|
14821
14626
|
function captureCandidate(root, scope, environment = []) {
|
|
14822
14627
|
let checkout;
|
|
14823
14628
|
try {
|
|
14824
|
-
checkout =
|
|
14629
|
+
checkout = fs12.realpathSync(root);
|
|
14825
14630
|
} catch {
|
|
14826
14631
|
return failure2("invalid_input", "candidate root does not exist");
|
|
14827
14632
|
}
|
|
@@ -14839,11 +14644,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14839
14644
|
if (item === ".git" || item === ".workit")
|
|
14840
14645
|
continue;
|
|
14841
14646
|
names.add(item);
|
|
14842
|
-
if (!
|
|
14647
|
+
if (!fs12.existsSync(target))
|
|
14843
14648
|
continue;
|
|
14844
14649
|
let stat;
|
|
14845
14650
|
try {
|
|
14846
|
-
stat =
|
|
14651
|
+
stat = fs12.lstatSync(target);
|
|
14847
14652
|
} catch {
|
|
14848
14653
|
uncertain = true;
|
|
14849
14654
|
continue;
|
|
@@ -14859,17 +14664,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14859
14664
|
for (const item of [...names].toSorted()) {
|
|
14860
14665
|
if (!scopeMatches(item, normalizedScope))
|
|
14861
14666
|
continue;
|
|
14862
|
-
const target =
|
|
14667
|
+
const target = path15.join(checkout, item);
|
|
14863
14668
|
try {
|
|
14864
|
-
const stat =
|
|
14669
|
+
const stat = fs12.lstatSync(target);
|
|
14865
14670
|
if (stat.isSymbolicLink()) {
|
|
14866
|
-
const link =
|
|
14671
|
+
const link = fs12.readlinkSync(target);
|
|
14867
14672
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
14868
14673
|
} else if (stat.isFile()) {
|
|
14869
14674
|
files.push({
|
|
14870
14675
|
path: item,
|
|
14871
14676
|
kind: "file",
|
|
14872
|
-
digest: digestBytes(
|
|
14677
|
+
digest: digestBytes(fs12.readFileSync(target)),
|
|
14873
14678
|
executable: (stat.mode & 73) !== 0
|
|
14874
14679
|
});
|
|
14875
14680
|
} else if (!stat.isDirectory()) {
|
|
@@ -14894,7 +14699,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14894
14699
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
14895
14700
|
if (values.some(({ value }) => value === null))
|
|
14896
14701
|
completeness = "uncertain";
|
|
14897
|
-
const headResult =
|
|
14702
|
+
const headResult = spawnSync9("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
14898
14703
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
14899
14704
|
completeness = "uncertain";
|
|
14900
14705
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -15034,7 +14839,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
15034
14839
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
15035
14840
|
let root;
|
|
15036
14841
|
try {
|
|
15037
|
-
root =
|
|
14842
|
+
root = fs12.realpathSync(checkoutRoot);
|
|
15038
14843
|
} catch {
|
|
15039
14844
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
15040
14845
|
}
|
|
@@ -15043,23 +14848,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15043
14848
|
continue;
|
|
15044
14849
|
if (!reference.digest)
|
|
15045
14850
|
return failure2("invalid_input", "document references require a byte digest");
|
|
15046
|
-
const target =
|
|
15047
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
14851
|
+
const target = path15.resolve(root, reference.path);
|
|
14852
|
+
if (target !== root && !target.startsWith(`${root}${path15.sep}`))
|
|
15048
14853
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15049
14854
|
fields: [
|
|
15050
14855
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
15051
14856
|
]
|
|
15052
14857
|
});
|
|
15053
14858
|
try {
|
|
15054
|
-
const below =
|
|
14859
|
+
const below = path15.relative(root, target);
|
|
15055
14860
|
let current = root;
|
|
15056
|
-
for (const segment of below.split(
|
|
14861
|
+
for (const segment of below.split(path15.sep)) {
|
|
15057
14862
|
if (!segment)
|
|
15058
14863
|
continue;
|
|
15059
|
-
current =
|
|
15060
|
-
const stat =
|
|
15061
|
-
const real =
|
|
15062
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
14864
|
+
current = path15.join(current, segment);
|
|
14865
|
+
const stat = fs12.lstatSync(current);
|
|
14866
|
+
const real = fs12.realpathSync(current);
|
|
14867
|
+
if (real !== root && !real.startsWith(`${root}${path15.sep}`))
|
|
15063
14868
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15064
14869
|
fields: [
|
|
15065
14870
|
{
|
|
@@ -15075,7 +14880,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15075
14880
|
if (current !== target && !stat.isDirectory())
|
|
15076
14881
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
15077
14882
|
}
|
|
15078
|
-
if (digestBytes(
|
|
14883
|
+
if (digestBytes(fs12.readFileSync(target)) !== reference.digest)
|
|
15079
14884
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
15080
14885
|
} catch {
|
|
15081
14886
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -15370,6 +15175,54 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
15370
15175
|
requirementIds: evaluations.map((item) => item.requirementId)
|
|
15371
15176
|
});
|
|
15372
15177
|
}
|
|
15178
|
+
var BRIEF_TEXT = 250;
|
|
15179
|
+
var BRIEF_BLOCKERS = 3;
|
|
15180
|
+
var BRIEF_OPEN = 8;
|
|
15181
|
+
var clip = (value) => {
|
|
15182
|
+
const fits = (text) => Buffer.byteLength(JSON.stringify(text)) <= BRIEF_TEXT;
|
|
15183
|
+
if (fits(value))
|
|
15184
|
+
return value;
|
|
15185
|
+
let length = Math.min(value.length, BRIEF_TEXT);
|
|
15186
|
+
while (length > 0 && !fits(`${value.slice(0, length)}…`))
|
|
15187
|
+
length -= 1;
|
|
15188
|
+
return `${value.slice(0, length)}…`;
|
|
15189
|
+
};
|
|
15190
|
+
function boundTaskSummary(summary) {
|
|
15191
|
+
const rules = new Map((summary.policy?.requirements ?? []).map((item) => [item.id, item]));
|
|
15192
|
+
const unmet = summary.requirements.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
15193
|
+
const { progress, closure } = summary;
|
|
15194
|
+
const next = progress.nextAction;
|
|
15195
|
+
const open = unmet.slice(0, BRIEF_OPEN).map((item) => ({
|
|
15196
|
+
dimension: rules.get(item.requirementId)?.dimension ?? "verification",
|
|
15197
|
+
before: rules.get(item.requirementId)?.before ?? "close",
|
|
15198
|
+
status: item.status
|
|
15199
|
+
}));
|
|
15200
|
+
const brief = {
|
|
15201
|
+
id: summary.id,
|
|
15202
|
+
revision: summary.revision,
|
|
15203
|
+
workspaceRevision: summary.workspaceRevision,
|
|
15204
|
+
updatedAt: summary.updatedAt,
|
|
15205
|
+
objective: clip(summary.objective),
|
|
15206
|
+
status: summary.status,
|
|
15207
|
+
closure: closure && {
|
|
15208
|
+
outcome: closure.outcome,
|
|
15209
|
+
at: closure.at,
|
|
15210
|
+
summary: clip(closure.summary)
|
|
15211
|
+
},
|
|
15212
|
+
progress: {
|
|
15213
|
+
summary: clip(progress.summary),
|
|
15214
|
+
nextAction: next && clip(next),
|
|
15215
|
+
blockers: progress.blockers.slice(0, BRIEF_BLOCKERS).map((item) => ({
|
|
15216
|
+
reason: clip(item.reason),
|
|
15217
|
+
dependentAction: clip(item.dependentAction)
|
|
15218
|
+
}))
|
|
15219
|
+
},
|
|
15220
|
+
requirements: { total: summary.requirements.length, unmet: unmet.length, open },
|
|
15221
|
+
truncated: false
|
|
15222
|
+
};
|
|
15223
|
+
brief.truncated = JSON.stringify(brief).includes("…") || progress.blockers.length > BRIEF_BLOCKERS || unmet.length > BRIEF_OPEN;
|
|
15224
|
+
return brief;
|
|
15225
|
+
}
|
|
15373
15226
|
|
|
15374
15227
|
// packages/workit-core/src/core/task-engine.ts
|
|
15375
15228
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
@@ -15932,7 +15785,7 @@ var judgeInputSummary = (raw) => {
|
|
|
15932
15785
|
|
|
15933
15786
|
// packages/workit-core/src/autonomy.ts
|
|
15934
15787
|
import os4 from "node:os";
|
|
15935
|
-
import
|
|
15788
|
+
import path16 from "node:path";
|
|
15936
15789
|
var accountHome = () => {
|
|
15937
15790
|
try {
|
|
15938
15791
|
return os4.userInfo().homedir;
|
|
@@ -15941,10 +15794,10 @@ var accountHome = () => {
|
|
|
15941
15794
|
}
|
|
15942
15795
|
};
|
|
15943
15796
|
var grantsHome = { resolve: accountHome };
|
|
15944
|
-
var grantsDir = () =>
|
|
15797
|
+
var grantsDir = () => path16.join(grantsHome.resolve(), ".config", "workit");
|
|
15945
15798
|
var grantsOverride = () => {
|
|
15946
|
-
const configured =
|
|
15947
|
-
return configured ===
|
|
15799
|
+
const configured = path16.resolve(resolveConfigDir());
|
|
15800
|
+
return configured === path16.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path16.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
15948
15801
|
};
|
|
15949
15802
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
15950
15803
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -16756,6 +16609,15 @@ class WorkitCore {
|
|
|
16756
16609
|
if (!parsed.ok)
|
|
16757
16610
|
return parsed;
|
|
16758
16611
|
const input = parsed.data;
|
|
16612
|
+
if (this.context.readOnlyChild) {
|
|
16613
|
+
if (input.action !== "inspect")
|
|
16614
|
+
return failure2("permission_denied", "child sessions can only inspect the task summary");
|
|
16615
|
+
const task = this.store.readTask(input.taskId);
|
|
16616
|
+
if (!task.ok)
|
|
16617
|
+
return task;
|
|
16618
|
+
const summary = this.summary(task.data);
|
|
16619
|
+
return summary.ok ? { ...summary, data: boundTaskSummary(summary.data) } : summary;
|
|
16620
|
+
}
|
|
16759
16621
|
if ((this.context.workerId ?? null) !== null) {
|
|
16760
16622
|
if (input.action !== "inspect" && input.action !== "list")
|
|
16761
16623
|
return failure2("permission_denied", "helpers cannot control task lifecycle or scope");
|
|
@@ -17948,7 +17810,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
17948
17810
|
return null;
|
|
17949
17811
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
17950
17812
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
17951
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
17813
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path17.resolve(store.root, ref.path)] : []));
|
|
17952
17814
|
const observedFiles = filesKey(files);
|
|
17953
17815
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
17954
17816
|
if (!compact.ok || !compact.data)
|
|
@@ -18026,32 +17888,32 @@ var turnMarker = (input) => {
|
|
|
18026
17888
|
if (location instanceof Error)
|
|
18027
17889
|
return null;
|
|
18028
17890
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
18029
|
-
return
|
|
17891
|
+
return path17.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
18030
17892
|
} catch {
|
|
18031
17893
|
return null;
|
|
18032
17894
|
}
|
|
18033
17895
|
};
|
|
18034
17896
|
var writeTurnDigest = (file, digest) => {
|
|
18035
17897
|
try {
|
|
18036
|
-
|
|
18037
|
-
|
|
17898
|
+
fs13.mkdirSync(path17.dirname(file), { recursive: true });
|
|
17899
|
+
fs13.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
18038
17900
|
`);
|
|
18039
17901
|
} catch {}
|
|
18040
17902
|
};
|
|
18041
17903
|
var clearTurnDigest = (file) => {
|
|
18042
17904
|
try {
|
|
18043
|
-
|
|
17905
|
+
fs13.rmSync(file, { force: true });
|
|
18044
17906
|
} catch {}
|
|
18045
17907
|
};
|
|
18046
17908
|
var pruneTurnDigests = (dir, now) => {
|
|
18047
17909
|
try {
|
|
18048
|
-
for (const name of
|
|
17910
|
+
for (const name of fs13.readdirSync(dir)) {
|
|
18049
17911
|
if (!name.startsWith("turn-"))
|
|
18050
17912
|
continue;
|
|
18051
|
-
const file =
|
|
17913
|
+
const file = path17.join(dir, name);
|
|
18052
17914
|
try {
|
|
18053
|
-
if (now -
|
|
18054
|
-
|
|
17915
|
+
if (now - fs13.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
17916
|
+
fs13.rmSync(file, { force: true });
|
|
18055
17917
|
} catch {}
|
|
18056
17918
|
}
|
|
18057
17919
|
} catch {}
|
|
@@ -18067,7 +17929,7 @@ var changedTurnContext = (input, text) => {
|
|
|
18067
17929
|
return text;
|
|
18068
17930
|
const digest = digestOf(text);
|
|
18069
17931
|
try {
|
|
18070
|
-
const previous = JSON.parse(
|
|
17932
|
+
const previous = JSON.parse(fs13.readFileSync(file, "utf8"));
|
|
18071
17933
|
if (previous.digest === digest)
|
|
18072
17934
|
return null;
|
|
18073
17935
|
} catch {}
|
|
@@ -18078,23 +17940,23 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
18078
17940
|
const file = turnMarker(input);
|
|
18079
17941
|
if (!file)
|
|
18080
17942
|
return;
|
|
18081
|
-
pruneTurnDigests(
|
|
17943
|
+
pruneTurnDigests(path17.dirname(file), Date.now());
|
|
18082
17944
|
if (text)
|
|
18083
17945
|
writeTurnDigest(file, digestOf(text));
|
|
18084
17946
|
else
|
|
18085
17947
|
clearTurnDigest(file);
|
|
18086
17948
|
};
|
|
18087
17949
|
// packages/workit-core/src/core/route-intent.ts
|
|
18088
|
-
import { spawnSync as
|
|
17950
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
18089
17951
|
import { existsSync as existsSync6 } from "node:fs";
|
|
18090
|
-
import
|
|
17952
|
+
import path20 from "node:path";
|
|
18091
17953
|
|
|
18092
17954
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
18093
|
-
import { spawnSync as
|
|
17955
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
18094
17956
|
import { createHash as createHash14 } from "node:crypto";
|
|
18095
|
-
import
|
|
17957
|
+
import fs15 from "node:fs";
|
|
18096
17958
|
import os5 from "node:os";
|
|
18097
|
-
import
|
|
17959
|
+
import path19 from "node:path";
|
|
18098
17960
|
|
|
18099
17961
|
// packages/workit-core/src/core/branch.ts
|
|
18100
17962
|
import { createHash as createHash12 } from "node:crypto";
|
|
@@ -18306,8 +18168,8 @@ var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*
|
|
|
18306
18168
|
|
|
18307
18169
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
18308
18170
|
import { createHash as createHash13 } from "node:crypto";
|
|
18309
|
-
import
|
|
18310
|
-
import
|
|
18171
|
+
import fs14 from "node:fs";
|
|
18172
|
+
import path18 from "node:path";
|
|
18311
18173
|
var sessionMarkerFile = (input, session, store) => {
|
|
18312
18174
|
let dir = store;
|
|
18313
18175
|
if (dir === undefined) {
|
|
@@ -18317,12 +18179,12 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
18317
18179
|
dir = location.dir;
|
|
18318
18180
|
}
|
|
18319
18181
|
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
18320
|
-
return
|
|
18182
|
+
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
18321
18183
|
};
|
|
18322
18184
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
18323
18185
|
var readSessionMarker = (file) => {
|
|
18324
18186
|
try {
|
|
18325
|
-
const value = JSON.parse(
|
|
18187
|
+
const value = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
18326
18188
|
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
18327
18189
|
} catch {
|
|
18328
18190
|
return { nudged: [], loaded: [], raw: [] };
|
|
@@ -18330,8 +18192,8 @@ var readSessionMarker = (file) => {
|
|
|
18330
18192
|
};
|
|
18331
18193
|
var writeSessionMarker = (file, marker) => {
|
|
18332
18194
|
try {
|
|
18333
|
-
|
|
18334
|
-
|
|
18195
|
+
fs14.mkdirSync(path18.dirname(file), { recursive: true });
|
|
18196
|
+
fs14.writeFileSync(file, JSON.stringify(marker));
|
|
18335
18197
|
} catch {}
|
|
18336
18198
|
};
|
|
18337
18199
|
|
|
@@ -18828,14 +18690,14 @@ var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
|
18828
18690
|
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
18829
18691
|
var MAX_DEPTH = 4;
|
|
18830
18692
|
var MAX_EXPANSIONS = 16;
|
|
18831
|
-
var isAbsolute = (value) =>
|
|
18693
|
+
var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
18832
18694
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
18833
18695
|
var joinDir = (base, raw) => {
|
|
18834
18696
|
const next = raw.replace(HERE, ".");
|
|
18835
18697
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18836
18698
|
return;
|
|
18837
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
18838
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
18699
|
+
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os5.homedir(), next.slice(1)) : next;
|
|
18700
|
+
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
18839
18701
|
};
|
|
18840
18702
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
18841
18703
|
var cdTarget = (words) => {
|
|
@@ -19173,7 +19035,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
19173
19035
|
return found;
|
|
19174
19036
|
}
|
|
19175
19037
|
var RECENT = new Map;
|
|
19176
|
-
function
|
|
19038
|
+
function commandLines(command, dialect = "posix") {
|
|
19039
|
+
const lines = [];
|
|
19040
|
+
try {
|
|
19041
|
+
walk2(command, dialect, 0, lines);
|
|
19042
|
+
} catch (error) {
|
|
19043
|
+
throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
|
|
19044
|
+
}
|
|
19045
|
+
return lines;
|
|
19046
|
+
}
|
|
19047
|
+
function walk2(command, dialect, depth, seen) {
|
|
19177
19048
|
const found = [];
|
|
19178
19049
|
let dir = null;
|
|
19179
19050
|
const groups = [];
|
|
@@ -19184,7 +19055,8 @@ function walk2(command, dialect, depth) {
|
|
|
19184
19055
|
const nested = (script, inner) => {
|
|
19185
19056
|
if (!script || depth >= MAX_DEPTH)
|
|
19186
19057
|
return;
|
|
19187
|
-
|
|
19058
|
+
const inside = seen ? walk2(script, inner, depth + 1, seen) : rawInvocations(script, inner, depth + 1);
|
|
19059
|
+
for (const invocation of inside)
|
|
19188
19060
|
found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
|
|
19189
19061
|
};
|
|
19190
19062
|
const visit = (input, stdin, hops) => {
|
|
@@ -19196,11 +19068,17 @@ function walk2(command, dialect, depth) {
|
|
|
19196
19068
|
const name = commandName(words[0]);
|
|
19197
19069
|
if (!name)
|
|
19198
19070
|
return;
|
|
19071
|
+
seen?.push(words);
|
|
19072
|
+
if (name === "-c" || name === "--command")
|
|
19073
|
+
return nested(words[1], "posix");
|
|
19199
19074
|
const runs = (lines) => {
|
|
19200
19075
|
if (hops < MAX_DEPTH)
|
|
19201
19076
|
for (const next of lines)
|
|
19202
19077
|
visit(next, undefined, hops + 1);
|
|
19203
19078
|
};
|
|
19079
|
+
const wrapped = seen ? null : workitCheckTail(words);
|
|
19080
|
+
if (wrapped)
|
|
19081
|
+
return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
|
|
19204
19082
|
if (CD.has(name)) {
|
|
19205
19083
|
const target = cdTarget(words);
|
|
19206
19084
|
dir = target === null ? os5.homedir() : joinDir(dir, target);
|
|
@@ -19291,6 +19169,51 @@ var LAUNCHERS = new Set([
|
|
|
19291
19169
|
"command"
|
|
19292
19170
|
]);
|
|
19293
19171
|
var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
19172
|
+
var WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
|
|
19173
|
+
var PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
|
|
19174
|
+
function workitCheckTail(words) {
|
|
19175
|
+
let at = 0;
|
|
19176
|
+
if (PACKAGE_RUNNERS.has(commandName(words[0]))) {
|
|
19177
|
+
at = words.findIndex((word, index) => index > 0 && !word.startsWith("-"));
|
|
19178
|
+
if (at < 0 || !WORKIT_PACKAGE.test(words[at]))
|
|
19179
|
+
return null;
|
|
19180
|
+
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
19181
|
+
return null;
|
|
19182
|
+
const args = words.slice(at + 1);
|
|
19183
|
+
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
19184
|
+
return null;
|
|
19185
|
+
const split = args.indexOf("--");
|
|
19186
|
+
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
19187
|
+
if (!tail.length)
|
|
19188
|
+
return null;
|
|
19189
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
19190
|
+
}
|
|
19191
|
+
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
19192
|
+
let segments;
|
|
19193
|
+
try {
|
|
19194
|
+
segments = segmentsOf(command, dialect);
|
|
19195
|
+
} catch {
|
|
19196
|
+
return null;
|
|
19197
|
+
}
|
|
19198
|
+
if (segments.length !== 1)
|
|
19199
|
+
return null;
|
|
19200
|
+
const [segment] = segments;
|
|
19201
|
+
if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
|
|
19202
|
+
return null;
|
|
19203
|
+
const { words } = stripPrefixes(segment.words);
|
|
19204
|
+
const name = commandName(words[0]);
|
|
19205
|
+
if (!name || depth >= MAX_DEPTH)
|
|
19206
|
+
return null;
|
|
19207
|
+
if (SHELLS.has(name)) {
|
|
19208
|
+
const script = shellScript(words, undefined);
|
|
19209
|
+
return script ? simpleCommand(script, "posix", depth + 1) : null;
|
|
19210
|
+
}
|
|
19211
|
+
if (name === "-c" || name === "--command")
|
|
19212
|
+
return words[1] ? simpleCommand(words[1], "posix", depth + 1) : null;
|
|
19213
|
+
if (EVALS.has(name) || POWERSHELLS.has(name) || ["xargs", "find", "parallel", "script", "cmd", "source", "."].includes(name))
|
|
19214
|
+
return null;
|
|
19215
|
+
return words;
|
|
19216
|
+
}
|
|
19294
19217
|
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
19295
19218
|
const found = [];
|
|
19296
19219
|
for (const { words } of segmentsOf(command, dialect)) {
|
|
@@ -19586,7 +19509,7 @@ var repoAt = (dir) => {
|
|
|
19586
19509
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
19587
19510
|
};
|
|
19588
19511
|
var isWorkitWorkspace = (repo) => {
|
|
19589
|
-
if (
|
|
19512
|
+
if (fs15.existsSync(repo.location.dir))
|
|
19590
19513
|
return true;
|
|
19591
19514
|
try {
|
|
19592
19515
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -19607,17 +19530,17 @@ var headSha2 = (repo) => {
|
|
|
19607
19530
|
if (!git || git.reftable)
|
|
19608
19531
|
return null;
|
|
19609
19532
|
try {
|
|
19610
|
-
const head =
|
|
19533
|
+
const head = fs15.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
19611
19534
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
19612
19535
|
return head;
|
|
19613
19536
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
19614
19537
|
if (!ref)
|
|
19615
19538
|
return null;
|
|
19616
|
-
const common =
|
|
19539
|
+
const common = path19.dirname(repo.location.dir);
|
|
19617
19540
|
try {
|
|
19618
|
-
return
|
|
19541
|
+
return fs15.readFileSync(path19.join(common, ref), "utf8").trim() || null;
|
|
19619
19542
|
} catch {}
|
|
19620
|
-
const packed =
|
|
19543
|
+
const packed = fs15.readFileSync(path19.join(common, "packed-refs"), "utf8");
|
|
19621
19544
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
19622
19545
|
} catch {
|
|
19623
19546
|
return null;
|
|
@@ -19629,7 +19552,7 @@ var gitConfig = (dir, invocation, cache) => {
|
|
|
19629
19552
|
if (cached)
|
|
19630
19553
|
return cached;
|
|
19631
19554
|
const config = new Map;
|
|
19632
|
-
const listed =
|
|
19555
|
+
const listed = spawnSync10("git", [...invocation.config ?? [], "config", "-z", "--list"], {
|
|
19633
19556
|
cwd: dir,
|
|
19634
19557
|
encoding: "utf8",
|
|
19635
19558
|
timeout: 3000,
|
|
@@ -19705,8 +19628,8 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
19705
19628
|
const sub = invocation.args[0];
|
|
19706
19629
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
19707
19630
|
return [invocation];
|
|
19708
|
-
const dir =
|
|
19709
|
-
if (!
|
|
19631
|
+
const dir = path19.resolve(root, invocation.dir ?? ".");
|
|
19632
|
+
if (!fs15.existsSync(dir))
|
|
19710
19633
|
return [invocation];
|
|
19711
19634
|
const config = gitConfig(dir, invocation, cache);
|
|
19712
19635
|
const alias = configValue(config, `alias.${sub.toLowerCase()}`);
|
|
@@ -19728,11 +19651,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
19728
19651
|
var normalizedUrl = (url, dir) => {
|
|
19729
19652
|
const trimmed = url.trim();
|
|
19730
19653
|
if (/^file:\/\//i.test(trimmed))
|
|
19731
|
-
return
|
|
19654
|
+
return path19.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
19732
19655
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
19733
19656
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
19734
19657
|
if (!scheme && !scp)
|
|
19735
|
-
return
|
|
19658
|
+
return path19.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
19736
19659
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
19737
19660
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
19738
19661
|
};
|
|
@@ -19793,7 +19716,7 @@ var refusal = (action, branch, dir) => {
|
|
|
19793
19716
|
};
|
|
19794
19717
|
var refFacts = (repo, remote) => {
|
|
19795
19718
|
const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
|
|
19796
|
-
const listed =
|
|
19719
|
+
const listed = spawnSync10("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
|
|
19797
19720
|
const remotePrefix = `refs/remotes/${remote}/`;
|
|
19798
19721
|
for (const ref of (listed.stdout ?? "").split(`
|
|
19799
19722
|
`)) {
|
|
@@ -19810,7 +19733,7 @@ var globBranches = (repo, glob) => {
|
|
|
19810
19733
|
const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
|
|
19811
19734
|
const [sourcePrefix, sourceSuffix = ""] = source.split("*");
|
|
19812
19735
|
const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
|
|
19813
|
-
const listed =
|
|
19736
|
+
const listed = spawnSync10("git", [
|
|
19814
19737
|
"for-each-ref",
|
|
19815
19738
|
"--format=%(refname)",
|
|
19816
19739
|
sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
|
|
@@ -19887,7 +19810,7 @@ var tagRefusal = (action, facts, dir) => {
|
|
|
19887
19810
|
return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (${shape} tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
|
|
19888
19811
|
};
|
|
19889
19812
|
var remoteHeads = (repo, remote) => {
|
|
19890
|
-
const listed =
|
|
19813
|
+
const listed = spawnSync10("git", ["ls-remote", "--heads", remote], {
|
|
19891
19814
|
cwd: repo.dir,
|
|
19892
19815
|
encoding: "utf8",
|
|
19893
19816
|
timeout: 3000,
|
|
@@ -19956,7 +19879,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
19956
19879
|
const out = [];
|
|
19957
19880
|
const repos = new Map;
|
|
19958
19881
|
const repoFor = (relative) => {
|
|
19959
|
-
const dir =
|
|
19882
|
+
const dir = path19.resolve(cwd, relative ?? ".");
|
|
19960
19883
|
if (!repos.has(dir)) {
|
|
19961
19884
|
const repo = repoAt(dir);
|
|
19962
19885
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -20042,7 +19965,7 @@ var analysisDenial = (error) => ({
|
|
|
20042
19965
|
});
|
|
20043
19966
|
var NEXT_COMMAND = "next";
|
|
20044
19967
|
var MARKER_TTL_SECONDS = 3600;
|
|
20045
|
-
var markerFile = (repo, session, key) =>
|
|
19968
|
+
var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
20046
19969
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
20047
19970
|
function noteRawCommit(input, command, key) {
|
|
20048
19971
|
try {
|
|
@@ -20053,14 +19976,14 @@ function noteRawCommit(input, command, key) {
|
|
|
20053
19976
|
if (action.kind !== "commit")
|
|
20054
19977
|
continue;
|
|
20055
19978
|
const file = markerFile(repo, input.session.id, key);
|
|
20056
|
-
|
|
20057
|
-
|
|
19979
|
+
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
19980
|
+
fs15.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
20058
19981
|
`);
|
|
20059
19982
|
}
|
|
20060
19983
|
} catch {}
|
|
20061
19984
|
}
|
|
20062
19985
|
var headCommit = (dir) => {
|
|
20063
|
-
const run =
|
|
19986
|
+
const run = spawnSync10("git", [
|
|
20064
19987
|
"log",
|
|
20065
19988
|
"-1",
|
|
20066
19989
|
"--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
|
|
@@ -20077,11 +20000,11 @@ var headCommit = (dir) => {
|
|
|
20077
20000
|
var settle2 = (input, repo, file) => {
|
|
20078
20001
|
let marker;
|
|
20079
20002
|
try {
|
|
20080
|
-
marker = JSON.parse(
|
|
20003
|
+
marker = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
20081
20004
|
} catch {
|
|
20082
20005
|
return;
|
|
20083
20006
|
}
|
|
20084
|
-
|
|
20007
|
+
fs15.rmSync(file, { force: true });
|
|
20085
20008
|
const session = input.session.id;
|
|
20086
20009
|
const now = Date.now() / 1000;
|
|
20087
20010
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -20130,7 +20053,7 @@ function settlePendingCommit(input) {
|
|
|
20130
20053
|
if (!repo)
|
|
20131
20054
|
return;
|
|
20132
20055
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
20133
|
-
if (
|
|
20056
|
+
if (fs15.existsSync(file))
|
|
20134
20057
|
settle2(input, repo, file);
|
|
20135
20058
|
} catch {}
|
|
20136
20059
|
}
|
|
@@ -20274,7 +20197,7 @@ var targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.
|
|
|
20274
20197
|
branch,
|
|
20275
20198
|
dir: invocation.dir
|
|
20276
20199
|
})) : []);
|
|
20277
|
-
var localBranchExists = (dir, branch) =>
|
|
20200
|
+
var localBranchExists = (dir, branch) => spawnSync11("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
20278
20201
|
cwd: dir,
|
|
20279
20202
|
stdio: "ignore",
|
|
20280
20203
|
timeout: 5000
|
|
@@ -20284,7 +20207,7 @@ var judgedDir = (root, dir) => {
|
|
|
20284
20207
|
return null;
|
|
20285
20208
|
if (dir === null)
|
|
20286
20209
|
return root;
|
|
20287
|
-
const resolved =
|
|
20210
|
+
const resolved = path20.resolve(root, dir);
|
|
20288
20211
|
return existsSync6(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
20289
20212
|
};
|
|
20290
20213
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
@@ -20318,22 +20241,771 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
20318
20241
|
}
|
|
20319
20242
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
20320
20243
|
};
|
|
20244
|
+
// packages/workit-core/src/core/heavy-rules.ts
|
|
20245
|
+
import fs16 from "node:fs";
|
|
20246
|
+
import os6 from "node:os";
|
|
20247
|
+
import path21 from "node:path";
|
|
20248
|
+
var EMPTY = () => ({ deny: [], allow: [] });
|
|
20249
|
+
var readText2 = (file) => {
|
|
20250
|
+
try {
|
|
20251
|
+
return fs16.readFileSync(file, "utf8");
|
|
20252
|
+
} catch (error) {
|
|
20253
|
+
if (error.code === "ENOENT")
|
|
20254
|
+
return null;
|
|
20255
|
+
throw error;
|
|
20256
|
+
}
|
|
20257
|
+
};
|
|
20258
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20259
|
+
function parseJsonc(text) {
|
|
20260
|
+
let out = "";
|
|
20261
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
20262
|
+
const char = text[index];
|
|
20263
|
+
if (char === '"') {
|
|
20264
|
+
const end = /"(?:[^"\\]|\\.)*"/y;
|
|
20265
|
+
end.lastIndex = index;
|
|
20266
|
+
const match = end.exec(text);
|
|
20267
|
+
if (!match)
|
|
20268
|
+
throw new Error("unterminated string");
|
|
20269
|
+
out += match[0];
|
|
20270
|
+
index += match[0].length - 1;
|
|
20271
|
+
} else if (char === "/" && text[index + 1] === "/") {
|
|
20272
|
+
while (index < text.length && text[index] !== `
|
|
20273
|
+
`)
|
|
20274
|
+
index += 1;
|
|
20275
|
+
out += `
|
|
20276
|
+
`;
|
|
20277
|
+
} else if (char === "/" && text[index + 1] === "*") {
|
|
20278
|
+
const close = text.indexOf("*/", index + 2);
|
|
20279
|
+
if (close < 0)
|
|
20280
|
+
throw new Error("unterminated comment");
|
|
20281
|
+
index = close + 1;
|
|
20282
|
+
} else
|
|
20283
|
+
out += char;
|
|
20284
|
+
}
|
|
20285
|
+
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
20286
|
+
}
|
|
20287
|
+
function gitTop(cwd) {
|
|
20288
|
+
for (let dir = path21.resolve(cwd);; dir = path21.dirname(dir)) {
|
|
20289
|
+
if (fs16.existsSync(path21.join(dir, ".git")))
|
|
20290
|
+
return dir;
|
|
20291
|
+
if (path21.dirname(dir) === dir)
|
|
20292
|
+
return null;
|
|
20293
|
+
}
|
|
20294
|
+
}
|
|
20295
|
+
var projectDirs = (cwd) => {
|
|
20296
|
+
const top = gitTop(cwd);
|
|
20297
|
+
const dirs = [];
|
|
20298
|
+
for (let dir = path21.resolve(cwd);; dir = path21.dirname(dir)) {
|
|
20299
|
+
dirs.push(dir);
|
|
20300
|
+
if (!top || dir === top || path21.dirname(dir) === dir)
|
|
20301
|
+
return dirs;
|
|
20302
|
+
}
|
|
20303
|
+
};
|
|
20304
|
+
var homeOf = (env) => env.HOME || os6.homedir();
|
|
20305
|
+
var CLAUDE_MANAGED = {
|
|
20306
|
+
linux: "/etc/claude-code/managed-settings.json",
|
|
20307
|
+
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
20308
|
+
win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
|
|
20309
|
+
};
|
|
20310
|
+
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
20311
|
+
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
20312
|
+
const user = env.CLAUDE_CONFIG_DIR || path21.join(homeOf(env), ".claude");
|
|
20313
|
+
return [
|
|
20314
|
+
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
20315
|
+
path21.join(user, "settings.json"),
|
|
20316
|
+
path21.join(project, ".claude", "settings.json"),
|
|
20317
|
+
path21.join(project, ".claude", "settings.local.json")
|
|
20318
|
+
];
|
|
20319
|
+
}
|
|
20320
|
+
var bashPattern = (entry) => {
|
|
20321
|
+
const match = /^Bash(?:\((.*)\))?$/su.exec(entry.trim());
|
|
20322
|
+
return match ? match[1] ?? "" : null;
|
|
20323
|
+
};
|
|
20324
|
+
function claudeRules(cwd, env = process.env) {
|
|
20325
|
+
const rules = EMPTY();
|
|
20326
|
+
try {
|
|
20327
|
+
for (const file of claudeSettingsFiles(cwd, env)) {
|
|
20328
|
+
const text = readText2(file);
|
|
20329
|
+
if (text === null)
|
|
20330
|
+
continue;
|
|
20331
|
+
const value = JSON.parse(text);
|
|
20332
|
+
if (!isRecord3(value))
|
|
20333
|
+
return "unreadable";
|
|
20334
|
+
const permissions = value.permissions;
|
|
20335
|
+
if (permissions === undefined)
|
|
20336
|
+
continue;
|
|
20337
|
+
if (!isRecord3(permissions))
|
|
20338
|
+
return "unreadable";
|
|
20339
|
+
for (const [key, bucket] of [
|
|
20340
|
+
["deny", rules.deny],
|
|
20341
|
+
["ask", rules.deny],
|
|
20342
|
+
["allow", rules.allow]
|
|
20343
|
+
]) {
|
|
20344
|
+
const list = permissions[key];
|
|
20345
|
+
if (list === undefined)
|
|
20346
|
+
continue;
|
|
20347
|
+
if (!Array.isArray(list))
|
|
20348
|
+
return "unreadable";
|
|
20349
|
+
for (const entry of list) {
|
|
20350
|
+
if (typeof entry !== "string")
|
|
20351
|
+
return "unreadable";
|
|
20352
|
+
const pattern = bashPattern(entry);
|
|
20353
|
+
if (pattern !== null)
|
|
20354
|
+
bucket.push(pattern);
|
|
20355
|
+
}
|
|
20356
|
+
}
|
|
20357
|
+
}
|
|
20358
|
+
} catch {
|
|
20359
|
+
return "unreadable";
|
|
20360
|
+
}
|
|
20361
|
+
return rules;
|
|
20362
|
+
}
|
|
20363
|
+
var OPENCODE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
20364
|
+
var opencodePermission = (permission, rules) => {
|
|
20365
|
+
if (permission === undefined)
|
|
20366
|
+
return true;
|
|
20367
|
+
if (typeof permission === "string") {
|
|
20368
|
+
(permission === "allow" ? rules.allow : rules.deny).push("");
|
|
20369
|
+
return ["allow", "ask", "deny"].includes(permission);
|
|
20370
|
+
}
|
|
20371
|
+
if (!isRecord3(permission))
|
|
20372
|
+
return false;
|
|
20373
|
+
for (const key of ["bash", "shell"]) {
|
|
20374
|
+
const value = permission[key];
|
|
20375
|
+
if (value === undefined)
|
|
20376
|
+
continue;
|
|
20377
|
+
if (typeof value === "string") {
|
|
20378
|
+
if (!["allow", "ask", "deny"].includes(value))
|
|
20379
|
+
return false;
|
|
20380
|
+
(value === "allow" ? rules.allow : rules.deny).push("");
|
|
20381
|
+
continue;
|
|
20382
|
+
}
|
|
20383
|
+
if (!isRecord3(value))
|
|
20384
|
+
return false;
|
|
20385
|
+
for (const [pattern, action] of Object.entries(value)) {
|
|
20386
|
+
if (action !== "allow" && action !== "ask" && action !== "deny")
|
|
20387
|
+
return false;
|
|
20388
|
+
(action === "allow" ? rules.allow : rules.deny).push(pattern);
|
|
20389
|
+
}
|
|
20390
|
+
}
|
|
20391
|
+
return true;
|
|
20392
|
+
};
|
|
20393
|
+
var opencodeConfig = (value, rules) => {
|
|
20394
|
+
if (!isRecord3(value))
|
|
20395
|
+
return false;
|
|
20396
|
+
if (!opencodePermission(value.permission, rules))
|
|
20397
|
+
return false;
|
|
20398
|
+
if (value.agent === undefined)
|
|
20399
|
+
return true;
|
|
20400
|
+
if (!isRecord3(value.agent))
|
|
20401
|
+
return false;
|
|
20402
|
+
return Object.values(value.agent).every((agent) => isRecord3(agent) && opencodePermission(agent.permission, rules));
|
|
20403
|
+
};
|
|
20404
|
+
function opencodeRules(cwd, env = process.env) {
|
|
20405
|
+
const rules = EMPTY();
|
|
20406
|
+
try {
|
|
20407
|
+
const config = env.XDG_CONFIG_HOME || path21.join(homeOf(env), ".config");
|
|
20408
|
+
const files = [
|
|
20409
|
+
...OPENCODE_NAMES.map((name) => path21.join(config, "opencode", name)),
|
|
20410
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path21.join(dir, name), path21.join(dir, ".opencode", name)])),
|
|
20411
|
+
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
20412
|
+
];
|
|
20413
|
+
for (const file of files) {
|
|
20414
|
+
const text = readText2(file);
|
|
20415
|
+
if (text !== null && !opencodeConfig(parseJsonc(text), rules))
|
|
20416
|
+
return "unreadable";
|
|
20417
|
+
}
|
|
20418
|
+
if (env.OPENCODE_CONFIG_CONTENT && !opencodeConfig(parseJsonc(env.OPENCODE_CONFIG_CONTENT), rules))
|
|
20419
|
+
return "unreadable";
|
|
20420
|
+
} catch {
|
|
20421
|
+
return "unreadable";
|
|
20422
|
+
}
|
|
20423
|
+
return rules;
|
|
20424
|
+
}
|
|
20425
|
+
var codexRule = (body) => {
|
|
20426
|
+
const open = /\bpattern\s*=\s*\[/u.exec(body);
|
|
20427
|
+
if (!open)
|
|
20428
|
+
return null;
|
|
20429
|
+
const words = [];
|
|
20430
|
+
let index = open.index + open[0].length;
|
|
20431
|
+
for (;; ) {
|
|
20432
|
+
while (/[\s,]/u.test(body[index] ?? ""))
|
|
20433
|
+
index += 1;
|
|
20434
|
+
if (body[index] === "]")
|
|
20435
|
+
break;
|
|
20436
|
+
if (body[index] !== '"')
|
|
20437
|
+
return null;
|
|
20438
|
+
const literal = /"((?:[^"\\]|\\.)*)"/y;
|
|
20439
|
+
literal.lastIndex = index;
|
|
20440
|
+
const match = literal.exec(body);
|
|
20441
|
+
if (!match)
|
|
20442
|
+
return null;
|
|
20443
|
+
words.push(JSON.parse(`"${match[1]}"`));
|
|
20444
|
+
index = literal.lastIndex;
|
|
20445
|
+
}
|
|
20446
|
+
const decision = /\bdecision\s*=\s*"(\w+)"/u.exec(body)?.[1];
|
|
20447
|
+
if (!words.length || !decision || !["allow", "prompt", "forbidden"].includes(decision))
|
|
20448
|
+
return null;
|
|
20449
|
+
return { pattern: words.join(" "), decision };
|
|
20450
|
+
};
|
|
20451
|
+
function codexRules(cwd, env = process.env) {
|
|
20452
|
+
const rules = EMPTY();
|
|
20453
|
+
try {
|
|
20454
|
+
const home = env.CODEX_HOME || path21.join(homeOf(env), ".codex");
|
|
20455
|
+
const top = gitTop(cwd) ?? cwd;
|
|
20456
|
+
for (const dir of [path21.join(home, "rules"), path21.join(top, ".codex", "rules")]) {
|
|
20457
|
+
let files;
|
|
20458
|
+
try {
|
|
20459
|
+
files = fs16.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
20460
|
+
} catch (error) {
|
|
20461
|
+
if (error.code === "ENOENT")
|
|
20462
|
+
continue;
|
|
20463
|
+
return "unreadable";
|
|
20464
|
+
}
|
|
20465
|
+
for (const name of files) {
|
|
20466
|
+
const text = readText2(path21.join(dir, name)) ?? "";
|
|
20467
|
+
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
20468
|
+
const rule = codexRule(part);
|
|
20469
|
+
if (!rule)
|
|
20470
|
+
return "unreadable";
|
|
20471
|
+
(rule.decision === "allow" ? rules.allow : rules.deny).push(rule.pattern);
|
|
20472
|
+
}
|
|
20473
|
+
}
|
|
20474
|
+
}
|
|
20475
|
+
} catch {
|
|
20476
|
+
return "unreadable";
|
|
20477
|
+
}
|
|
20478
|
+
return rules;
|
|
20479
|
+
}
|
|
20480
|
+
function cursorRules(cwd, env = process.env) {
|
|
20481
|
+
const rules = EMPTY();
|
|
20482
|
+
try {
|
|
20483
|
+
const top = gitTop(cwd) ?? cwd;
|
|
20484
|
+
for (const file of [
|
|
20485
|
+
path21.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
20486
|
+
path21.join(top, ".cursor", "cli.json")
|
|
20487
|
+
]) {
|
|
20488
|
+
const text = readText2(file);
|
|
20489
|
+
if (text === null)
|
|
20490
|
+
continue;
|
|
20491
|
+
const value = JSON.parse(text);
|
|
20492
|
+
if (!isRecord3(value))
|
|
20493
|
+
return "unreadable";
|
|
20494
|
+
if (value.permissions === undefined)
|
|
20495
|
+
continue;
|
|
20496
|
+
if (!isRecord3(value.permissions))
|
|
20497
|
+
return "unreadable";
|
|
20498
|
+
for (const [key, bucket] of [
|
|
20499
|
+
["deny", rules.deny],
|
|
20500
|
+
["allow", rules.allow]
|
|
20501
|
+
]) {
|
|
20502
|
+
const list = value.permissions[key];
|
|
20503
|
+
if (list === undefined)
|
|
20504
|
+
continue;
|
|
20505
|
+
if (!Array.isArray(list))
|
|
20506
|
+
return "unreadable";
|
|
20507
|
+
for (const entry of list) {
|
|
20508
|
+
if (typeof entry !== "string")
|
|
20509
|
+
return "unreadable";
|
|
20510
|
+
const match = /^Shell(?:\((.*)\))?$/su.exec(entry.trim());
|
|
20511
|
+
if (match)
|
|
20512
|
+
bucket.push(match[1] ?? "");
|
|
20513
|
+
}
|
|
20514
|
+
}
|
|
20515
|
+
}
|
|
20516
|
+
} catch {
|
|
20517
|
+
return "unreadable";
|
|
20518
|
+
}
|
|
20519
|
+
return rules;
|
|
20520
|
+
}
|
|
20521
|
+
var baseName = (word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "");
|
|
20522
|
+
var firstToken = (pattern) => pattern.trim().split(/[:\s]/u)[0] ?? "";
|
|
20523
|
+
function names(pattern, words) {
|
|
20524
|
+
const token = firstToken(pattern);
|
|
20525
|
+
if (!token || token === "*" || token.includes("*"))
|
|
20526
|
+
return true;
|
|
20527
|
+
const wanted = baseName(token);
|
|
20528
|
+
return words.some((word) => baseName(word) === wanted);
|
|
20529
|
+
}
|
|
20530
|
+
var literalPrefix = (pattern) => pattern.trim().replace(/(?::\*| \*|\*)$/u, "");
|
|
20531
|
+
var coversWrapper = (pattern) => {
|
|
20532
|
+
const token = firstToken(pattern);
|
|
20533
|
+
if (!token || token.includes("*"))
|
|
20534
|
+
return true;
|
|
20535
|
+
const words = literalPrefix(pattern).split(/\s+/u);
|
|
20536
|
+
if (baseName(words[0]) !== "workit")
|
|
20537
|
+
return false;
|
|
20538
|
+
return ["check", "--heavy"].slice(0, words.length - 1).every((word, at) => {
|
|
20539
|
+
const given = words[at + 1];
|
|
20540
|
+
return given === word || word.startsWith(given);
|
|
20541
|
+
});
|
|
20542
|
+
};
|
|
20543
|
+
var coversCommand = (pattern, command) => {
|
|
20544
|
+
const prefix = literalPrefix(pattern);
|
|
20545
|
+
if (!prefix)
|
|
20546
|
+
return true;
|
|
20547
|
+
return !prefix.includes("*") && command.trim().startsWith(prefix);
|
|
20548
|
+
};
|
|
20549
|
+
function wrapAllowed(read, words, command, options = {}) {
|
|
20550
|
+
if (read === "unreadable")
|
|
20551
|
+
return { ok: false, why: "named" };
|
|
20552
|
+
if (read.deny.some((pattern) => names(pattern, words)))
|
|
20553
|
+
return { ok: false, why: "named" };
|
|
20554
|
+
if (!options.bypass && read.allow.some(coversWrapper) && !read.allow.some((pattern) => coversCommand(pattern, command)))
|
|
20555
|
+
return { ok: false, why: "allowlisted" };
|
|
20556
|
+
return { ok: true };
|
|
20557
|
+
}
|
|
20558
|
+
|
|
20559
|
+
// packages/workit-core/src/core/heavy.ts
|
|
20560
|
+
import fs17 from "node:fs";
|
|
20561
|
+
import os7 from "node:os";
|
|
20562
|
+
import path22 from "node:path";
|
|
20563
|
+
function stateDir(env, platform = process.platform) {
|
|
20564
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
20565
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
20566
|
+
if (env.XDG_STATE_HOME)
|
|
20567
|
+
return path22.join(env.XDG_STATE_HOME, "workit");
|
|
20568
|
+
const home = env.HOME || os7.homedir();
|
|
20569
|
+
if (platform === "darwin")
|
|
20570
|
+
return path22.join(home, "Library", "Application Support", "workit");
|
|
20571
|
+
if (platform === "win32")
|
|
20572
|
+
return path22.join(env.LOCALAPPDATA ?? path22.join(home, "AppData", "Local"), "workit");
|
|
20573
|
+
return path22.join(home, ".local", "state", "workit");
|
|
20574
|
+
}
|
|
20575
|
+
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
20576
|
+
var heavyCommandsFile = (env) => path22.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
20577
|
+
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;
|
|
20578
|
+
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
20579
|
+
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
20580
|
+
var WATCH_SCRIPT = /(?:^|[:_.-])(?:watch|dev|serve|start|storybook)(?:$|[:_.-])/iu;
|
|
20581
|
+
var sub = (...names) => new Set(names);
|
|
20582
|
+
var TOOLS = {
|
|
20583
|
+
jest: "any",
|
|
20584
|
+
vitest: "any",
|
|
20585
|
+
mocha: "any",
|
|
20586
|
+
ava: "any",
|
|
20587
|
+
tap: "any",
|
|
20588
|
+
karma: "any",
|
|
20589
|
+
jasmine: "any",
|
|
20590
|
+
nyc: "any",
|
|
20591
|
+
c8: "any",
|
|
20592
|
+
tsc: "any",
|
|
20593
|
+
"vue-tsc": "any",
|
|
20594
|
+
"svelte-check": "any",
|
|
20595
|
+
eslint: "any",
|
|
20596
|
+
stylelint: "any",
|
|
20597
|
+
tslint: "any",
|
|
20598
|
+
webpack: "any",
|
|
20599
|
+
"webpack-cli": "any",
|
|
20600
|
+
rollup: "any",
|
|
20601
|
+
parcel: "any",
|
|
20602
|
+
tsup: "any",
|
|
20603
|
+
vite: sub("build", "optimize"),
|
|
20604
|
+
next: sub("build", "lint"),
|
|
20605
|
+
nuxt: sub("build", "generate"),
|
|
20606
|
+
nuxi: sub("build", "generate"),
|
|
20607
|
+
astro: sub("build", "check"),
|
|
20608
|
+
ng: sub("build", "b", "test", "t", "e2e", "lint"),
|
|
20609
|
+
"react-scripts": sub("build", "test"),
|
|
20610
|
+
playwright: sub("test", "install"),
|
|
20611
|
+
cypress: sub("run"),
|
|
20612
|
+
wdio: "any",
|
|
20613
|
+
testcafe: "any",
|
|
20614
|
+
turbo: "any",
|
|
20615
|
+
lerna: sub("run", "exec", "bootstrap"),
|
|
20616
|
+
"sonar-scanner": "any",
|
|
20617
|
+
sonarless: sub("scan"),
|
|
20618
|
+
"sonarless-mcp": sub("scan"),
|
|
20619
|
+
go: sub("test", "build", "vet", "install", "generate"),
|
|
20620
|
+
"golangci-lint": sub("run"),
|
|
20621
|
+
gotestsum: "any",
|
|
20622
|
+
staticcheck: "any",
|
|
20623
|
+
cargo: sub("build", "b", "test", "t", "check", "c", "clippy", "bench", "nextest", "doc", "install", "tarpaulin", "llvm-cov"),
|
|
20624
|
+
mvn: "any",
|
|
20625
|
+
mvnw: "any",
|
|
20626
|
+
gradle: "any",
|
|
20627
|
+
gradlew: "any",
|
|
20628
|
+
ant: "any",
|
|
20629
|
+
sbt: "any",
|
|
20630
|
+
bazel: sub("build", "test", "run", "coverage"),
|
|
20631
|
+
bazelisk: sub("build", "test", "run", "coverage"),
|
|
20632
|
+
buck2: sub("build", "test", "run"),
|
|
20633
|
+
dotnet: sub("build", "test", "publish", "restore", "pack", "msbuild"),
|
|
20634
|
+
msbuild: "any",
|
|
20635
|
+
pytest: "any",
|
|
20636
|
+
"py.test": "any",
|
|
20637
|
+
tox: "any",
|
|
20638
|
+
nox: "any",
|
|
20639
|
+
mypy: "any",
|
|
20640
|
+
pyright: "any",
|
|
20641
|
+
pylint: "any",
|
|
20642
|
+
coverage: sub("run"),
|
|
20643
|
+
maturin: sub("build", "develop"),
|
|
20644
|
+
pip: sub("install", "wheel"),
|
|
20645
|
+
pip3: sub("install", "wheel"),
|
|
20646
|
+
rspec: "any",
|
|
20647
|
+
rails: sub("test", "assets:precompile"),
|
|
20648
|
+
phpunit: "any",
|
|
20649
|
+
pest: "any",
|
|
20650
|
+
phpstan: "any",
|
|
20651
|
+
psalm: "any",
|
|
20652
|
+
composer: sub("install", "update", "require", "test"),
|
|
20653
|
+
mix: sub("test", "compile", "deps.get", "dialyzer", "credo"),
|
|
20654
|
+
swift: sub("build", "test"),
|
|
20655
|
+
xcodebuild: "any",
|
|
20656
|
+
flutter: sub("build", "test"),
|
|
20657
|
+
dart: sub("test", "compile"),
|
|
20658
|
+
pod: sub("install"),
|
|
20659
|
+
cmake: sub("--build"),
|
|
20660
|
+
ninja: "any",
|
|
20661
|
+
meson: sub("compile", "test"),
|
|
20662
|
+
ctest: "any",
|
|
20663
|
+
scons: "any",
|
|
20664
|
+
"docker-compose": sub("build"),
|
|
20665
|
+
buildah: sub("bud", "build"),
|
|
20666
|
+
earthly: "any"
|
|
20667
|
+
};
|
|
20668
|
+
var PM_VALUES = new Set([
|
|
20669
|
+
"--prefix",
|
|
20670
|
+
"-w",
|
|
20671
|
+
"--workspace",
|
|
20672
|
+
"-C",
|
|
20673
|
+
"--dir",
|
|
20674
|
+
"--filter",
|
|
20675
|
+
"-F",
|
|
20676
|
+
"--cwd",
|
|
20677
|
+
"-p",
|
|
20678
|
+
"--package",
|
|
20679
|
+
"--cache",
|
|
20680
|
+
"--registry"
|
|
20681
|
+
]);
|
|
20682
|
+
function positional2(words, from, values = PM_VALUES) {
|
|
20683
|
+
let index = from;
|
|
20684
|
+
while (index < words.length && words[index].startsWith("-")) {
|
|
20685
|
+
if (words[index] === "--")
|
|
20686
|
+
return index + 1;
|
|
20687
|
+
index += values.has(words[index]) ? 2 : 1;
|
|
20688
|
+
}
|
|
20689
|
+
return index;
|
|
20690
|
+
}
|
|
20691
|
+
var baseName2 = (word) => (word?.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/u, "");
|
|
20692
|
+
var WORKIT = /^(?:workit|@brainervirus\/workit-cli(?:@.*)?)$/u;
|
|
20693
|
+
var PYTHONS = /^(?:python[\d.]*|py)$/u;
|
|
20694
|
+
var NPM_INSTALL = new Set([
|
|
20695
|
+
"ci",
|
|
20696
|
+
"clean-install",
|
|
20697
|
+
"cit",
|
|
20698
|
+
"install-test",
|
|
20699
|
+
"it",
|
|
20700
|
+
"install",
|
|
20701
|
+
"i",
|
|
20702
|
+
"in",
|
|
20703
|
+
"add",
|
|
20704
|
+
"rebuild",
|
|
20705
|
+
"update",
|
|
20706
|
+
"up"
|
|
20707
|
+
]);
|
|
20708
|
+
var NPM_RUN = new Set(["run", "run-script", "rum", "urn"]);
|
|
20709
|
+
var NPM_TEST = new Set(["test", "t", "tst"]);
|
|
20710
|
+
var EXEC = new Set(["exec", "x", "dlx"]);
|
|
20711
|
+
function builtIn(words, depth = 0) {
|
|
20712
|
+
if (depth > 6 || !words.length)
|
|
20713
|
+
return null;
|
|
20714
|
+
const name = baseName2(words[0]);
|
|
20715
|
+
const label = () => words.slice(0, 3).join(" ");
|
|
20716
|
+
const inner = (from) => builtIn(words.slice(from), depth + 1);
|
|
20717
|
+
if (WORKIT.test(name))
|
|
20718
|
+
return null;
|
|
20719
|
+
if (INFO_FLAGS.has(words[1] ?? "") && words.length === 2)
|
|
20720
|
+
return null;
|
|
20721
|
+
if (words.some((word) => word === "--watch" || word.startsWith("--watch=") || word === "--watchAll"))
|
|
20722
|
+
return null;
|
|
20723
|
+
if (name === "npx" || name === "bunx" || name === "pnpx")
|
|
20724
|
+
return inner(positional2(words, 1));
|
|
20725
|
+
if (name === "corepack")
|
|
20726
|
+
return inner(1);
|
|
20727
|
+
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" || name === "deno") {
|
|
20728
|
+
const at = positional2(words, 1);
|
|
20729
|
+
const command = words[at];
|
|
20730
|
+
if (command === undefined)
|
|
20731
|
+
return name === "yarn" ? "yarn (install)" : null;
|
|
20732
|
+
if (name === "deno") {
|
|
20733
|
+
if (["test", "bench", "check", "compile", "lint"].includes(command))
|
|
20734
|
+
return label();
|
|
20735
|
+
if (command === "task" && HEAVY_SCRIPT.test(words[at + 1] ?? ""))
|
|
20736
|
+
return label();
|
|
20737
|
+
return null;
|
|
20738
|
+
}
|
|
20739
|
+
if (NPM_INSTALL.has(command) || NPM_TEST.has(command))
|
|
20740
|
+
return label();
|
|
20741
|
+
if (name === "bun" && command === "build")
|
|
20742
|
+
return label();
|
|
20743
|
+
if (EXEC.has(command))
|
|
20744
|
+
return inner(positional2(words, at + 1));
|
|
20745
|
+
if (name === "yarn" && command === "workspace")
|
|
20746
|
+
return inner(at + 2);
|
|
20747
|
+
if (name === "yarn" && command === "workspaces")
|
|
20748
|
+
return words.slice(at + 1).some((word) => HEAVY_SCRIPT.test(word)) ? label() : null;
|
|
20749
|
+
if (NPM_RUN.has(command)) {
|
|
20750
|
+
const script = words[positional2(words, at + 1)];
|
|
20751
|
+
if (script === undefined || WATCH_SCRIPT.test(script))
|
|
20752
|
+
return null;
|
|
20753
|
+
if (HEAVY_SCRIPT.test(script))
|
|
20754
|
+
return label();
|
|
20755
|
+
return name === "bun" ? inner(positional2(words, at + 1)) : null;
|
|
20756
|
+
}
|
|
20757
|
+
if (name === "npm")
|
|
20758
|
+
return null;
|
|
20759
|
+
if (WATCH_SCRIPT.test(command))
|
|
20760
|
+
return null;
|
|
20761
|
+
if (HEAVY_SCRIPT.test(command) && !/\.[cm]?[jt]sx?$/u.test(command))
|
|
20762
|
+
return label();
|
|
20763
|
+
return inner(at);
|
|
20764
|
+
}
|
|
20765
|
+
if (PYTHONS.test(name)) {
|
|
20766
|
+
const at = words.indexOf("-m");
|
|
20767
|
+
return at > 0 && words[at + 1] ? inner(at + 1) : null;
|
|
20768
|
+
}
|
|
20769
|
+
if (["uv", "poetry", "pdm", "pipenv", "hatch", "rye"].includes(name)) {
|
|
20770
|
+
const at = positional2(words, 1);
|
|
20771
|
+
const command = words[at];
|
|
20772
|
+
if (command === "run")
|
|
20773
|
+
return inner(positional2(words, at + 1));
|
|
20774
|
+
if (command === "pip")
|
|
20775
|
+
return inner(at);
|
|
20776
|
+
return ["sync", "install", "build", "test", "add"].includes(command ?? "") ? label() : null;
|
|
20777
|
+
}
|
|
20778
|
+
if (name === "bundle") {
|
|
20779
|
+
const command = words[positional2(words, 1)];
|
|
20780
|
+
if (command === "exec")
|
|
20781
|
+
return inner(positional2(words, 2));
|
|
20782
|
+
return command === "install" || command === undefined ? label() : null;
|
|
20783
|
+
}
|
|
20784
|
+
if (name === "make" || name === "gmake") {
|
|
20785
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
20786
|
+
if (targets.some((target) => WATCH_SCRIPT.test(target)))
|
|
20787
|
+
return null;
|
|
20788
|
+
return !targets.length || targets.some((target) => HEAVY_SCRIPT.test(target)) ? label() : null;
|
|
20789
|
+
}
|
|
20790
|
+
if (name === "rake") {
|
|
20791
|
+
const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
|
|
20792
|
+
return targets.some((target) => HEAVY_SCRIPT.test(target) || /^spec(?:$|:)/u.test(target)) ? label() : null;
|
|
20793
|
+
}
|
|
20794
|
+
if (name === "nx") {
|
|
20795
|
+
const at = positional2(words, 1);
|
|
20796
|
+
const command = words[at] ?? "";
|
|
20797
|
+
if (["run-many", "affected"].includes(command))
|
|
20798
|
+
return words.some((word) => /^(?:-t|--targets?)(?:=|$)/u.test(word)) && !words.some((word) => WATCH_SCRIPT.test(word.replace(/^--?targets?=/u, ""))) ? label() : null;
|
|
20799
|
+
if (command === "run") {
|
|
20800
|
+
const target = (words[at + 1] ?? "").split(":")[1] ?? "";
|
|
20801
|
+
return HEAVY_SCRIPT.test(target) && !WATCH_SCRIPT.test(target) ? label() : null;
|
|
20802
|
+
}
|
|
20803
|
+
return HEAVY_SCRIPT.test(command) && !WATCH_SCRIPT.test(command) ? label() : null;
|
|
20804
|
+
}
|
|
20805
|
+
if (name === "docker" || name === "podman" || name === "nerdctl") {
|
|
20806
|
+
const at = positional2(words, 1, new Set(["-H", "--host", "--context", "-c", "--config"]));
|
|
20807
|
+
const command = words[at];
|
|
20808
|
+
if (command === "build")
|
|
20809
|
+
return label();
|
|
20810
|
+
if (command === "buildx" || command === "image" || command === "builder")
|
|
20811
|
+
return words[positional2(words, at + 1)] === "build" ? label() : null;
|
|
20812
|
+
if (command === "compose") {
|
|
20813
|
+
const at2 = positional2(words, at + 1, new Set(["-f", "--file", "-p"]));
|
|
20814
|
+
const action = words[at2];
|
|
20815
|
+
if (action === "build")
|
|
20816
|
+
return label();
|
|
20817
|
+
return action === "up" && words.slice(at2 + 1).includes("--build") ? label() : null;
|
|
20818
|
+
}
|
|
20819
|
+
return null;
|
|
20820
|
+
}
|
|
20821
|
+
const rule = TOOLS[name];
|
|
20822
|
+
if (!rule)
|
|
20823
|
+
return null;
|
|
20824
|
+
if (words.slice(1).some((word) => WATCH_FLAG.test(word) || word === "watch" || word === "dev"))
|
|
20825
|
+
return null;
|
|
20826
|
+
if (rule === "any")
|
|
20827
|
+
return label();
|
|
20828
|
+
const command = words.slice(1).find((word) => !word.startsWith("-") && !word.startsWith("+") || rule.has(word));
|
|
20829
|
+
return command !== undefined && rule.has(command) ? label() : null;
|
|
20830
|
+
}
|
|
20831
|
+
var globWord = (word) => new RegExp(`^${word.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*")}$`, "u");
|
|
20832
|
+
function parsePattern(pattern) {
|
|
20833
|
+
const words = pattern.trim().split(/\s+/u).filter(Boolean);
|
|
20834
|
+
return words.length ? words.map(globWord) : null;
|
|
20835
|
+
}
|
|
20836
|
+
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]));
|
|
20837
|
+
var readJson = (file) => {
|
|
20838
|
+
try {
|
|
20839
|
+
return JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
20840
|
+
} catch {
|
|
20841
|
+
return;
|
|
20842
|
+
}
|
|
20843
|
+
};
|
|
20844
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20845
|
+
var stringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
20846
|
+
function machinePatterns(env) {
|
|
20847
|
+
const value = readJson(heavyCommandsFile(env));
|
|
20848
|
+
return isRecord4(value) ? stringList(value.patterns) : [];
|
|
20849
|
+
}
|
|
20850
|
+
function repoPatterns(cwd) {
|
|
20851
|
+
return repoHeavyCommands(gitTop(cwd) ?? path22.resolve(cwd));
|
|
20852
|
+
}
|
|
20853
|
+
function heavyLines(lines, options) {
|
|
20854
|
+
const found = [];
|
|
20855
|
+
let patterns = null;
|
|
20856
|
+
for (const line of lines) {
|
|
20857
|
+
const reason = builtIn(line);
|
|
20858
|
+
if (reason) {
|
|
20859
|
+
found.push({ line, source: "catalog", reason });
|
|
20860
|
+
continue;
|
|
20861
|
+
}
|
|
20862
|
+
patterns ??= (options.patterns ?? [
|
|
20863
|
+
...machinePatterns(options.env ?? process.env).map((pattern) => ({
|
|
20864
|
+
pattern,
|
|
20865
|
+
from: "machine"
|
|
20866
|
+
})),
|
|
20867
|
+
...repoPatterns(options.cwd).map((pattern) => ({ pattern, from: "repo" }))
|
|
20868
|
+
]).flatMap((entry) => {
|
|
20869
|
+
const matchers = parsePattern(entry.pattern);
|
|
20870
|
+
return matchers ? [{ ...entry, matchers }] : [];
|
|
20871
|
+
});
|
|
20872
|
+
const hit = patterns.find((entry) => matches(entry.matchers, line));
|
|
20873
|
+
if (hit)
|
|
20874
|
+
found.push({
|
|
20875
|
+
line,
|
|
20876
|
+
source: { pattern: hit.pattern, from: hit.from },
|
|
20877
|
+
reason: `${hit.from} pattern "${hit.pattern}"`
|
|
20878
|
+
});
|
|
20879
|
+
}
|
|
20880
|
+
return found;
|
|
20881
|
+
}
|
|
20882
|
+
var linesOf = (command, dialect) => {
|
|
20883
|
+
try {
|
|
20884
|
+
return commandLines(command, dialect);
|
|
20885
|
+
} catch {
|
|
20886
|
+
return null;
|
|
20887
|
+
}
|
|
20888
|
+
};
|
|
20889
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/u.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
|
|
20890
|
+
var leaseCommand = (words) => `workit check --heavy -- ${words.map(quoteWord).join(" ")}`;
|
|
20891
|
+
function heavyPlan(command, options) {
|
|
20892
|
+
const dialect = options.dialect ?? "posix";
|
|
20893
|
+
if (dialect !== "posix" || !gitTop(options.cwd))
|
|
20894
|
+
return { kind: "none" };
|
|
20895
|
+
const lines = linesOf(command, dialect);
|
|
20896
|
+
if (!lines)
|
|
20897
|
+
return { kind: "none" };
|
|
20898
|
+
const heavy = heavyLines(lines, options);
|
|
20899
|
+
if (!heavy.length)
|
|
20900
|
+
return { kind: "none" };
|
|
20901
|
+
const simple = simpleCommand(command, dialect);
|
|
20902
|
+
const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
|
|
20903
|
+
const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
|
|
20904
|
+
words: match.line,
|
|
20905
|
+
text: match.line.join(" ")
|
|
20906
|
+
}));
|
|
20907
|
+
if (options.rules !== null)
|
|
20908
|
+
for (const { words, text } of judged) {
|
|
20909
|
+
const verdict = wrapAllowed(options.rules, words, text, { bypass: options.bypass });
|
|
20910
|
+
if (!verdict.ok)
|
|
20911
|
+
return {
|
|
20912
|
+
kind: "passthrough",
|
|
20913
|
+
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.`
|
|
20914
|
+
};
|
|
20915
|
+
}
|
|
20916
|
+
if (single) {
|
|
20917
|
+
const rewrite = leaseRewrite(command);
|
|
20918
|
+
return rewrite ? { kind: "rewrite", command: rewrite, reason: single.reason } : { kind: "none" };
|
|
20919
|
+
}
|
|
20920
|
+
const parts = heavy.map((match) => leaseCommand(match.line));
|
|
20921
|
+
return {
|
|
20922
|
+
kind: "split",
|
|
20923
|
+
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.`,
|
|
20924
|
+
unblock: parts[0] ?? null
|
|
20925
|
+
};
|
|
20926
|
+
}
|
|
20927
|
+
function workitOnPath(env = process.env) {
|
|
20928
|
+
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
20929
|
+
for (const dir of (env.PATH ?? "").split(path22.delimiter))
|
|
20930
|
+
for (const name of names) {
|
|
20931
|
+
if (!dir)
|
|
20932
|
+
continue;
|
|
20933
|
+
try {
|
|
20934
|
+
fs17.accessSync(path22.join(dir, name), fs17.constants.X_OK);
|
|
20935
|
+
return true;
|
|
20936
|
+
} catch {}
|
|
20937
|
+
}
|
|
20938
|
+
return false;
|
|
20939
|
+
}
|
|
20940
|
+
var ASSIGNMENTS = /^\s*((?:[A-Za-z_]\w*=(?:'[^']*'|"(?:[^"\\]|\\.)*"|[^\s'"])*\s+)*)/u;
|
|
20941
|
+
function leaseRewrite(command) {
|
|
20942
|
+
const text = command.trim();
|
|
20943
|
+
const prefix = ASSIGNMENTS.exec(text)?.[1] ?? "";
|
|
20944
|
+
const rest = text.slice(prefix.length).trim();
|
|
20945
|
+
if (!rest)
|
|
20946
|
+
return null;
|
|
20947
|
+
return `${prefix}workit check --heavy -- ${rest}`;
|
|
20948
|
+
}
|
|
20949
|
+
|
|
20950
|
+
// packages/workit-core/src/hooks/heavy-gate.ts
|
|
20951
|
+
var rulesFor = (host, cwd, env) => {
|
|
20952
|
+
switch (host) {
|
|
20953
|
+
case "claude_code":
|
|
20954
|
+
return claudeRules(cwd, env);
|
|
20955
|
+
case "opencode":
|
|
20956
|
+
return opencodeRules(cwd, env);
|
|
20957
|
+
case "codex_cli":
|
|
20958
|
+
case "codex_desktop":
|
|
20959
|
+
return codexRules(cwd, env);
|
|
20960
|
+
case "cursor":
|
|
20961
|
+
return cursorRules(cwd, env);
|
|
20962
|
+
case "pi":
|
|
20963
|
+
return null;
|
|
20964
|
+
}
|
|
20965
|
+
};
|
|
20966
|
+
function heavyGate(cwd, command, options) {
|
|
20967
|
+
const env = options.env ?? process.env;
|
|
20968
|
+
try {
|
|
20969
|
+
if (env.WORKIT_HEAVY_GATE === "0" || !maybeHeavy(command, cwd, env) || !workitOnPath(env))
|
|
20970
|
+
return { kind: "none" };
|
|
20971
|
+
return heavyPlan(command, {
|
|
20972
|
+
cwd,
|
|
20973
|
+
env,
|
|
20974
|
+
dialect: options.dialect ?? "posix",
|
|
20975
|
+
rules: rulesFor(options.host, cwd, env),
|
|
20976
|
+
bypass: options.permissionMode === "bypassPermissions"
|
|
20977
|
+
});
|
|
20978
|
+
} catch {
|
|
20979
|
+
return { kind: "none" };
|
|
20980
|
+
}
|
|
20981
|
+
}
|
|
20982
|
+
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.`;
|
|
20983
|
+
var heavyDenial = (original, plan) => ({
|
|
20984
|
+
kind: "deny",
|
|
20985
|
+
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}`,
|
|
20986
|
+
unblock: plan.command
|
|
20987
|
+
});
|
|
20988
|
+
var splitDenial = (plan) => ({
|
|
20989
|
+
kind: "deny",
|
|
20990
|
+
reason: plan.reason,
|
|
20991
|
+
unblock: plan.unblock
|
|
20992
|
+
});
|
|
20321
20993
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
20322
20994
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
20323
|
-
import
|
|
20324
|
-
import
|
|
20995
|
+
import os9 from "node:os";
|
|
20996
|
+
import path24 from "node:path";
|
|
20325
20997
|
|
|
20326
20998
|
// packages/workit-core/src/core/live-session.ts
|
|
20327
|
-
import { spawnSync as
|
|
20999
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
20328
21000
|
import { createHash as createHash15 } from "node:crypto";
|
|
20329
|
-
import
|
|
20330
|
-
import
|
|
20331
|
-
import
|
|
21001
|
+
import fs18 from "node:fs";
|
|
21002
|
+
import os8 from "node:os";
|
|
21003
|
+
import path23 from "node:path";
|
|
20332
21004
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
20333
21005
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
20334
21006
|
var liveProbe = {
|
|
20335
21007
|
platform: process.platform,
|
|
20336
|
-
run: (args) =>
|
|
21008
|
+
run: (args) => spawnSync12("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
|
|
20337
21009
|
start: (pid) => processStartOf(pid)
|
|
20338
21010
|
};
|
|
20339
21011
|
var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
|
|
@@ -20349,9 +21021,9 @@ var processAlive = (pid) => {
|
|
|
20349
21021
|
var processInfo = (pid) => {
|
|
20350
21022
|
try {
|
|
20351
21023
|
if (liveProbe.platform === "linux") {
|
|
20352
|
-
const stat =
|
|
21024
|
+
const stat = fs18.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
20353
21025
|
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
20354
|
-
const command =
|
|
21026
|
+
const command = fs18.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
20355
21027
|
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
20356
21028
|
}
|
|
20357
21029
|
if (liveProbe.platform === "win32")
|
|
@@ -20402,35 +21074,35 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
20402
21074
|
};
|
|
20403
21075
|
};
|
|
20404
21076
|
var checkoutOf = (target) => {
|
|
20405
|
-
let dir =
|
|
20406
|
-
while (!
|
|
20407
|
-
dir =
|
|
20408
|
-
if (
|
|
20409
|
-
dir =
|
|
20410
|
-
const parts = dir.split(
|
|
21077
|
+
let dir = path23.resolve(target);
|
|
21078
|
+
while (!fs18.existsSync(dir) && path23.dirname(dir) !== dir)
|
|
21079
|
+
dir = path23.dirname(dir);
|
|
21080
|
+
if (fs18.existsSync(dir) && !fs18.statSync(dir).isDirectory())
|
|
21081
|
+
dir = path23.dirname(dir);
|
|
21082
|
+
const parts = dir.split(path23.sep);
|
|
20411
21083
|
const dotGit = parts.lastIndexOf(".git");
|
|
20412
21084
|
if (dotGit > 0)
|
|
20413
|
-
dir = parts.slice(0, dotGit).join(
|
|
21085
|
+
dir = parts.slice(0, dotGit).join(path23.sep) || path23.sep;
|
|
20414
21086
|
const location = resolveStore(dir);
|
|
20415
21087
|
if (location instanceof Error || !location.shared || !location.top)
|
|
20416
21088
|
return null;
|
|
20417
21089
|
let top = location.top;
|
|
20418
21090
|
try {
|
|
20419
|
-
top =
|
|
21091
|
+
top = fs18.realpathSync.native(top);
|
|
20420
21092
|
} catch {}
|
|
20421
21093
|
return top;
|
|
20422
21094
|
};
|
|
20423
|
-
var markerFile2 = (top) =>
|
|
21095
|
+
var markerFile2 = (top) => path23.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
20424
21096
|
var checkoutKey = (top, platform = process.platform) => {
|
|
20425
|
-
let key =
|
|
21097
|
+
let key = path23.resolve(top);
|
|
20426
21098
|
try {
|
|
20427
|
-
key =
|
|
21099
|
+
key = fs18.realpathSync.native(key);
|
|
20428
21100
|
} catch {}
|
|
20429
21101
|
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
20430
21102
|
};
|
|
20431
21103
|
var readMarker = (file) => {
|
|
20432
21104
|
try {
|
|
20433
|
-
const value = JSON.parse(
|
|
21105
|
+
const value = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
20434
21106
|
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
20435
21107
|
return null;
|
|
20436
21108
|
return {
|
|
@@ -20453,7 +21125,7 @@ var isLive = (marker, deps) => {
|
|
|
20453
21125
|
const age = ageOf2(marker, deps.now ?? new Date);
|
|
20454
21126
|
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
20455
21127
|
return false;
|
|
20456
|
-
if (marker.machine !== (deps.machine ??
|
|
21128
|
+
if (marker.machine !== (deps.machine ?? os8.hostname()) || marker.pid === null)
|
|
20457
21129
|
return true;
|
|
20458
21130
|
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
20459
21131
|
return false;
|
|
@@ -20482,7 +21154,7 @@ var liveConflict = (target, owner, deps = {}) => {
|
|
|
20482
21154
|
const marker = readMarker(file);
|
|
20483
21155
|
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
20484
21156
|
return null;
|
|
20485
|
-
if (sameOwner(marker, owner, deps.machine ??
|
|
21157
|
+
if (sameOwner(marker, owner, deps.machine ?? os8.hostname()))
|
|
20486
21158
|
return null;
|
|
20487
21159
|
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
20488
21160
|
} catch {
|
|
@@ -20496,7 +21168,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
20496
21168
|
return;
|
|
20497
21169
|
const file = markerFile2(checkout);
|
|
20498
21170
|
const now = deps.now ?? new Date;
|
|
20499
|
-
const machine = deps.machine ??
|
|
21171
|
+
const machine = deps.machine ?? os8.hostname();
|
|
20500
21172
|
const current = readMarker(file);
|
|
20501
21173
|
if (current) {
|
|
20502
21174
|
const age = ageOf2(current, now);
|
|
@@ -20517,10 +21189,10 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
20517
21189
|
checkout,
|
|
20518
21190
|
heartbeatAt: now.toISOString()
|
|
20519
21191
|
};
|
|
20520
|
-
|
|
21192
|
+
fs18.mkdirSync(path23.dirname(file), { recursive: true });
|
|
20521
21193
|
const temp = `${file}.${process.pid}.tmp`;
|
|
20522
|
-
|
|
20523
|
-
|
|
21194
|
+
fs18.writeFileSync(temp, JSON.stringify(marker));
|
|
21195
|
+
fs18.renameSync(temp, file);
|
|
20524
21196
|
} catch {}
|
|
20525
21197
|
};
|
|
20526
21198
|
|
|
@@ -20529,27 +21201,42 @@ var NONE3 = { kind: "none" };
|
|
|
20529
21201
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
20530
21202
|
var canonical2 = (file) => {
|
|
20531
21203
|
let rest = "";
|
|
20532
|
-
for (let dir =
|
|
21204
|
+
for (let dir = path24.resolve(file);; dir = path24.dirname(dir)) {
|
|
20533
21205
|
try {
|
|
20534
|
-
return
|
|
21206
|
+
return path24.join(realpathSync5(dir), rest);
|
|
20535
21207
|
} catch {
|
|
20536
|
-
if (
|
|
20537
|
-
return
|
|
20538
|
-
rest = rest ?
|
|
21208
|
+
if (path24.dirname(dir) === dir)
|
|
21209
|
+
return path24.resolve(file);
|
|
21210
|
+
rest = rest ? path24.join(path24.basename(dir), rest) : path24.basename(dir);
|
|
20539
21211
|
}
|
|
20540
21212
|
}
|
|
20541
21213
|
};
|
|
20542
21214
|
var exempt = (cwd, root, file, plans) => {
|
|
20543
21215
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
20544
|
-
const relative =
|
|
20545
|
-
if (relative.startsWith("..") ||
|
|
21216
|
+
const relative = path24.relative(fold(canonical2(root)), fold(canonical2(path24.resolve(cwd, file))));
|
|
21217
|
+
if (relative.startsWith("..") || path24.isAbsolute(relative))
|
|
20546
21218
|
return true;
|
|
20547
|
-
const posix = relative.split(
|
|
21219
|
+
const posix = relative.split(path24.sep).join("/");
|
|
20548
21220
|
const segments = posix.split("/");
|
|
20549
21221
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
20550
21222
|
};
|
|
20551
21223
|
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
20552
21224
|
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
21225
|
+
var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
21226
|
+
var wrappedScript = (input) => {
|
|
21227
|
+
let words = input;
|
|
21228
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
21229
|
+
words = words.slice(1);
|
|
21230
|
+
const tail = workitCheckTail(words);
|
|
21231
|
+
if (!tail)
|
|
21232
|
+
return null;
|
|
21233
|
+
if ("script" in tail)
|
|
21234
|
+
return tail.script;
|
|
21235
|
+
const [shell, flag, script] = tail.argv;
|
|
21236
|
+
if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
|
|
21237
|
+
return script ?? null;
|
|
21238
|
+
return tail.argv.map(quoteWord2).join(" ");
|
|
21239
|
+
};
|
|
20553
21240
|
var editsOf = (input) => {
|
|
20554
21241
|
let words = input;
|
|
20555
21242
|
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
@@ -20626,10 +21313,18 @@ var editsOf = (input) => {
|
|
|
20626
21313
|
return [];
|
|
20627
21314
|
}
|
|
20628
21315
|
};
|
|
20629
|
-
var shellWrites = (command) => {
|
|
21316
|
+
var shellWrites = (command, depth = 0) => {
|
|
20630
21317
|
const targets = [];
|
|
20631
21318
|
let unknown = false;
|
|
20632
21319
|
for (const segment of segmentsOf(command)) {
|
|
21320
|
+
const inner = depth < 4 ? wrappedScript(segment.words) : null;
|
|
21321
|
+
if (inner !== null) {
|
|
21322
|
+
const nested = shellWrites(inner, depth + 1);
|
|
21323
|
+
if (nested.writes && nested.targets === null)
|
|
21324
|
+
unknown = true;
|
|
21325
|
+
else if (nested.targets)
|
|
21326
|
+
targets.push(...nested.targets);
|
|
21327
|
+
}
|
|
20633
21328
|
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
20634
21329
|
const edits = editsOf(segment.words);
|
|
20635
21330
|
if (edits === null)
|
|
@@ -20642,7 +21337,7 @@ var shellWrites = (command) => {
|
|
|
20642
21337
|
return { writes: true, targets: unknown ? null : targets };
|
|
20643
21338
|
};
|
|
20644
21339
|
var cache2 = new Map;
|
|
20645
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
21340
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path24.resolve(root, file)) ?? "-").join("\x00");
|
|
20646
21341
|
var gateFor = (cwd) => {
|
|
20647
21342
|
const store = new TaskStore(cwd);
|
|
20648
21343
|
const listed = store.listTaskIndex();
|
|
@@ -20721,7 +21416,7 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
20721
21416
|
return [];
|
|
20722
21417
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
20723
21418
|
return [];
|
|
20724
|
-
return [
|
|
21419
|
+
return [path24.resolve(cwd, invocation.dir ?? ".")];
|
|
20725
21420
|
});
|
|
20726
21421
|
};
|
|
20727
21422
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
@@ -20731,15 +21426,15 @@ var cdInto = (dir, words) => {
|
|
|
20731
21426
|
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
20732
21427
|
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
20733
21428
|
return;
|
|
20734
|
-
return target === undefined ?
|
|
21429
|
+
return target === undefined ? os9.homedir() : path24.resolve(dir, target);
|
|
20735
21430
|
};
|
|
20736
|
-
var
|
|
20737
|
-
var segmentTargets = (cwd, command, dialect) => {
|
|
21431
|
+
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
21432
|
+
var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
20738
21433
|
const targets = [];
|
|
20739
21434
|
let dir = cwd;
|
|
20740
21435
|
let stack = [];
|
|
20741
21436
|
const groups = [];
|
|
20742
|
-
const at = (file) =>
|
|
21437
|
+
const at = (file) => path24.isAbsolute(file) ? file : dir === undefined ? null : path24.resolve(dir, file);
|
|
20743
21438
|
for (const segment of segmentsOf(command, dialect)) {
|
|
20744
21439
|
if (segment.group === "open") {
|
|
20745
21440
|
groups.push({ dir, stack: [...stack] });
|
|
@@ -20767,6 +21462,9 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
20767
21462
|
dir = stack.length ? stack.pop() : undefined;
|
|
20768
21463
|
continue;
|
|
20769
21464
|
}
|
|
21465
|
+
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
21466
|
+
if (inner !== null && dir !== undefined)
|
|
21467
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
20770
21468
|
for (const file of segment.redirects)
|
|
20771
21469
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
20772
21470
|
const resolved = at(file);
|
|
@@ -20776,15 +21474,15 @@ var segmentTargets = (cwd, command, dialect) => {
|
|
|
20776
21474
|
const edits = editsOf(segment.words);
|
|
20777
21475
|
if (edits === null) {
|
|
20778
21476
|
if (dir !== undefined)
|
|
20779
|
-
targets.push(
|
|
21477
|
+
targets.push(path24.join(dir, DIR_TARGET));
|
|
20780
21478
|
} else
|
|
20781
21479
|
for (const file of edits) {
|
|
20782
21480
|
const resolved = at(file);
|
|
20783
21481
|
if (resolved)
|
|
20784
21482
|
targets.push(resolved);
|
|
20785
21483
|
}
|
|
20786
|
-
if (dir !== undefined && workitInvocations(segment.words.map(
|
|
20787
|
-
targets.push(
|
|
21484
|
+
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), "")))
|
|
21485
|
+
targets.push(path24.join(dir, DIR_TARGET));
|
|
20788
21486
|
}
|
|
20789
21487
|
return targets;
|
|
20790
21488
|
};
|
|
@@ -20819,7 +21517,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
20819
21517
|
var guarded = (targets) => {
|
|
20820
21518
|
const seen = new Set;
|
|
20821
21519
|
return targets.filter((target) => {
|
|
20822
|
-
const dir =
|
|
21520
|
+
const dir = path24.dirname(target);
|
|
20823
21521
|
if (seen.has(dir))
|
|
20824
21522
|
return false;
|
|
20825
21523
|
seen.add(dir);
|
|
@@ -20840,7 +21538,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
20840
21538
|
for (const target of guarded(targets))
|
|
20841
21539
|
claimLiveSession(target, owner, deps);
|
|
20842
21540
|
};
|
|
20843
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
21541
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path24.resolve(cwd, file)) : [path24.join(cwd, DIR_TARGET)];
|
|
20844
21542
|
function writeGate(cwd, paths, owner, deps) {
|
|
20845
21543
|
if (!owner)
|
|
20846
21544
|
return beforeWriteGate(cwd, paths);
|
|
@@ -20860,7 +21558,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
20860
21558
|
try {
|
|
20861
21559
|
targets = [
|
|
20862
21560
|
...segmentTargets(cwd, command, dialect),
|
|
20863
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
21561
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path24.join(dir, DIR_TARGET))
|
|
20864
21562
|
];
|
|
20865
21563
|
} catch {
|
|
20866
21564
|
targets = [];
|
|
@@ -20909,7 +21607,7 @@ function beforeWriteGate(cwd, paths) {
|
|
|
20909
21607
|
}
|
|
20910
21608
|
}
|
|
20911
21609
|
// packages/workit-core/src/hooks/stop.ts
|
|
20912
|
-
import { spawnSync as
|
|
21610
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
20913
21611
|
var NONE4 = { kind: "none" };
|
|
20914
21612
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
20915
21613
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -20930,7 +21628,7 @@ var asksUser = (message) => {
|
|
|
20930
21628
|
};
|
|
20931
21629
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
20932
21630
|
var gitList = (cwd, args) => {
|
|
20933
|
-
const run =
|
|
21631
|
+
const run = spawnSync13("git", args, {
|
|
20934
21632
|
cwd,
|
|
20935
21633
|
encoding: "utf8",
|
|
20936
21634
|
timeout: 3000,
|
|
@@ -21294,12 +21992,12 @@ ${line}` };
|
|
|
21294
21992
|
};
|
|
21295
21993
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
21296
21994
|
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
21297
|
-
import
|
|
21298
|
-
var
|
|
21995
|
+
import path25 from "node:path";
|
|
21996
|
+
var isRecord5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
21299
21997
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
21300
21998
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
21301
21999
|
var existingDirectory = (value) => {
|
|
21302
|
-
if (!nonEmpty2(value) || !
|
|
22000
|
+
if (!nonEmpty2(value) || !path25.isAbsolute(value) || !existsSync7(value))
|
|
21303
22001
|
return null;
|
|
21304
22002
|
try {
|
|
21305
22003
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -21352,7 +22050,7 @@ var isWriteTool = (name) => typeof name === "string" && WRITE_TOOLS.has(name.toL
|
|
|
21352
22050
|
var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
|
|
21353
22051
|
var PATCH_HEADER = /^[ \t]*\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm;
|
|
21354
22052
|
var writePaths = (input) => {
|
|
21355
|
-
if (!
|
|
22053
|
+
if (!isRecord5(input))
|
|
21356
22054
|
return [];
|
|
21357
22055
|
const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
|
|
21358
22056
|
for (const value of Object.values(input))
|
|
@@ -21447,10 +22145,32 @@ function handleHook(input, deps) {
|
|
|
21447
22145
|
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
|
|
21448
22146
|
if (gate.kind === "deny")
|
|
21449
22147
|
return gate;
|
|
22148
|
+
const plan = heavyGate(input.cwd, event.command, {
|
|
22149
|
+
host: input.host,
|
|
22150
|
+
dialect: event.dialect,
|
|
22151
|
+
permissionMode: input.permissionMode
|
|
22152
|
+
});
|
|
22153
|
+
if (plan.kind === "split" && canDeny)
|
|
22154
|
+
return splitDenial(plan);
|
|
22155
|
+
const rewrites = usable2(descriptor.shellPolicy.rewrite);
|
|
22156
|
+
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
22157
|
+
return heavyDenial(event.command, plan);
|
|
22158
|
+
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
22159
|
+
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
21450
22160
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
22161
|
+
const rewritten = (text) => lease ? {
|
|
22162
|
+
kind: "rewrite",
|
|
22163
|
+
command: lease.command,
|
|
22164
|
+
input: event.toolInput ? { ...event.toolInput, command: lease.command } : null,
|
|
22165
|
+
text
|
|
22166
|
+
} : NONE5;
|
|
21451
22167
|
if (descriptor.transport !== "hook-process")
|
|
21452
|
-
return
|
|
21453
|
-
|
|
22168
|
+
return rewritten(null);
|
|
22169
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
|
|
22170
|
+
if (!lease)
|
|
22171
|
+
return nudged;
|
|
22172
|
+
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
22173
|
+
`));
|
|
21454
22174
|
}
|
|
21455
22175
|
case "shell.post":
|
|
21456
22176
|
if (usable2(descriptor.events["shell.post"].support))
|
|
@@ -21528,7 +22248,12 @@ var CLAUDE_CODE_DESCRIPTOR = {
|
|
|
21528
22248
|
"compact.pre": { support: "none", native: null },
|
|
21529
22249
|
stop: { support: "native", native: "Stop" }
|
|
21530
22250
|
},
|
|
21531
|
-
shellPolicy: {
|
|
22251
|
+
shellPolicy: {
|
|
22252
|
+
deny: "native",
|
|
22253
|
+
channel: "permissionDecision",
|
|
22254
|
+
failClosed: false,
|
|
22255
|
+
rewrite: "native"
|
|
22256
|
+
},
|
|
21532
22257
|
context: {
|
|
21533
22258
|
sessionStart: "native",
|
|
21534
22259
|
perTurn: "native",
|
|
@@ -21610,7 +22335,7 @@ var EVENTS = {
|
|
|
21610
22335
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
21611
22336
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
21612
22337
|
var dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix";
|
|
21613
|
-
var toolCommand = (value) =>
|
|
22338
|
+
var toolCommand = (value) => isRecord5(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
21614
22339
|
var eventOf = (name, value) => {
|
|
21615
22340
|
const toolUseId = optionalText(value.tool_use_id);
|
|
21616
22341
|
switch (name) {
|
|
@@ -21638,19 +22363,28 @@ var eventOf = (name, value) => {
|
|
|
21638
22363
|
kind: "tool.pre",
|
|
21639
22364
|
tool: value.tool_name,
|
|
21640
22365
|
toolUseId,
|
|
21641
|
-
skill:
|
|
22366
|
+
skill: isRecord5(value.tool_input) ? optionalText(value.tool_input.skill) : null
|
|
21642
22367
|
}
|
|
21643
22368
|
};
|
|
21644
22369
|
if (!SHELL_TOOLS.has(value.tool_name))
|
|
21645
22370
|
return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
|
|
21646
22371
|
const command = toolCommand(value);
|
|
21647
|
-
return command ? {
|
|
22372
|
+
return command ? {
|
|
22373
|
+
ok: true,
|
|
22374
|
+
event: {
|
|
22375
|
+
kind: "shell.pre",
|
|
22376
|
+
command,
|
|
22377
|
+
toolUseId,
|
|
22378
|
+
dialect: dialectOf2(value),
|
|
22379
|
+
toolInput: value.tool_input
|
|
22380
|
+
}
|
|
22381
|
+
} : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
|
|
21648
22382
|
}
|
|
21649
22383
|
case "PostToolUse": {
|
|
21650
22384
|
const command = toolCommand(value);
|
|
21651
22385
|
if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
|
|
21652
22386
|
return { ok: false, error: "only shell PostToolUse is mapped" };
|
|
21653
|
-
const response =
|
|
22387
|
+
const response = isRecord5(value.tool_response) ? value.tool_response : {};
|
|
21654
22388
|
return {
|
|
21655
22389
|
ok: true,
|
|
21656
22390
|
event: {
|
|
@@ -21708,6 +22442,17 @@ var CONTEXT_EVENTS = new Set([
|
|
|
21708
22442
|
"PostToolUse"
|
|
21709
22443
|
]);
|
|
21710
22444
|
var render = (decision, native) => {
|
|
22445
|
+
if (decision.kind === "rewrite" && native === "PreToolUse" && decision.input)
|
|
22446
|
+
return {
|
|
22447
|
+
json: {
|
|
22448
|
+
hookSpecificOutput: {
|
|
22449
|
+
hookEventName: "PreToolUse",
|
|
22450
|
+
updatedInput: decision.input,
|
|
22451
|
+
...decision.text ? { additionalContext: decision.text } : {}
|
|
22452
|
+
}
|
|
22453
|
+
},
|
|
22454
|
+
exitCode: 0
|
|
22455
|
+
};
|
|
21711
22456
|
if (decision.kind === "deny" && native === "PreToolUse")
|
|
21712
22457
|
return {
|
|
21713
22458
|
json: {
|
|
@@ -21733,14 +22478,14 @@ var render = (decision, native) => {
|
|
|
21733
22478
|
var claudeCodeAdapter = {
|
|
21734
22479
|
descriptor: CLAUDE_CODE_DESCRIPTOR,
|
|
21735
22480
|
parse(raw) {
|
|
21736
|
-
const native =
|
|
22481
|
+
const native = isRecord5(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
21737
22482
|
const fail = (error) => ({
|
|
21738
22483
|
ok: false,
|
|
21739
22484
|
error,
|
|
21740
22485
|
native,
|
|
21741
22486
|
event: native ? EVENTS[native] : null
|
|
21742
22487
|
});
|
|
21743
|
-
if (!
|
|
22488
|
+
if (!isRecord5(raw) || !native)
|
|
21744
22489
|
return fail("hook_event_name is required");
|
|
21745
22490
|
if (!nonEmpty2(raw.session_id))
|
|
21746
22491
|
return fail("session_id is required");
|
|
@@ -21794,7 +22539,12 @@ var CODEX_DESCRIPTOR = {
|
|
|
21794
22539
|
"compact.pre": { support: "none", native: null },
|
|
21795
22540
|
stop: { support: "native", native: "Stop" }
|
|
21796
22541
|
},
|
|
21797
|
-
shellPolicy: {
|
|
22542
|
+
shellPolicy: {
|
|
22543
|
+
deny: "native",
|
|
22544
|
+
channel: "permissionDecision",
|
|
22545
|
+
failClosed: false,
|
|
22546
|
+
rewrite: "none"
|
|
22547
|
+
},
|
|
21798
22548
|
context: {
|
|
21799
22549
|
sessionStart: "native",
|
|
21800
22550
|
perTurn: "native",
|
|
@@ -21886,7 +22636,7 @@ var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
|
21886
22636
|
var isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase());
|
|
21887
22637
|
var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
21888
22638
|
var parseCodexHookInput = (value) => {
|
|
21889
|
-
if (!
|
|
22639
|
+
if (!isRecord5(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
|
|
21890
22640
|
return { ok: false, error: "hook_event_name is required" };
|
|
21891
22641
|
const event = value.hook_event_name;
|
|
21892
22642
|
const cwd = existingDirectory(value.cwd);
|
|
@@ -21895,7 +22645,7 @@ var parseCodexHookInput = (value) => {
|
|
|
21895
22645
|
const toolEvent = event === "PreToolUse" || event === "PostToolUse";
|
|
21896
22646
|
if (toolEvent && !nonEmpty2(value.tool_name))
|
|
21897
22647
|
return { ok: false, error: "tool_name is required" };
|
|
21898
|
-
const command =
|
|
22648
|
+
const command = isRecord5(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
21899
22649
|
if (toolEvent && isShellTool(value.tool_name) && !command)
|
|
21900
22650
|
return { ok: false, error: "tool_input.command is required for shell tools" };
|
|
21901
22651
|
if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
|
|
@@ -22009,7 +22759,7 @@ var codexAdapter = {
|
|
|
22009
22759
|
const host = detectCodexSurface(env);
|
|
22010
22760
|
const parsed = parseCodexHookInput(raw);
|
|
22011
22761
|
if (!parsed.ok) {
|
|
22012
|
-
const native =
|
|
22762
|
+
const native = isRecord5(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
22013
22763
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
|
|
22014
22764
|
}
|
|
22015
22765
|
const input = parsed.data;
|
|
@@ -22036,8 +22786,8 @@ var codexAdapter = {
|
|
|
22036
22786
|
};
|
|
22037
22787
|
|
|
22038
22788
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
22039
|
-
import
|
|
22040
|
-
import
|
|
22789
|
+
import fs19 from "node:fs";
|
|
22790
|
+
import path26 from "node:path";
|
|
22041
22791
|
var undocumented = { support: "undocumented", native: null };
|
|
22042
22792
|
var none = { support: "none", native: null };
|
|
22043
22793
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -22059,7 +22809,7 @@ var CURSOR_DESCRIPTOR = {
|
|
|
22059
22809
|
"compact.pre": { support: "partial", native: "preCompact" },
|
|
22060
22810
|
stop: { support: "native", native: "stop" }
|
|
22061
22811
|
},
|
|
22062
|
-
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false },
|
|
22812
|
+
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false, rewrite: "none" },
|
|
22063
22813
|
context: {
|
|
22064
22814
|
sessionStart: "native",
|
|
22065
22815
|
perTurn: "none",
|
|
@@ -22152,7 +22902,7 @@ var EVENTS3 = {
|
|
|
22152
22902
|
stop: "stop"
|
|
22153
22903
|
};
|
|
22154
22904
|
var parseCursorHookInput = (value) => {
|
|
22155
|
-
if (!
|
|
22905
|
+
if (!isRecord5(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
22156
22906
|
return { ok: false, error: "hook_event_name is required" };
|
|
22157
22907
|
const roots = value.workspace_roots;
|
|
22158
22908
|
if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
|
|
@@ -22234,17 +22984,17 @@ var protocolEvent2 = (input) => {
|
|
|
22234
22984
|
};
|
|
22235
22985
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
22236
22986
|
var lastAssistantText = (file) => {
|
|
22237
|
-
if (!file || !
|
|
22987
|
+
if (!file || !path26.isAbsolute(file))
|
|
22238
22988
|
return null;
|
|
22239
22989
|
try {
|
|
22240
|
-
const size =
|
|
22990
|
+
const size = fs19.statSync(file).size;
|
|
22241
22991
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
22242
22992
|
const buffer = Buffer.alloc(size - start);
|
|
22243
|
-
const fd =
|
|
22993
|
+
const fd = fs19.openSync(file, "r");
|
|
22244
22994
|
try {
|
|
22245
|
-
|
|
22995
|
+
fs19.readSync(fd, buffer, 0, buffer.length, start);
|
|
22246
22996
|
} finally {
|
|
22247
|
-
|
|
22997
|
+
fs19.closeSync(fd);
|
|
22248
22998
|
}
|
|
22249
22999
|
const lines = buffer.toString("utf8").split(`
|
|
22250
23000
|
`);
|
|
@@ -22255,14 +23005,14 @@ var lastAssistantText = (file) => {
|
|
|
22255
23005
|
} catch {
|
|
22256
23006
|
continue;
|
|
22257
23007
|
}
|
|
22258
|
-
if (!
|
|
23008
|
+
if (!isRecord5(entry) || entry.role !== "assistant")
|
|
22259
23009
|
continue;
|
|
22260
|
-
const content =
|
|
23010
|
+
const content = isRecord5(entry.message) ? entry.message.content : entry.content;
|
|
22261
23011
|
if (typeof content === "string")
|
|
22262
23012
|
return content;
|
|
22263
23013
|
if (!Array.isArray(content))
|
|
22264
23014
|
return null;
|
|
22265
|
-
const text = content.filter((part) =>
|
|
23015
|
+
const text = content.filter((part) => isRecord5(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
|
|
22266
23016
|
`);
|
|
22267
23017
|
return text || null;
|
|
22268
23018
|
}
|
|
@@ -22271,7 +23021,7 @@ var lastAssistantText = (file) => {
|
|
|
22271
23021
|
return null;
|
|
22272
23022
|
}
|
|
22273
23023
|
};
|
|
22274
|
-
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
23024
|
+
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path26.sep) ? root : `${root}${path26.sep}`);
|
|
22275
23025
|
var hookCwd = (input) => {
|
|
22276
23026
|
const cwd = existingDirectory(input.cwd);
|
|
22277
23027
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
@@ -22312,7 +23062,7 @@ var cursorAdapter = {
|
|
|
22312
23062
|
parse(raw) {
|
|
22313
23063
|
const parsed = parseCursorHookInput(raw);
|
|
22314
23064
|
if (!parsed.ok) {
|
|
22315
|
-
const native =
|
|
23065
|
+
const native = isRecord5(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
22316
23066
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
|
|
22317
23067
|
}
|
|
22318
23068
|
const input = parsed.data;
|