@brainervirus/workit-cursor 8.22.3 → 8.24.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/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +1590 -328
- package/dist/mcp-server.js +988 -146
- package/dist/workit-hook.js +1577 -325
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +5 -5
- package/skills/workit-fanout/references/brief.md +17 -11
- package/skills/workit-fanout/references/orchestration.md +6 -6
- package/skills/workit-implement/SKILL.md +7 -5
- package/skills/workit-review/SKILL.md +3 -0
- package/skills/workit-ship/SKILL.md +1 -2
|
@@ -6981,6 +6981,110 @@ function resolveRef(cwd, ref) {
|
|
|
6981
6981
|
return null;
|
|
6982
6982
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
6983
6983
|
}
|
|
6984
|
+
function diffNumstat(cwd, fork, head) {
|
|
6985
|
+
const range = laneRange(fork, head);
|
|
6986
|
+
if (!range)
|
|
6987
|
+
return null;
|
|
6988
|
+
const run = git(cwd, [...LANE_DIFF, "--numstat", "-z", ...range, "--"]);
|
|
6989
|
+
if (!run.ok)
|
|
6990
|
+
return null;
|
|
6991
|
+
const out = [];
|
|
6992
|
+
for (const record of run.stdout.split("\x00")) {
|
|
6993
|
+
const match = /^(-|\d+)\t(-|\d+)\t(.+)$/su.exec(record.replace(/^\n/u, ""));
|
|
6994
|
+
if (!match)
|
|
6995
|
+
continue;
|
|
6996
|
+
const binary = match[1] === "-";
|
|
6997
|
+
out.push({
|
|
6998
|
+
path: match[3],
|
|
6999
|
+
added: binary ? 0 : Number(match[1]),
|
|
7000
|
+
deleted: binary ? 0 : Number(match[2]),
|
|
7001
|
+
binary
|
|
7002
|
+
});
|
|
7003
|
+
}
|
|
7004
|
+
return out;
|
|
7005
|
+
}
|
|
7006
|
+
function diffRaw(cwd, fork, head) {
|
|
7007
|
+
const range = laneRange(fork, head);
|
|
7008
|
+
if (!range)
|
|
7009
|
+
return null;
|
|
7010
|
+
const run = git(cwd, [...LANE_DIFF, "--raw", "-z", "--no-abbrev", ...range, "--"]);
|
|
7011
|
+
if (!run.ok)
|
|
7012
|
+
return null;
|
|
7013
|
+
const fields = run.stdout.split("\x00");
|
|
7014
|
+
const out = [];
|
|
7015
|
+
for (let index = 0;index + 1 < fields.length; index += 2) {
|
|
7016
|
+
const meta = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) [A-Z]\d*$/u.exec(fields[index].replace(/^\n/u, ""));
|
|
7017
|
+
if (!meta)
|
|
7018
|
+
return null;
|
|
7019
|
+
out.push({
|
|
7020
|
+
path: fields[index + 1],
|
|
7021
|
+
oldMode: meta[1],
|
|
7022
|
+
newMode: meta[2],
|
|
7023
|
+
oldBlob: meta[3],
|
|
7024
|
+
newBlob: meta[4]
|
|
7025
|
+
});
|
|
7026
|
+
}
|
|
7027
|
+
return out;
|
|
7028
|
+
}
|
|
7029
|
+
function readBlobs(cwd, ids) {
|
|
7030
|
+
const out = new Map;
|
|
7031
|
+
const wanted = [...new Set(ids)].filter((id) => /^[0-9a-f]{40,64}$/u.test(id));
|
|
7032
|
+
if (!wanted.length)
|
|
7033
|
+
return out;
|
|
7034
|
+
const run = spawnSync("git", ["cat-file", "--batch"], {
|
|
7035
|
+
cwd,
|
|
7036
|
+
input: `${wanted.join(`
|
|
7037
|
+
`)}
|
|
7038
|
+
`,
|
|
7039
|
+
maxBuffer: MAX_BUFFER,
|
|
7040
|
+
timeout: GIT_TIMEOUTS.local,
|
|
7041
|
+
killSignal: "SIGKILL",
|
|
7042
|
+
windowsHide: true
|
|
7043
|
+
});
|
|
7044
|
+
if (run.status !== 0 || !run.stdout)
|
|
7045
|
+
return null;
|
|
7046
|
+
const bytes = run.stdout;
|
|
7047
|
+
let at = 0;
|
|
7048
|
+
for (const id of wanted) {
|
|
7049
|
+
const end = bytes.indexOf(10, at);
|
|
7050
|
+
if (end < 0)
|
|
7051
|
+
return null;
|
|
7052
|
+
const header = bytes.subarray(at, end).toString("utf8").split(" ");
|
|
7053
|
+
at = end + 1;
|
|
7054
|
+
if (header[1] === "missing")
|
|
7055
|
+
continue;
|
|
7056
|
+
const size = Number(header[2]);
|
|
7057
|
+
if (header[0] !== id || !Number.isSafeInteger(size))
|
|
7058
|
+
return null;
|
|
7059
|
+
out.set(id, bytes.subarray(at, at + size));
|
|
7060
|
+
at += size + 1;
|
|
7061
|
+
}
|
|
7062
|
+
return out;
|
|
7063
|
+
}
|
|
7064
|
+
function treeFilesNamed(cwd, rev, name) {
|
|
7065
|
+
if (!safeArg(rev))
|
|
7066
|
+
return null;
|
|
7067
|
+
const run = git(cwd, ["ls-tree", "-r", "-z", "--full-tree", rev]);
|
|
7068
|
+
if (!run.ok)
|
|
7069
|
+
return null;
|
|
7070
|
+
const out = new Map;
|
|
7071
|
+
for (const record of run.stdout.split("\x00")) {
|
|
7072
|
+
const match = /^\d{6} blob ([0-9a-f]+)\t(.+)$/su.exec(record);
|
|
7073
|
+
if (match && (match[2] === name || match[2].endsWith(`/${name}`)))
|
|
7074
|
+
out.set(match[2], match[1]);
|
|
7075
|
+
}
|
|
7076
|
+
return out;
|
|
7077
|
+
}
|
|
7078
|
+
function fileAt(cwd, rev, file) {
|
|
7079
|
+
if (!safeArg(rev))
|
|
7080
|
+
return null;
|
|
7081
|
+
const run = git(cwd, ["cat-file", "blob", `${rev}:${file}`]);
|
|
7082
|
+
return run.ok ? run.stdout : null;
|
|
7083
|
+
}
|
|
7084
|
+
function untrackedFiles(cwd) {
|
|
7085
|
+
const run = git(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]);
|
|
7086
|
+
return run.ok ? run.stdout.split("\x00").filter(Boolean) : [];
|
|
7087
|
+
}
|
|
6984
7088
|
function currentBranch(cwd) {
|
|
6985
7089
|
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
6986
7090
|
}
|
|
@@ -7027,11 +7131,24 @@ var MAX_BUFFER, GIT_TIMEOUTS, git = (cwd, args, options = {}) => {
|
|
|
7027
7131
|
const { atimeNs, mtimeNs } = fs.statSync(from, { bigint: true });
|
|
7028
7132
|
const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
|
|
7029
7133
|
fs.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
|
|
7134
|
+
}, LANE_DIFF, laneRange = (fork, head) => {
|
|
7135
|
+
if (!safeArg(fork) || head !== null && !safeArg(head))
|
|
7136
|
+
return null;
|
|
7137
|
+
return head === null ? [fork] : [fork, head];
|
|
7030
7138
|
}, config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
|
|
7031
7139
|
var init_rev = __esm(() => {
|
|
7032
7140
|
MAX_BUFFER = 256 * 1024 * 1024;
|
|
7033
7141
|
GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
7034
7142
|
MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
7143
|
+
LANE_DIFF = [
|
|
7144
|
+
"-c",
|
|
7145
|
+
"core.quotePath=false",
|
|
7146
|
+
"diff",
|
|
7147
|
+
"--no-color",
|
|
7148
|
+
"--no-ext-diff",
|
|
7149
|
+
"--no-textconv",
|
|
7150
|
+
"--no-renames"
|
|
7151
|
+
];
|
|
7035
7152
|
});
|
|
7036
7153
|
|
|
7037
7154
|
// packages/workit-core/src/store/paths.ts
|
|
@@ -8094,7 +8211,7 @@ var init_workspaces = __esm(() => {
|
|
|
8094
8211
|
}).passthrough().optional(),
|
|
8095
8212
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
8096
8213
|
defaultEndpoint: unknown().optional(),
|
|
8097
|
-
verification: _enum(["self", "independent"]).optional(),
|
|
8214
|
+
verification: _enum(["proportional", "self", "independent"]).optional(),
|
|
8098
8215
|
delegation: unknown().optional(),
|
|
8099
8216
|
trailers: boolean2().optional(),
|
|
8100
8217
|
liveSessionGuard: boolean2().optional(),
|
|
@@ -9787,7 +9904,8 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
9787
9904
|
verdict: failing?.verdict ?? null,
|
|
9788
9905
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
9789
9906
|
};
|
|
9790
|
-
const
|
|
9907
|
+
const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
|
|
9908
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
9791
9909
|
return {
|
|
9792
9910
|
branch,
|
|
9793
9911
|
head,
|
|
@@ -10068,12 +10186,709 @@ var init_ledger = __esm(() => {
|
|
|
10068
10186
|
]);
|
|
10069
10187
|
});
|
|
10070
10188
|
|
|
10071
|
-
// packages/workit-core/src/
|
|
10072
|
-
|
|
10189
|
+
// packages/workit-core/src/forge/pr-body.ts
|
|
10190
|
+
var init_pr_body = __esm(() => {
|
|
10191
|
+
init_vcs_config();
|
|
10192
|
+
});
|
|
10193
|
+
|
|
10194
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
10195
|
+
var init_pr_create = __esm(() => {
|
|
10196
|
+
init_vcs_config();
|
|
10197
|
+
init_vcs_config();
|
|
10198
|
+
init_branch();
|
|
10199
|
+
init_pr_body();
|
|
10200
|
+
});
|
|
10201
|
+
|
|
10202
|
+
// packages/workit-core/src/forge/redact.ts
|
|
10203
|
+
var KEY, ASSIGNMENT, QUOTED_ASSIGNMENT, ANSI;
|
|
10204
|
+
var init_redact = __esm(() => {
|
|
10205
|
+
KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
10206
|
+
ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
|
|
10207
|
+
QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
10208
|
+
ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
10209
|
+
});
|
|
10210
|
+
// packages/workit-core/src/git/ops.ts
|
|
10211
|
+
var COMMIT_TIMEOUT_MS;
|
|
10212
|
+
var init_ops = __esm(() => {
|
|
10213
|
+
init_branch();
|
|
10214
|
+
init_branch_policy();
|
|
10215
|
+
init_commit_flavors();
|
|
10216
|
+
init_pr_create();
|
|
10217
|
+
init_release_tracks();
|
|
10218
|
+
init_vcs_config();
|
|
10219
|
+
init_workspaces();
|
|
10220
|
+
init_redact();
|
|
10221
|
+
init_host_session();
|
|
10222
|
+
init_ledger();
|
|
10223
|
+
init_rev();
|
|
10224
|
+
COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
10225
|
+
});
|
|
10226
|
+
|
|
10227
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
10228
|
+
function patternRegex(dir, pattern) {
|
|
10229
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
10230
|
+
return null;
|
|
10231
|
+
const anchored = pattern.includes("/");
|
|
10232
|
+
const body = pattern.replace(/^\//u, "");
|
|
10233
|
+
let source = "";
|
|
10234
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
10235
|
+
const char = body[index];
|
|
10236
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
10237
|
+
const slash = body[index + 2] === "/";
|
|
10238
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
10239
|
+
index += slash ? 2 : 1;
|
|
10240
|
+
} else if (char === "*")
|
|
10241
|
+
source += "[^/]*";
|
|
10242
|
+
else if (char === "?")
|
|
10243
|
+
source += "[^/]";
|
|
10244
|
+
else if (char === "[") {
|
|
10245
|
+
const end = body.indexOf("]", index + 2);
|
|
10246
|
+
if (end < 0)
|
|
10247
|
+
return null;
|
|
10248
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
10249
|
+
source += `[${set}]`;
|
|
10250
|
+
index = end;
|
|
10251
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
10252
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
10253
|
+
index += 1;
|
|
10254
|
+
} else
|
|
10255
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
10256
|
+
}
|
|
10257
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
10258
|
+
try {
|
|
10259
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
10260
|
+
} catch {
|
|
10261
|
+
return null;
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
function rulesOf(dir, text) {
|
|
10265
|
+
const out = [];
|
|
10266
|
+
for (const raw of text.split(`
|
|
10267
|
+
`)) {
|
|
10268
|
+
const line = raw.trim();
|
|
10269
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
10270
|
+
continue;
|
|
10271
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
10272
|
+
let generated = null;
|
|
10273
|
+
for (const attribute of attributes) {
|
|
10274
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
10275
|
+
generated = true;
|
|
10276
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
10277
|
+
generated = false;
|
|
10278
|
+
}
|
|
10279
|
+
if (generated === null)
|
|
10280
|
+
continue;
|
|
10281
|
+
const regex = patternRegex(dir, pattern);
|
|
10282
|
+
if (regex)
|
|
10283
|
+
out.push({ regex, generated });
|
|
10284
|
+
}
|
|
10285
|
+
return out;
|
|
10286
|
+
}
|
|
10287
|
+
function generatedByAttributes(files, paths) {
|
|
10288
|
+
const rules = [...files].map(([file, text]) => {
|
|
10289
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
10290
|
+
return {
|
|
10291
|
+
depth: dir ? dir.split("/").length : 0,
|
|
10292
|
+
rules: rulesOf(dir, text)
|
|
10293
|
+
};
|
|
10294
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
10295
|
+
const out = new Set;
|
|
10296
|
+
for (const file of paths) {
|
|
10297
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
10298
|
+
if (last?.generated)
|
|
10299
|
+
out.add(file);
|
|
10300
|
+
}
|
|
10301
|
+
return out;
|
|
10302
|
+
}
|
|
10303
|
+
|
|
10304
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
10305
|
+
function substanceOf(file, text) {
|
|
10306
|
+
const syntax = syntaxOf(file);
|
|
10307
|
+
if (!syntax)
|
|
10308
|
+
return plainSubstance(text);
|
|
10309
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
10310
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
10311
|
+
return plainSubstance(text);
|
|
10312
|
+
return lex(syntax, text);
|
|
10313
|
+
}
|
|
10314
|
+
function plainSubstance(text) {
|
|
10315
|
+
return text.split(`
|
|
10316
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
10317
|
+
`);
|
|
10318
|
+
}
|
|
10319
|
+
function lex(syntax, text) {
|
|
10320
|
+
const collapse = !syntax.indented;
|
|
10321
|
+
let out = "";
|
|
10322
|
+
let lineStart = 0;
|
|
10323
|
+
let kept = 0;
|
|
10324
|
+
let pendingSpace = false;
|
|
10325
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
10326
|
+
let index = 0;
|
|
10327
|
+
if (text.startsWith("#!")) {
|
|
10328
|
+
const end = text.indexOf(`
|
|
10329
|
+
`);
|
|
10330
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
10331
|
+
kept = out.length;
|
|
10332
|
+
index = end < 0 ? text.length : end;
|
|
10333
|
+
}
|
|
10334
|
+
const emitCode = (chunk) => {
|
|
10335
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
10336
|
+
const before = out[out.length - 1];
|
|
10337
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
10338
|
+
out += " ";
|
|
10339
|
+
}
|
|
10340
|
+
pendingSpace = false;
|
|
10341
|
+
out += chunk;
|
|
10342
|
+
};
|
|
10343
|
+
const emitVerbatim = (chunk) => {
|
|
10344
|
+
emitCode(chunk);
|
|
10345
|
+
kept = out.length;
|
|
10346
|
+
};
|
|
10347
|
+
const newline = () => {
|
|
10348
|
+
if (!collapse) {
|
|
10349
|
+
let end = out.length;
|
|
10350
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
10351
|
+
end -= 1;
|
|
10352
|
+
out = out.slice(0, end);
|
|
10353
|
+
}
|
|
10354
|
+
pendingSpace = false;
|
|
10355
|
+
if (out.length > lineStart) {
|
|
10356
|
+
out += `
|
|
10357
|
+
`;
|
|
10358
|
+
lineStart = out.length;
|
|
10359
|
+
}
|
|
10360
|
+
};
|
|
10361
|
+
const regexAllowed = () => {
|
|
10362
|
+
let end = out.length;
|
|
10363
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
10364
|
+
end -= 1;
|
|
10365
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
10366
|
+
return true;
|
|
10367
|
+
let start = end;
|
|
10368
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
10369
|
+
start -= 1;
|
|
10370
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
10371
|
+
};
|
|
10372
|
+
const scanString = (quote, escapes, multiline) => {
|
|
10373
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
10374
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
10375
|
+
let end = index + quote.length;
|
|
10376
|
+
while (end < text.length) {
|
|
10377
|
+
const char = text[end];
|
|
10378
|
+
if (escapes && char === "\\") {
|
|
10379
|
+
end += 2;
|
|
10380
|
+
continue;
|
|
10381
|
+
}
|
|
10382
|
+
if (text.startsWith(quote, end)) {
|
|
10383
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
10384
|
+
return false;
|
|
10385
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
10386
|
+
index = end + quote.length;
|
|
10387
|
+
return true;
|
|
10388
|
+
}
|
|
10389
|
+
if (char === `
|
|
10390
|
+
` && !multiline)
|
|
10391
|
+
return false;
|
|
10392
|
+
end += 1;
|
|
10393
|
+
}
|
|
10394
|
+
return false;
|
|
10395
|
+
};
|
|
10396
|
+
while (index < text.length) {
|
|
10397
|
+
const frame = frames[frames.length - 1];
|
|
10398
|
+
const char = text[index];
|
|
10399
|
+
if (frame.kind === "template") {
|
|
10400
|
+
if (char === "\\") {
|
|
10401
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
10402
|
+
index += 2;
|
|
10403
|
+
} else if (char === "`") {
|
|
10404
|
+
emitVerbatim(char);
|
|
10405
|
+
frames.pop();
|
|
10406
|
+
index += 1;
|
|
10407
|
+
} else if (text.startsWith("${", index)) {
|
|
10408
|
+
emitVerbatim("${");
|
|
10409
|
+
frames.push({ kind: "code", braces: 0 });
|
|
10410
|
+
index += 2;
|
|
10411
|
+
} else {
|
|
10412
|
+
out += char;
|
|
10413
|
+
kept = out.length;
|
|
10414
|
+
index += 1;
|
|
10415
|
+
}
|
|
10416
|
+
continue;
|
|
10417
|
+
}
|
|
10418
|
+
if (char === `
|
|
10419
|
+
`) {
|
|
10420
|
+
newline();
|
|
10421
|
+
index += 1;
|
|
10422
|
+
continue;
|
|
10423
|
+
}
|
|
10424
|
+
if (SPACE.test(char)) {
|
|
10425
|
+
if (collapse)
|
|
10426
|
+
pendingSpace = true;
|
|
10427
|
+
else
|
|
10428
|
+
out += char;
|
|
10429
|
+
index += 1;
|
|
10430
|
+
continue;
|
|
10431
|
+
}
|
|
10432
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
10433
|
+
if (lineOpener) {
|
|
10434
|
+
const end = text.indexOf(`
|
|
10435
|
+
`, index);
|
|
10436
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
10437
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
10438
|
+
emitVerbatim(comment);
|
|
10439
|
+
else
|
|
10440
|
+
pendingSpace = true;
|
|
10441
|
+
index = end < 0 ? text.length : end;
|
|
10442
|
+
continue;
|
|
10443
|
+
}
|
|
10444
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
10445
|
+
if (block) {
|
|
10446
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
10447
|
+
if (end < 0)
|
|
10448
|
+
return null;
|
|
10449
|
+
const comment = text.slice(index, end + block[1].length);
|
|
10450
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
10451
|
+
emitVerbatim(comment);
|
|
10452
|
+
else
|
|
10453
|
+
pendingSpace = true;
|
|
10454
|
+
if (comment.includes(`
|
|
10455
|
+
`))
|
|
10456
|
+
newline();
|
|
10457
|
+
index = end + block[1].length;
|
|
10458
|
+
continue;
|
|
10459
|
+
}
|
|
10460
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
10461
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
10462
|
+
return null;
|
|
10463
|
+
continue;
|
|
10464
|
+
}
|
|
10465
|
+
if (syntax.quotes.includes(char)) {
|
|
10466
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
10467
|
+
return null;
|
|
10468
|
+
continue;
|
|
10469
|
+
}
|
|
10470
|
+
if (syntax.literal?.includes(char)) {
|
|
10471
|
+
if (!scanString(char, false, false))
|
|
10472
|
+
return null;
|
|
10473
|
+
continue;
|
|
10474
|
+
}
|
|
10475
|
+
if (syntax.rawBacktick && char === "`") {
|
|
10476
|
+
if (!scanString("`", false, true))
|
|
10477
|
+
return null;
|
|
10478
|
+
continue;
|
|
10479
|
+
}
|
|
10480
|
+
if (syntax.js && char === "`") {
|
|
10481
|
+
emitVerbatim(char);
|
|
10482
|
+
frames.push({ kind: "template" });
|
|
10483
|
+
index += 1;
|
|
10484
|
+
continue;
|
|
10485
|
+
}
|
|
10486
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
10487
|
+
return null;
|
|
10488
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
10489
|
+
const lineEnd = text.indexOf(`
|
|
10490
|
+
`, index);
|
|
10491
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
10492
|
+
return null;
|
|
10493
|
+
}
|
|
10494
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
10495
|
+
let end = index + 1;
|
|
10496
|
+
let inClass = false;
|
|
10497
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
10498
|
+
if (text[end] === `
|
|
10499
|
+
`)
|
|
10500
|
+
return null;
|
|
10501
|
+
if (text[end] === "\\")
|
|
10502
|
+
end += 1;
|
|
10503
|
+
else if (text[end] === "[")
|
|
10504
|
+
inClass = true;
|
|
10505
|
+
else if (text[end] === "]")
|
|
10506
|
+
inClass = false;
|
|
10507
|
+
end += 1;
|
|
10508
|
+
}
|
|
10509
|
+
if (end >= text.length)
|
|
10510
|
+
return null;
|
|
10511
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
10512
|
+
index = end + 1;
|
|
10513
|
+
continue;
|
|
10514
|
+
}
|
|
10515
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
10516
|
+
if (char === "{")
|
|
10517
|
+
frame.braces += 1;
|
|
10518
|
+
else if (char === "}") {
|
|
10519
|
+
if (frame.braces === 0) {
|
|
10520
|
+
emitVerbatim(char);
|
|
10521
|
+
frames.pop();
|
|
10522
|
+
index += 1;
|
|
10523
|
+
continue;
|
|
10524
|
+
}
|
|
10525
|
+
frame.braces -= 1;
|
|
10526
|
+
}
|
|
10527
|
+
}
|
|
10528
|
+
emitCode(char);
|
|
10529
|
+
index += 1;
|
|
10530
|
+
}
|
|
10531
|
+
if (frames.length !== 1)
|
|
10532
|
+
return null;
|
|
10533
|
+
newline();
|
|
10534
|
+
return out;
|
|
10535
|
+
}
|
|
10536
|
+
function unbalanced(body) {
|
|
10537
|
+
let depth = 0;
|
|
10538
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
10539
|
+
if (part === "{")
|
|
10540
|
+
depth += 1;
|
|
10541
|
+
else if (part === "}")
|
|
10542
|
+
depth = Math.max(0, depth - 1);
|
|
10543
|
+
}
|
|
10544
|
+
return depth > 0;
|
|
10545
|
+
}
|
|
10546
|
+
function hasOpaqueLineBreak(text) {
|
|
10547
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
10548
|
+
}
|
|
10549
|
+
function textOf(bytes) {
|
|
10550
|
+
if (bytes.includes(0))
|
|
10551
|
+
return null;
|
|
10552
|
+
try {
|
|
10553
|
+
return decoder.decode(bytes);
|
|
10554
|
+
} catch {
|
|
10555
|
+
return null;
|
|
10556
|
+
}
|
|
10557
|
+
}
|
|
10558
|
+
function cosmeticChange(file, before, after) {
|
|
10559
|
+
const left = textOf(before);
|
|
10560
|
+
const right = textOf(after);
|
|
10561
|
+
if (left === null || right === null)
|
|
10562
|
+
return false;
|
|
10563
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
10564
|
+
return false;
|
|
10565
|
+
const a = substanceOf(file, left);
|
|
10566
|
+
return a !== null && a === substanceOf(file, right);
|
|
10567
|
+
}
|
|
10568
|
+
var SLASH_BLOCK, C_LIKE, SYNTAXES, DIRECTIVE, extensionOf = (file) => {
|
|
10569
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
10570
|
+
const dot = name.lastIndexOf(".");
|
|
10571
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
10572
|
+
}, CI_PATH, syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null, SPACE, WORD, NO_SPACE_AFTER = "([{,;", NO_SPACE_BEFORE = ")]},;", REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^", REGEX_KEYWORDS, decoder;
|
|
10573
|
+
var init_substance = __esm(() => {
|
|
10574
|
+
SLASH_BLOCK = [["/*", "*/"]];
|
|
10575
|
+
C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
10576
|
+
SYNTAXES = Object.fromEntries([
|
|
10577
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
10578
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
10579
|
+
ext,
|
|
10580
|
+
{ ...C_LIKE, triple: true }
|
|
10581
|
+
]),
|
|
10582
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
10583
|
+
...["css", "scss", "less"].map((ext) => [
|
|
10584
|
+
ext,
|
|
10585
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
10586
|
+
]),
|
|
10587
|
+
...["py", "pyi"].map((ext) => [
|
|
10588
|
+
ext,
|
|
10589
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
10590
|
+
]),
|
|
10591
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
10592
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
10593
|
+
ext,
|
|
10594
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
10595
|
+
])
|
|
10596
|
+
]);
|
|
10597
|
+
DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
|
|
10598
|
+
CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
10599
|
+
SPACE = /[ \t\r\f\v]/u;
|
|
10600
|
+
WORD = /[\w$]/u;
|
|
10601
|
+
REGEX_KEYWORDS = new Set([
|
|
10602
|
+
"return",
|
|
10603
|
+
"typeof",
|
|
10604
|
+
"instanceof",
|
|
10605
|
+
"case",
|
|
10606
|
+
"do",
|
|
10607
|
+
"else",
|
|
10608
|
+
"in",
|
|
10609
|
+
"of",
|
|
10610
|
+
"new",
|
|
10611
|
+
"delete",
|
|
10612
|
+
"void",
|
|
10613
|
+
"throw",
|
|
10614
|
+
"yield",
|
|
10615
|
+
"await"
|
|
10616
|
+
]);
|
|
10617
|
+
decoder = new TextDecoder("utf-8", { fatal: true });
|
|
10618
|
+
});
|
|
10619
|
+
|
|
10620
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
10621
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
10073
10622
|
import path10 from "node:path";
|
|
10623
|
+
function kindOf(file) {
|
|
10624
|
+
if (file.binary)
|
|
10625
|
+
return "behavior";
|
|
10626
|
+
if (file.generated)
|
|
10627
|
+
return "generated";
|
|
10628
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
10629
|
+
return "lockfile";
|
|
10630
|
+
if (isDoc(file.path))
|
|
10631
|
+
return "doc";
|
|
10632
|
+
if (file.cosmetic)
|
|
10633
|
+
return "cosmetic";
|
|
10634
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
10635
|
+
}
|
|
10636
|
+
function globMatch(pattern, file) {
|
|
10637
|
+
let regex = globCache.get(pattern);
|
|
10638
|
+
if (!regex) {
|
|
10639
|
+
let source = "";
|
|
10640
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
10641
|
+
const char = pattern[index];
|
|
10642
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
10643
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
10644
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
10645
|
+
} else if (char === "*")
|
|
10646
|
+
source += "[^/]*";
|
|
10647
|
+
else if (char === "?")
|
|
10648
|
+
source += "[^/]";
|
|
10649
|
+
else
|
|
10650
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
10651
|
+
}
|
|
10652
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
10653
|
+
globCache.set(pattern, regex);
|
|
10654
|
+
}
|
|
10655
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
10656
|
+
}
|
|
10657
|
+
function isRiskPath(file, extra = []) {
|
|
10658
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
10659
|
+
return true;
|
|
10660
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
10661
|
+
return true;
|
|
10662
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
10663
|
+
return false;
|
|
10664
|
+
return file.split("/").map((segment, index, all) => index === all.length - 1 ? segment.replace(/\.[^.]+$/u, "") : segment).some((segment) => segment.toLowerCase().split(/[-_.]/u).some((token) => RISK_TOKENS.has(token)));
|
|
10665
|
+
}
|
|
10666
|
+
function computeLane(input) {
|
|
10667
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
10668
|
+
const risky = new Set(riskFiles);
|
|
10669
|
+
const kinds = input.files.map((file) => ({
|
|
10670
|
+
file,
|
|
10671
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
10672
|
+
}));
|
|
10673
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
10674
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
10675
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
10676
|
+
const result = (lane, reasons) => ({
|
|
10677
|
+
lane,
|
|
10678
|
+
reasons,
|
|
10679
|
+
lines,
|
|
10680
|
+
files: behavior.length,
|
|
10681
|
+
riskFiles
|
|
10682
|
+
});
|
|
10683
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
10684
|
+
if (input.riskTier === "high")
|
|
10685
|
+
return result("full", ["the task judgment records high risk"]);
|
|
10686
|
+
if (riskFiles.length)
|
|
10687
|
+
return result("full", [
|
|
10688
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
10689
|
+
]);
|
|
10690
|
+
if (!behavior.length)
|
|
10691
|
+
return result("none", [
|
|
10692
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
10693
|
+
]);
|
|
10694
|
+
if (lines >= fullMinLines)
|
|
10695
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
10696
|
+
if (lines > selfMaxLines)
|
|
10697
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
10698
|
+
if (behavior.length > selfMaxFiles)
|
|
10699
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
10700
|
+
if (opaque.length)
|
|
10701
|
+
return result("focused", [
|
|
10702
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
10703
|
+
]);
|
|
10704
|
+
return result("self", [
|
|
10705
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
10706
|
+
]);
|
|
10707
|
+
}
|
|
10708
|
+
function untrackedStat(cwd, file) {
|
|
10709
|
+
try {
|
|
10710
|
+
const bytes = readFileSync3(path10.join(cwd, file));
|
|
10711
|
+
if (bytes.includes(0))
|
|
10712
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10713
|
+
const text = bytes.toString("utf8");
|
|
10714
|
+
if (hasOpaqueLineBreak(text))
|
|
10715
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10716
|
+
const lines = text.split(`
|
|
10717
|
+
`).length - (text.endsWith(`
|
|
10718
|
+
`) ? 1 : 0);
|
|
10719
|
+
return { path: file, added: lines, deleted: 0 };
|
|
10720
|
+
} catch {
|
|
10721
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10722
|
+
}
|
|
10723
|
+
}
|
|
10724
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
10725
|
+
const raw = diffRaw(cwd, fork, head);
|
|
10726
|
+
if (!raw)
|
|
10727
|
+
return null;
|
|
10728
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
10729
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
10730
|
+
if (!blobs)
|
|
10731
|
+
return null;
|
|
10732
|
+
const out = new Set;
|
|
10733
|
+
const opaque = new Set;
|
|
10734
|
+
for (const entry of candidates) {
|
|
10735
|
+
const before = blobs.get(entry.oldBlob);
|
|
10736
|
+
let after = blobs.get(entry.newBlob);
|
|
10737
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
10738
|
+
try {
|
|
10739
|
+
after = readFileSync3(path10.join(cwd, entry.path));
|
|
10740
|
+
} catch {
|
|
10741
|
+
after = undefined;
|
|
10742
|
+
}
|
|
10743
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
10744
|
+
out.add(entry.path);
|
|
10745
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
10746
|
+
opaque.add(entry.path);
|
|
10747
|
+
}
|
|
10748
|
+
return { cosmetic: out, opaque };
|
|
10749
|
+
}
|
|
10750
|
+
function generatedAt(cwd, fork, paths) {
|
|
10751
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
10752
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
10753
|
+
if (!files || !blobs)
|
|
10754
|
+
return new Set;
|
|
10755
|
+
const texts = new Map;
|
|
10756
|
+
for (const [file, id] of files) {
|
|
10757
|
+
const bytes = blobs.get(id);
|
|
10758
|
+
if (bytes)
|
|
10759
|
+
texts.set(file, bytes.toString("utf8"));
|
|
10760
|
+
}
|
|
10761
|
+
return generatedByAttributes(texts, paths);
|
|
10762
|
+
}
|
|
10763
|
+
function laneFiles(cwd, fork, head) {
|
|
10764
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
10765
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
10766
|
+
if (!stats || !cosmetic)
|
|
10767
|
+
return null;
|
|
10768
|
+
const files = [
|
|
10769
|
+
...stats.map((entry) => ({
|
|
10770
|
+
...entry,
|
|
10771
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
10772
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
10773
|
+
})),
|
|
10774
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
10775
|
+
];
|
|
10776
|
+
const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
|
|
10777
|
+
return files.map((entry) => ({
|
|
10778
|
+
...entry,
|
|
10779
|
+
generated: generated.has(entry.path)
|
|
10780
|
+
}));
|
|
10781
|
+
}
|
|
10782
|
+
function branchLane(cwd, options) {
|
|
10783
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
10784
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
10785
|
+
const head = options.workingTree ? null : options.head;
|
|
10786
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
10787
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
10788
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
10789
|
+
if (!fork || !files) {
|
|
10790
|
+
const high = riskTier === "high";
|
|
10791
|
+
return {
|
|
10792
|
+
lane: high ? "full" : "focused",
|
|
10793
|
+
reasons: [
|
|
10794
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
10795
|
+
...high ? ["the task judgment records high risk"] : []
|
|
10796
|
+
],
|
|
10797
|
+
lines: 0,
|
|
10798
|
+
files: 0,
|
|
10799
|
+
riskFiles: [],
|
|
10800
|
+
base,
|
|
10801
|
+
head: options.head
|
|
10802
|
+
};
|
|
10803
|
+
}
|
|
10804
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
10805
|
+
return {
|
|
10806
|
+
...computeLane({
|
|
10807
|
+
files,
|
|
10808
|
+
riskTier,
|
|
10809
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
10810
|
+
}),
|
|
10811
|
+
base,
|
|
10812
|
+
head: options.head
|
|
10813
|
+
};
|
|
10814
|
+
}
|
|
10815
|
+
var LANE_THRESHOLDS, RISK_TOKENS, RISK_FILES, RISK_NAMES, RISK_PATHS_FILE = ".workit/risk-paths", LOCKFILES, DOC_EXT, DOC_NAMES, INSTRUCTIONS, TEST_PATH, basename = (file) => file.slice(file.lastIndexOf("/") + 1), isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file))), globCache, parseRiskPaths = (text) => text.split(`
|
|
10816
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, "")), REGULAR, ABSENT, forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD"), laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
10817
|
+
var init_lane = __esm(() => {
|
|
10818
|
+
init_rev();
|
|
10819
|
+
init_ops();
|
|
10820
|
+
init_ledger();
|
|
10821
|
+
init_substance();
|
|
10822
|
+
LANE_THRESHOLDS = {
|
|
10823
|
+
selfMaxLines: 100,
|
|
10824
|
+
selfMaxFiles: 5,
|
|
10825
|
+
fullMinLines: 500
|
|
10826
|
+
};
|
|
10827
|
+
RISK_TOKENS = new Set([
|
|
10828
|
+
"auth",
|
|
10829
|
+
"authn",
|
|
10830
|
+
"authz",
|
|
10831
|
+
"oauth",
|
|
10832
|
+
"security",
|
|
10833
|
+
"permission",
|
|
10834
|
+
"permissions",
|
|
10835
|
+
"secret",
|
|
10836
|
+
"secrets",
|
|
10837
|
+
"crypto",
|
|
10838
|
+
"gate",
|
|
10839
|
+
"gates",
|
|
10840
|
+
"policy",
|
|
10841
|
+
"policies",
|
|
10842
|
+
"hook",
|
|
10843
|
+
"hooks",
|
|
10844
|
+
"husky",
|
|
10845
|
+
"lefthook",
|
|
10846
|
+
"release",
|
|
10847
|
+
"releases",
|
|
10848
|
+
"migration",
|
|
10849
|
+
"migrations",
|
|
10850
|
+
"migrate",
|
|
10851
|
+
"workflows"
|
|
10852
|
+
]);
|
|
10853
|
+
RISK_FILES = new Set([
|
|
10854
|
+
".gitlab-ci.yml",
|
|
10855
|
+
"Jenkinsfile",
|
|
10856
|
+
"azure-pipelines.yml",
|
|
10857
|
+
".pre-commit-config.yaml",
|
|
10858
|
+
".workit/risk-paths"
|
|
10859
|
+
]);
|
|
10860
|
+
RISK_NAMES = new Set([".gitattributes"]);
|
|
10861
|
+
LOCKFILES = new Set([
|
|
10862
|
+
"bun.lock",
|
|
10863
|
+
"bun.lockb",
|
|
10864
|
+
"package-lock.json",
|
|
10865
|
+
"npm-shrinkwrap.json",
|
|
10866
|
+
"yarn.lock",
|
|
10867
|
+
"pnpm-lock.yaml",
|
|
10868
|
+
"Cargo.lock",
|
|
10869
|
+
"go.sum",
|
|
10870
|
+
"poetry.lock",
|
|
10871
|
+
"uv.lock",
|
|
10872
|
+
"Pipfile.lock",
|
|
10873
|
+
"Gemfile.lock",
|
|
10874
|
+
"composer.lock"
|
|
10875
|
+
]);
|
|
10876
|
+
DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
10877
|
+
DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
10878
|
+
INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
10879
|
+
TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
10880
|
+
globCache = new Map;
|
|
10881
|
+
REGULAR = new Set(["100644", "100755"]);
|
|
10882
|
+
ABSENT = /^0+$/u;
|
|
10883
|
+
});
|
|
10884
|
+
|
|
10885
|
+
// packages/workit-core/src/core/policy/derive.ts
|
|
10886
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
10887
|
+
import path11 from "node:path";
|
|
10074
10888
|
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
10075
10889
|
const out = [];
|
|
10076
10890
|
const base = { scope, dependentAction: null };
|
|
10891
|
+
const proportional = verification === "proportional";
|
|
10077
10892
|
if (judgment.behaviorChange)
|
|
10078
10893
|
out.push(derived2({
|
|
10079
10894
|
...base,
|
|
@@ -10084,7 +10899,17 @@ function deriveRequirements(judgment, scope, constraints, verification = "self")
|
|
|
10084
10899
|
before: "close",
|
|
10085
10900
|
acceptanceAllowed: false
|
|
10086
10901
|
}));
|
|
10087
|
-
if (
|
|
10902
|
+
if (proportional && judgment.riskTier === "normal")
|
|
10903
|
+
out.push(derived2({
|
|
10904
|
+
...base,
|
|
10905
|
+
ruleId: RULES.lane,
|
|
10906
|
+
dimension: "review",
|
|
10907
|
+
reason: "The review lane computed from the diff decides the verdict: none needs none, self the author's own (shown as self-reviewed), focused and full a non-author one.",
|
|
10908
|
+
satisfaction: "Read the lane in `workit policy assess` or `workit pr status`; record what it asks: `workit ledger verdict tests-verified --self --how <what you ran>` for self, or hand focused and full to a verifier.",
|
|
10909
|
+
before: "close",
|
|
10910
|
+
acceptanceAllowed: false
|
|
10911
|
+
}));
|
|
10912
|
+
if (!proportional && judgment.behaviorChange && judgment.riskTier === "normal")
|
|
10088
10913
|
out.push(verification === "independent" ? derived2({
|
|
10089
10914
|
...base,
|
|
10090
10915
|
ruleId: RULES.nonAuthor,
|
|
@@ -10163,7 +10988,9 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
10163
10988
|
case RULES.self:
|
|
10164
10989
|
case RULES.nonAuthor:
|
|
10165
10990
|
case RULES.verified:
|
|
10166
|
-
return verdict(requirement, root, ledger);
|
|
10991
|
+
return verdict(requirement.ruleId, root, ledger);
|
|
10992
|
+
case RULES.lane:
|
|
10993
|
+
return laneVerdict(task, root, ledger);
|
|
10167
10994
|
default:
|
|
10168
10995
|
return null;
|
|
10169
10996
|
}
|
|
@@ -10222,7 +11049,7 @@ var RULES, derived2 = (spec) => ({
|
|
|
10222
11049
|
decisionIds: []
|
|
10223
11050
|
};
|
|
10224
11051
|
}, planRecorded = (task, root) => {
|
|
10225
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" &&
|
|
11052
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path11.resolve(root, ref.path)));
|
|
10226
11053
|
if (cited)
|
|
10227
11054
|
return {
|
|
10228
11055
|
met: true,
|
|
@@ -10235,14 +11062,28 @@ var RULES, derived2 = (spec) => ({
|
|
|
10235
11062
|
reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
|
|
10236
11063
|
decisionIds: []
|
|
10237
11064
|
};
|
|
10238
|
-
},
|
|
11065
|
+
}, workingLane = (task, root) => branchLane(root, { head: null, workingTree: true, judgment: latestJudgment(task) }), laneVerdict = (task, root, ledger) => {
|
|
11066
|
+
if (!ledger.branch() && latestJudgment(task)?.behaviorChange === false)
|
|
11067
|
+
return {
|
|
11068
|
+
met: true,
|
|
11069
|
+
reason: "no branch to read a lane from, and the judgment records no behavior change",
|
|
11070
|
+
decisionIds: []
|
|
11071
|
+
};
|
|
11072
|
+
const lane = workingLane(task, root);
|
|
11073
|
+
const label = `lane ${lane.lane} (${lane.reasons.join("; ")})`;
|
|
11074
|
+
const need = laneNeed(lane.lane);
|
|
11075
|
+
if (need === "none")
|
|
11076
|
+
return { met: true, reason: `${label}: no verdict needed`, decisionIds: [] };
|
|
11077
|
+
const status = verdict(need === "self" ? RULES.self : RULES.nonAuthor, root, ledger);
|
|
11078
|
+
return { ...status, reason: `${label}: ${status.reason}` };
|
|
11079
|
+
}, verdict = (ruleId, root, ledger) => {
|
|
10239
11080
|
const branch = ledger.branch();
|
|
10240
11081
|
if (!branch)
|
|
10241
11082
|
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
10242
11083
|
const check = checkVerdicts(root, branch, ledger.rows());
|
|
10243
11084
|
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
10244
11085
|
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
10245
|
-
const met = !failing && (
|
|
11086
|
+
const met = !failing && (ruleId === RULES.verified ? check.verdicts.find((entry) => entry.accepted && entry.kind === "live" && String(entry.verdict.result) === "verified") : ruleId === RULES.nonAuthor ? check.verdicts.find((entry) => entry.accepted && strong(entry)) : check.verdicts.find((entry) => entry.current && strong(entry)));
|
|
10246
11087
|
if (met)
|
|
10247
11088
|
return {
|
|
10248
11089
|
met: true,
|
|
@@ -10250,18 +11091,20 @@ var RULES, derived2 = (spec) => ({
|
|
|
10250
11091
|
decisionIds: []
|
|
10251
11092
|
};
|
|
10252
11093
|
const why = failing ? "a current independent verdict fails" : check.verdicts.some((entry) => entry.current && entry.verdict.result === "type-check-only") ? "type-check-only does not prove a behavior change" : (check.accepted.reasons.join(", ") || "no_verdict").replace(/_/g, " ");
|
|
10253
|
-
const unblock =
|
|
11094
|
+
const unblock = ruleId === RULES.self ? "record your own verdict after an observed `workit check test`: `workit ledger verdict tests-verified --self --how <what you ran>` (shown as self-reviewed), or have a verifier record one" : `an independent session records \`workit ledger verdict verified${ruleId === RULES.verified ? " --kind live" : ""} --how <what it exercised>\``;
|
|
10254
11095
|
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
10255
11096
|
};
|
|
10256
11097
|
var init_derive = __esm(() => {
|
|
10257
11098
|
init_rev();
|
|
10258
11099
|
init_ledger();
|
|
11100
|
+
init_lane();
|
|
10259
11101
|
init_task_contract();
|
|
10260
11102
|
RULES = {
|
|
10261
11103
|
test: "check:test",
|
|
10262
11104
|
self: "verdict:self",
|
|
10263
11105
|
nonAuthor: "verdict:non-author",
|
|
10264
11106
|
verified: "verdict:verified",
|
|
11107
|
+
lane: "verdict:lane",
|
|
10265
11108
|
product: "decision:product",
|
|
10266
11109
|
plan: "plan"
|
|
10267
11110
|
};
|
|
@@ -10819,7 +11662,7 @@ var init_safe_write = () => {};
|
|
|
10819
11662
|
|
|
10820
11663
|
// packages/workit-core/src/autonomy.ts
|
|
10821
11664
|
import os4 from "node:os";
|
|
10822
|
-
import
|
|
11665
|
+
import path12 from "node:path";
|
|
10823
11666
|
function endpointOf(entry) {
|
|
10824
11667
|
const raw = entry?.defaultEndpoint;
|
|
10825
11668
|
if (raw === undefined)
|
|
@@ -10916,10 +11759,13 @@ var accountHome = () => {
|
|
|
10916
11759
|
} catch {
|
|
10917
11760
|
return os4.homedir();
|
|
10918
11761
|
}
|
|
10919
|
-
}, grantsHome, grantsDir = () =>
|
|
10920
|
-
const configured =
|
|
10921
|
-
return configured ===
|
|
10922
|
-
}, GRANT_KINDS, DEFAULT_GRANTS, DEFAULT_ENDPOINTS, DEFAULT_ENDPOINT = "commit", isDefaultEndpoint = (value) => typeof value === "string" && DEFAULT_ENDPOINTS.includes(value), DEFAULT_VERIFICATION = "
|
|
11762
|
+
}, grantsHome, grantsDir = () => path12.join(grantsHome.resolve(), ".config", "workit"), grantsOverride = () => {
|
|
11763
|
+
const configured = path12.resolve(resolveConfigDir());
|
|
11764
|
+
return configured === path12.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 ${path12.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
11765
|
+
}, GRANT_KINDS, DEFAULT_GRANTS, DEFAULT_ENDPOINTS, DEFAULT_ENDPOINT = "commit", isDefaultEndpoint = (value) => typeof value === "string" && DEFAULT_ENDPOINTS.includes(value), VERIFICATION_MODES, DEFAULT_VERIFICATION = "proportional", verificationOf = (entry) => {
|
|
11766
|
+
const value = entry?.verification;
|
|
11767
|
+
return VERIFICATION_MODES.includes(value) ? value : DEFAULT_VERIFICATION;
|
|
11768
|
+
}, isGrantValue = (value) => value === true || value === false || value === "verified", normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value, fromAutoApprove = (value) => {
|
|
10923
11769
|
if (value === true)
|
|
10924
11770
|
return { push: true, pr: true, merge: "verified" };
|
|
10925
11771
|
if (!Array.isArray(value))
|
|
@@ -10946,6 +11792,11 @@ var init_autonomy = __esm(() => {
|
|
|
10946
11792
|
rerun: true
|
|
10947
11793
|
});
|
|
10948
11794
|
DEFAULT_ENDPOINTS = ["commit", "pr", "green", "merged"];
|
|
11795
|
+
VERIFICATION_MODES = [
|
|
11796
|
+
"proportional",
|
|
11797
|
+
"self",
|
|
11798
|
+
"independent"
|
|
11799
|
+
];
|
|
10949
11800
|
});
|
|
10950
11801
|
|
|
10951
11802
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
@@ -11058,7 +11909,7 @@ var init_read_open_flags = () => {};
|
|
|
11058
11909
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
11059
11910
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
11060
11911
|
import fsSync from "node:fs";
|
|
11061
|
-
import
|
|
11912
|
+
import path13 from "node:path";
|
|
11062
11913
|
function parseSidecarLockSnapshot(snapshot, parser) {
|
|
11063
11914
|
return snapshot && { ...snapshot, payload: parseSidecarLockPayload(snapshot.raw, parser) };
|
|
11064
11915
|
}
|
|
@@ -11086,13 +11937,13 @@ ${ownershipToken}
|
|
|
11086
11937
|
return { raw, ownershipToken };
|
|
11087
11938
|
}
|
|
11088
11939
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
11089
|
-
const resolved =
|
|
11090
|
-
const lexicalRelative =
|
|
11091
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
11092
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
11940
|
+
const resolved = path13.resolve(lockPath);
|
|
11941
|
+
const lexicalRelative = path13.relative(lockRoot.rootDir, resolved);
|
|
11942
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path13.sep}`) && !path13.isAbsolute(lexicalRelative) ? lexicalRelative : path13.relative(lockRoot.rootReal, resolved);
|
|
11943
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path13.sep}`) || path13.isAbsolute(relative)) {
|
|
11093
11944
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
11094
11945
|
}
|
|
11095
|
-
return relative.split(
|
|
11946
|
+
return relative.split(path13.sep).join(path13.posix.sep);
|
|
11096
11947
|
}
|
|
11097
11948
|
function parseSidecarLockPayload(raw, parser) {
|
|
11098
11949
|
if (parser) {
|
|
@@ -11262,7 +12113,7 @@ function sleepSync(ms) {
|
|
|
11262
12113
|
|
|
11263
12114
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
11264
12115
|
import fs10 from "node:fs";
|
|
11265
|
-
import
|
|
12116
|
+
import path14 from "node:path";
|
|
11266
12117
|
function getSyncHeldLocks() {
|
|
11267
12118
|
const globalWithState = globalThis;
|
|
11268
12119
|
if (!globalWithState[SYNC_HELD_LOCKS_KEY]) {
|
|
@@ -11340,26 +12191,26 @@ function canonicalLockParentSync(parent) {
|
|
|
11340
12191
|
return process.platform === "win32" ? fs10.realpathSync.native(parent) : fs10.realpathSync(parent);
|
|
11341
12192
|
}
|
|
11342
12193
|
function normalizeTargetPath(targetPath) {
|
|
11343
|
-
const resolved =
|
|
11344
|
-
fs10.mkdirSync(
|
|
12194
|
+
const resolved = path14.resolve(targetPath);
|
|
12195
|
+
fs10.mkdirSync(path14.dirname(resolved), { recursive: true });
|
|
11345
12196
|
try {
|
|
11346
|
-
return
|
|
12197
|
+
return path14.join(canonicalLockParentSync(path14.dirname(resolved)), path14.basename(resolved));
|
|
11347
12198
|
} catch {
|
|
11348
12199
|
return resolved;
|
|
11349
12200
|
}
|
|
11350
12201
|
}
|
|
11351
12202
|
function boundedLockPath(lockPath, lockRoot) {
|
|
11352
|
-
const resolved =
|
|
12203
|
+
const resolved = path14.resolve(lockPath);
|
|
11353
12204
|
if (!lockRoot)
|
|
11354
12205
|
return resolved;
|
|
11355
12206
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
11356
|
-
const parent =
|
|
12207
|
+
const parent = path14.dirname(resolved);
|
|
11357
12208
|
const parentReal = canonicalLockParentSync(parent);
|
|
11358
|
-
const parentRelative =
|
|
11359
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
12209
|
+
const parentRelative = path14.relative(lockRoot.rootReal, parentReal);
|
|
12210
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path14.sep}`) || path14.isAbsolute(parentRelative)) {
|
|
11360
12211
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
11361
12212
|
}
|
|
11362
|
-
return
|
|
12213
|
+
return path14.join(parentReal, path14.basename(resolved));
|
|
11363
12214
|
}
|
|
11364
12215
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
11365
12216
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -11575,14 +12426,14 @@ var init_file_lock = __esm(() => {
|
|
|
11575
12426
|
});
|
|
11576
12427
|
|
|
11577
12428
|
// packages/workit-core/src/core/package-root.ts
|
|
11578
|
-
import { existsSync as
|
|
11579
|
-
import
|
|
12429
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
12430
|
+
import path15 from "node:path";
|
|
11580
12431
|
import { fileURLToPath } from "node:url";
|
|
11581
12432
|
var packageRoot = () => {
|
|
11582
|
-
let dir =
|
|
12433
|
+
let dir = path15.dirname(fileURLToPath(import.meta.url));
|
|
11583
12434
|
while (true) {
|
|
11584
|
-
const parent =
|
|
11585
|
-
if (
|
|
12435
|
+
const parent = path15.dirname(dir);
|
|
12436
|
+
if (existsSync4(path15.join(dir, "package.json")) || parent === dir)
|
|
11586
12437
|
return dir;
|
|
11587
12438
|
dir = parent;
|
|
11588
12439
|
}
|
|
@@ -11592,7 +12443,7 @@ var init_package_root = () => {};
|
|
|
11592
12443
|
// packages/workit-core/src/store/event-log.ts
|
|
11593
12444
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11594
12445
|
import fs11 from "node:fs";
|
|
11595
|
-
import
|
|
12446
|
+
import path16 from "node:path";
|
|
11596
12447
|
function readLog(file, from = 0, expectSeq = null) {
|
|
11597
12448
|
let fd;
|
|
11598
12449
|
try {
|
|
@@ -11665,7 +12516,7 @@ function appendEvent(file, event, validEnd) {
|
|
|
11665
12516
|
fs11.closeSync(fd);
|
|
11666
12517
|
}
|
|
11667
12518
|
if (created)
|
|
11668
|
-
fsyncDirectory(
|
|
12519
|
+
fsyncDirectory(path16.dirname(file));
|
|
11669
12520
|
return validEnd;
|
|
11670
12521
|
}
|
|
11671
12522
|
function replaceFile(file, bytes, durable) {
|
|
@@ -11681,7 +12532,7 @@ function replaceFile(file, bytes, durable) {
|
|
|
11681
12532
|
}
|
|
11682
12533
|
renameWithRetry(temporary, file);
|
|
11683
12534
|
if (durable)
|
|
11684
|
-
fsyncDirectory(
|
|
12535
|
+
fsyncDirectory(path16.dirname(file));
|
|
11685
12536
|
} catch (error) {
|
|
11686
12537
|
try {
|
|
11687
12538
|
fs11.unlinkSync(temporary);
|
|
@@ -11991,7 +12842,7 @@ var init_notes = __esm(() => {
|
|
|
11991
12842
|
// packages/workit-core/src/core/task-store.ts
|
|
11992
12843
|
import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
|
|
11993
12844
|
import * as fs12 from "node:fs";
|
|
11994
|
-
import
|
|
12845
|
+
import path17 from "node:path";
|
|
11995
12846
|
|
|
11996
12847
|
class TaskStore {
|
|
11997
12848
|
root;
|
|
@@ -12003,7 +12854,7 @@ class TaskStore {
|
|
|
12003
12854
|
migrationChecked = false;
|
|
12004
12855
|
compactDue = new Set;
|
|
12005
12856
|
constructor(root, options = {}) {
|
|
12006
|
-
const resolved =
|
|
12857
|
+
const resolved = path17.resolve(root);
|
|
12007
12858
|
this.origin = fs12.existsSync(resolved) ? fs12.realpathSync(resolved) : resolved;
|
|
12008
12859
|
this.located = resolveStore(this.origin);
|
|
12009
12860
|
this.root = checkoutRootOf(this.origin, this.located);
|
|
@@ -12012,7 +12863,7 @@ class TaskStore {
|
|
|
12012
12863
|
this.migrateOnRead = options.migrateOnRead;
|
|
12013
12864
|
}
|
|
12014
12865
|
location() {
|
|
12015
|
-
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs12.existsSync(
|
|
12866
|
+
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs12.existsSync(path17.join(this.root, ".git"))) {
|
|
12016
12867
|
this.located = undefined;
|
|
12017
12868
|
this.migrationChecked = false;
|
|
12018
12869
|
}
|
|
@@ -12168,7 +13019,7 @@ class TaskStore {
|
|
|
12168
13019
|
const moved = this.bindLocked(again.data.id, key, input.now);
|
|
12169
13020
|
return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
|
|
12170
13021
|
}
|
|
12171
|
-
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" :
|
|
13022
|
+
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path17.basename(this.root) || this.root;
|
|
12172
13023
|
const intent = {
|
|
12173
13024
|
objective: input.objective?.trim() || `Work on ${label}`,
|
|
12174
13025
|
scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
|
|
@@ -12234,14 +13085,14 @@ class TaskStore {
|
|
|
12234
13085
|
const branch = key.ok ? key.data.branch : null;
|
|
12235
13086
|
if (!branch || !location.ok)
|
|
12236
13087
|
return success(null, null, null);
|
|
12237
|
-
const common =
|
|
12238
|
-
const heads =
|
|
12239
|
-
const headLog = location.data.git ?
|
|
13088
|
+
const common = path17.dirname(location.data.dir);
|
|
13089
|
+
const heads = path17.join(common, "logs", "refs", "heads");
|
|
13090
|
+
const headLog = location.data.git ? path17.join(location.data.git.gitDir, "logs", "HEAD") : "";
|
|
12240
13091
|
const signatures = [
|
|
12241
|
-
|
|
13092
|
+
path17.join(heads, branch),
|
|
12242
13093
|
headLog,
|
|
12243
13094
|
heads,
|
|
12244
|
-
|
|
13095
|
+
path17.join(common, "packed-refs")
|
|
12245
13096
|
].map((file) => fileSignature(file) ?? "-");
|
|
12246
13097
|
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
12247
13098
|
let resolved = origins.get(slot);
|
|
@@ -12253,7 +13104,7 @@ class TaskStore {
|
|
|
12253
13104
|
aliases.push(rename.from);
|
|
12254
13105
|
const start = branchStart(location.data, branch, aliases);
|
|
12255
13106
|
const slash = start?.indexOf("/") ?? -1;
|
|
12256
|
-
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(
|
|
13107
|
+
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path17.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
|
|
12257
13108
|
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
12258
13109
|
resolved = {
|
|
12259
13110
|
signature: signatures.join("|"),
|
|
@@ -12491,13 +13342,13 @@ class TaskStore {
|
|
|
12491
13342
|
if (!ready.ok)
|
|
12492
13343
|
return ready;
|
|
12493
13344
|
const dirs = this.paths();
|
|
12494
|
-
const recovery =
|
|
13345
|
+
const recovery = path17.join(this.root, ".workit", "recovery");
|
|
12495
13346
|
if (fs12.existsSync(recovery)) {
|
|
12496
13347
|
let files = 0;
|
|
12497
13348
|
let bytes = 0;
|
|
12498
13349
|
try {
|
|
12499
13350
|
for (const name of fs12.readdirSync(recovery)) {
|
|
12500
|
-
const stat = fs12.lstatSync(
|
|
13351
|
+
const stat = fs12.lstatSync(path17.join(recovery, name));
|
|
12501
13352
|
if (!stat.isFile())
|
|
12502
13353
|
continue;
|
|
12503
13354
|
files += 1;
|
|
@@ -12550,7 +13401,7 @@ class TaskStore {
|
|
|
12550
13401
|
if (fs12.existsSync(dirs.blobs))
|
|
12551
13402
|
for (const name of fs12.readdirSync(dirs.blobs)) {
|
|
12552
13403
|
const digest = name.replace(/\.json$/, "");
|
|
12553
|
-
const file =
|
|
13404
|
+
const file = path17.join(dirs.blobs, name);
|
|
12554
13405
|
let stat;
|
|
12555
13406
|
try {
|
|
12556
13407
|
stat = fs12.lstatSync(file);
|
|
@@ -12568,7 +13419,7 @@ class TaskStore {
|
|
|
12568
13419
|
}
|
|
12569
13420
|
return success(null, null, null);
|
|
12570
13421
|
};
|
|
12571
|
-
const swept = dryRun ? sweep() : this.withLock(
|
|
13422
|
+
const swept = dryRun ? sweep() : this.withLock(path17.join(dirs.dir, "gc.lock"), sweep);
|
|
12572
13423
|
if (!swept.ok)
|
|
12573
13424
|
return swept;
|
|
12574
13425
|
const temporaryDirs = [
|
|
@@ -12576,7 +13427,7 @@ class TaskStore {
|
|
|
12576
13427
|
dirs.tasks,
|
|
12577
13428
|
dirs.blobs,
|
|
12578
13429
|
dirs.checkout,
|
|
12579
|
-
...names.map((name) =>
|
|
13430
|
+
...names.map((name) => path17.join(dirs.tasks, name))
|
|
12580
13431
|
];
|
|
12581
13432
|
for (const directory of temporaryDirs) {
|
|
12582
13433
|
if (!fs12.existsSync(directory))
|
|
@@ -12584,7 +13435,7 @@ class TaskStore {
|
|
|
12584
13435
|
for (const name of fs12.readdirSync(directory)) {
|
|
12585
13436
|
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
12586
13437
|
continue;
|
|
12587
|
-
const file =
|
|
13438
|
+
const file = path17.join(directory, name);
|
|
12588
13439
|
try {
|
|
12589
13440
|
const stat = fs12.lstatSync(file);
|
|
12590
13441
|
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
@@ -12601,30 +13452,30 @@ class TaskStore {
|
|
|
12601
13452
|
}
|
|
12602
13453
|
paths() {
|
|
12603
13454
|
const location = this.location();
|
|
12604
|
-
const dir = location.ok ? location.data.dir :
|
|
12605
|
-
const checkout =
|
|
13455
|
+
const dir = location.ok ? location.data.dir : path17.join(this.root, ".workit");
|
|
13456
|
+
const checkout = path17.join(dir, "checkouts", checkoutSlug(this.root));
|
|
12606
13457
|
return {
|
|
12607
13458
|
dir,
|
|
12608
13459
|
shared: location.ok ? location.data.shared : false,
|
|
12609
|
-
marker:
|
|
13460
|
+
marker: path17.join(dir, "store.json"),
|
|
12610
13461
|
checkout,
|
|
12611
|
-
workspace:
|
|
12612
|
-
checkoutLock:
|
|
12613
|
-
tasks:
|
|
12614
|
-
index:
|
|
12615
|
-
blobs:
|
|
12616
|
-
legacy:
|
|
12617
|
-
v2:
|
|
13462
|
+
workspace: path17.join(checkout, "workspace.json"),
|
|
13463
|
+
checkoutLock: path17.join(checkout, "metadata.lock"),
|
|
13464
|
+
tasks: path17.join(dir, "tasks"),
|
|
13465
|
+
index: path17.join(dir, "task-index.json"),
|
|
13466
|
+
blobs: path17.join(dir, "blobs", "candidates"),
|
|
13467
|
+
legacy: path17.join(dir, "legacy", checkoutSlug(this.root), "v2"),
|
|
13468
|
+
v2: path17.join(this.root, ".workit")
|
|
12618
13469
|
};
|
|
12619
13470
|
}
|
|
12620
13471
|
eventsPath(taskId) {
|
|
12621
|
-
return
|
|
13472
|
+
return path17.join(this.paths().tasks, taskId, "events.jsonl");
|
|
12622
13473
|
}
|
|
12623
13474
|
snapshotPath(taskId) {
|
|
12624
|
-
return
|
|
13475
|
+
return path17.join(this.paths().tasks, taskId, "snapshot.json");
|
|
12625
13476
|
}
|
|
12626
13477
|
taskLockPath(taskId) {
|
|
12627
|
-
return
|
|
13478
|
+
return path17.join(this.paths().tasks, taskId, "lock");
|
|
12628
13479
|
}
|
|
12629
13480
|
ready(write = false) {
|
|
12630
13481
|
const location = this.location();
|
|
@@ -12663,13 +13514,13 @@ class TaskStore {
|
|
|
12663
13514
|
return {
|
|
12664
13515
|
read: (digest) => {
|
|
12665
13516
|
try {
|
|
12666
|
-
return JSON.parse(fs12.readFileSync(
|
|
13517
|
+
return JSON.parse(fs12.readFileSync(path17.join(dir, `${digest}.json`), "utf8"));
|
|
12667
13518
|
} catch (error) {
|
|
12668
13519
|
throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
|
|
12669
13520
|
}
|
|
12670
13521
|
},
|
|
12671
13522
|
write: (digest, value) => {
|
|
12672
|
-
const file =
|
|
13523
|
+
const file = path17.join(dir, `${digest}.json`);
|
|
12673
13524
|
if (fs12.existsSync(file)) {
|
|
12674
13525
|
const stamp = new Date;
|
|
12675
13526
|
try {
|
|
@@ -12861,7 +13712,7 @@ class TaskStore {
|
|
|
12861
13712
|
}
|
|
12862
13713
|
}
|
|
12863
13714
|
openTask(record, type, key, legacy = null) {
|
|
12864
|
-
const dir =
|
|
13715
|
+
const dir = path17.join(this.paths().tasks, record.id);
|
|
12865
13716
|
fs12.mkdirSync(dir, { recursive: true });
|
|
12866
13717
|
const blobs = this.blobStore();
|
|
12867
13718
|
const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
|
|
@@ -12954,7 +13805,7 @@ class TaskStore {
|
|
|
12954
13805
|
}
|
|
12955
13806
|
withKeyLock(key, operation) {
|
|
12956
13807
|
const digest = createHash11("sha256").update(key).digest("hex").slice(0, 32);
|
|
12957
|
-
return this.withLock(
|
|
13808
|
+
return this.withLock(path17.join(this.paths().dir, "keys", `${digest}.lock`), operation);
|
|
12958
13809
|
}
|
|
12959
13810
|
bindLocked(taskId, key, timestamp) {
|
|
12960
13811
|
const current = this.readWorkspace();
|
|
@@ -13095,7 +13946,7 @@ class TaskStore {
|
|
|
13095
13946
|
writeWorkspace(record) {
|
|
13096
13947
|
const file = this.paths().workspace;
|
|
13097
13948
|
try {
|
|
13098
|
-
fs12.mkdirSync(
|
|
13949
|
+
fs12.mkdirSync(path17.dirname(file), { recursive: true });
|
|
13099
13950
|
replaceFile(file, `${canonicalJson(record)}
|
|
13100
13951
|
`, true);
|
|
13101
13952
|
return success(null, null, null);
|
|
@@ -13173,23 +14024,23 @@ class TaskStore {
|
|
|
13173
14024
|
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
13174
14025
|
}
|
|
13175
14026
|
v2Dirs() {
|
|
13176
|
-
return [...new Set([this.root, this.origin])].map((dir) =>
|
|
14027
|
+
return [...new Set([this.root, this.origin])].map((dir) => path17.join(dir, ".workit"));
|
|
13177
14028
|
}
|
|
13178
14029
|
pendingMigrations() {
|
|
13179
14030
|
return this.v2Dirs().filter((dir) => {
|
|
13180
|
-
const raw = this.readV2Workspace(
|
|
14031
|
+
const raw = this.readV2Workspace(path17.join(dir, "workspace.json"));
|
|
13181
14032
|
return raw !== null && raw !== TOMBSTONE;
|
|
13182
14033
|
});
|
|
13183
14034
|
}
|
|
13184
14035
|
migrateV2(v2) {
|
|
13185
14036
|
const dirs = this.paths();
|
|
13186
|
-
const v2Workspace =
|
|
13187
|
-
const v2Tasks =
|
|
13188
|
-
const legacy =
|
|
14037
|
+
const v2Workspace = path17.join(v2, "workspace.json");
|
|
14038
|
+
const v2Tasks = path17.join(v2, "tasks");
|
|
14039
|
+
const legacy = path17.join(dirs.dir, "legacy", checkoutSlug(path17.dirname(v2)), "v2");
|
|
13189
14040
|
const raw = this.readV2Workspace(v2Workspace);
|
|
13190
14041
|
if (raw === null || raw === TOMBSTONE)
|
|
13191
14042
|
return success(null, null, null);
|
|
13192
|
-
return this.withLock(dirs.checkoutLock, () => this.withLock(
|
|
14043
|
+
return this.withLock(dirs.checkoutLock, () => this.withLock(path17.join(v2, "metadata.lock"), () => {
|
|
13193
14044
|
const workspace = this.readV2Workspace(v2Workspace);
|
|
13194
14045
|
if (workspace === null || workspace === TOMBSTONE)
|
|
13195
14046
|
return success(null, null, null);
|
|
@@ -13198,7 +14049,7 @@ class TaskStore {
|
|
|
13198
14049
|
return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
|
|
13199
14050
|
let files = [];
|
|
13200
14051
|
try {
|
|
13201
|
-
files = fs12.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(
|
|
14052
|
+
files = fs12.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path17.join(v2Tasks, name)));
|
|
13202
14053
|
} catch {}
|
|
13203
14054
|
const current = this.readWorkspace();
|
|
13204
14055
|
if (!current.ok)
|
|
@@ -13216,21 +14067,21 @@ class TaskStore {
|
|
|
13216
14067
|
let skipped = 0;
|
|
13217
14068
|
const done = new Map;
|
|
13218
14069
|
try {
|
|
13219
|
-
fs12.mkdirSync(
|
|
14070
|
+
fs12.mkdirSync(path17.join(legacy, "tasks"), { recursive: true });
|
|
13220
14071
|
for (const name of ["workspace.json", "index.json"])
|
|
13221
|
-
if (fs12.existsSync(
|
|
13222
|
-
fs12.copyFileSync(
|
|
14072
|
+
if (fs12.existsSync(path17.join(v2, name)))
|
|
14073
|
+
fs12.copyFileSync(path17.join(v2, name), path17.join(legacy, name));
|
|
13223
14074
|
for (const name of files) {
|
|
13224
14075
|
const id = name.slice(0, -5);
|
|
13225
|
-
const file =
|
|
14076
|
+
const file = path17.join(v2Tasks, name);
|
|
13226
14077
|
const bytes = fs12.readFileSync(file, "utf8");
|
|
13227
14078
|
const digest = sha256(bytes);
|
|
13228
|
-
fs12.writeFileSync(
|
|
14079
|
+
fs12.writeFileSync(path17.join(legacy, "tasks", name), bytes);
|
|
13229
14080
|
const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
|
|
13230
14081
|
if (!parsed.ok || parsed.data.id !== id)
|
|
13231
14082
|
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 });
|
|
13232
14083
|
const record = { ...parsed.data, workspaceId: target.id };
|
|
13233
|
-
const origin = { path:
|
|
14084
|
+
const origin = { path: path17.join(legacy, "tasks", name), digest };
|
|
13234
14085
|
if (!fs12.existsSync(this.eventsPath(id))) {
|
|
13235
14086
|
this.openTask(record, "migrated.from_v2", null, origin);
|
|
13236
14087
|
migrated += 1;
|
|
@@ -13266,7 +14117,7 @@ class TaskStore {
|
|
|
13266
14117
|
try {
|
|
13267
14118
|
let complete = true;
|
|
13268
14119
|
for (const [name, digest] of done) {
|
|
13269
|
-
const file =
|
|
14120
|
+
const file = path17.join(v2Tasks, name);
|
|
13270
14121
|
if (sha256(fs12.readFileSync(file, "utf8")) !== digest) {
|
|
13271
14122
|
complete = false;
|
|
13272
14123
|
continue;
|
|
@@ -13277,7 +14128,7 @@ class TaskStore {
|
|
|
13277
14128
|
return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
|
|
13278
14129
|
path: v2
|
|
13279
14130
|
});
|
|
13280
|
-
fs12.rmSync(
|
|
14131
|
+
fs12.rmSync(path17.join(v2, "index.json"), { force: true });
|
|
13281
14132
|
this.writeTombstone(v2, target.id);
|
|
13282
14133
|
} catch (error) {
|
|
13283
14134
|
return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
|
|
@@ -13297,7 +14148,7 @@ class TaskStore {
|
|
|
13297
14148
|
}
|
|
13298
14149
|
adoptLocalStore() {
|
|
13299
14150
|
const dirs = this.paths();
|
|
13300
|
-
const marker =
|
|
14151
|
+
const marker = path17.join(dirs.v2, "store.json");
|
|
13301
14152
|
if (!dirs.shared || !fs12.existsSync(marker))
|
|
13302
14153
|
return success(null, null, null);
|
|
13303
14154
|
return this.withLock(dirs.checkoutLock, () => {
|
|
@@ -13307,7 +14158,7 @@ class TaskStore {
|
|
|
13307
14158
|
try {
|
|
13308
14159
|
const merge = (from, to) => {
|
|
13309
14160
|
if (!fs12.existsSync(to)) {
|
|
13310
|
-
fs12.mkdirSync(
|
|
14161
|
+
fs12.mkdirSync(path17.dirname(to), { recursive: true });
|
|
13311
14162
|
try {
|
|
13312
14163
|
fs12.renameSync(from, to);
|
|
13313
14164
|
} catch {
|
|
@@ -13318,24 +14169,24 @@ class TaskStore {
|
|
|
13318
14169
|
}
|
|
13319
14170
|
if (fs12.lstatSync(from).isDirectory() && fs12.lstatSync(to).isDirectory())
|
|
13320
14171
|
for (const name of fs12.readdirSync(from))
|
|
13321
|
-
merge(
|
|
14172
|
+
merge(path17.join(from, name), path17.join(to, name));
|
|
13322
14173
|
};
|
|
13323
14174
|
for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
|
|
13324
|
-
const from =
|
|
14175
|
+
const from = path17.join(dirs.v2, sub);
|
|
13325
14176
|
if (!fs12.existsSync(from))
|
|
13326
14177
|
continue;
|
|
13327
14178
|
if (sub === "tasks") {
|
|
13328
14179
|
for (const name of fs12.readdirSync(from))
|
|
13329
|
-
if (validId(name) && !fs12.existsSync(
|
|
14180
|
+
if (validId(name) && !fs12.existsSync(path17.join(dirs.dir, sub, name)))
|
|
13330
14181
|
tasks += 1;
|
|
13331
14182
|
}
|
|
13332
|
-
merge(from,
|
|
14183
|
+
merge(from, path17.join(dirs.dir, sub));
|
|
13333
14184
|
}
|
|
13334
|
-
const ledger =
|
|
13335
|
-
if (fs12.existsSync(ledger) && !fs12.existsSync(
|
|
13336
|
-
merge(ledger,
|
|
13337
|
-
fs12.rmSync(
|
|
13338
|
-
fs12.renameSync(marker,
|
|
14185
|
+
const ledger = path17.join(dirs.v2, "ledger");
|
|
14186
|
+
if (fs12.existsSync(ledger) && !fs12.existsSync(path17.join(dirs.dir, "ledger")))
|
|
14187
|
+
merge(ledger, path17.join(dirs.dir, "ledger"));
|
|
14188
|
+
fs12.rmSync(path17.join(dirs.v2, "task-index.json"), { force: true });
|
|
14189
|
+
fs12.renameSync(marker, path17.join(dirs.v2, "store.moved.json"));
|
|
13339
14190
|
} catch (error) {
|
|
13340
14191
|
return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
|
|
13341
14192
|
path: dirs.v2
|
|
@@ -13373,7 +14224,7 @@ class TaskStore {
|
|
|
13373
14224
|
schemaVersion: SCHEMA_VERSION,
|
|
13374
14225
|
id: workspaceId ?? newId(),
|
|
13375
14226
|
revision: newRevision(),
|
|
13376
|
-
root:
|
|
14227
|
+
root: path17.dirname(v2),
|
|
13377
14228
|
runtime: { createdWith: version, updatedWith: version },
|
|
13378
14229
|
writer: null,
|
|
13379
14230
|
store: {
|
|
@@ -13385,11 +14236,11 @@ class TaskStore {
|
|
|
13385
14236
|
critical: ["store"]
|
|
13386
14237
|
};
|
|
13387
14238
|
fs12.mkdirSync(v2, { recursive: true });
|
|
13388
|
-
const ignore =
|
|
14239
|
+
const ignore = path17.join(v2, ".gitignore");
|
|
13389
14240
|
if (!fs12.existsSync(ignore))
|
|
13390
14241
|
fs12.writeFileSync(ignore, `*
|
|
13391
14242
|
`);
|
|
13392
|
-
replaceFile(
|
|
14243
|
+
replaceFile(path17.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
|
|
13393
14244
|
`, true);
|
|
13394
14245
|
}
|
|
13395
14246
|
stubFor(taskId) {
|
|
@@ -13413,8 +14264,8 @@ class TaskStore {
|
|
|
13413
14264
|
}
|
|
13414
14265
|
writeTombstoneIfMissing() {
|
|
13415
14266
|
try {
|
|
13416
|
-
const v2 =
|
|
13417
|
-
if (fs12.existsSync(
|
|
14267
|
+
const v2 = path17.join(this.root, ".workit");
|
|
14268
|
+
if (fs12.existsSync(path17.join(v2, "workspace.json")))
|
|
13418
14269
|
return;
|
|
13419
14270
|
let id = null;
|
|
13420
14271
|
try {
|
|
@@ -13431,7 +14282,7 @@ class TaskStore {
|
|
|
13431
14282
|
replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
|
|
13432
14283
|
`, false);
|
|
13433
14284
|
if (!dirs.shared) {
|
|
13434
|
-
const ignore =
|
|
14285
|
+
const ignore = path17.join(dirs.dir, ".gitignore");
|
|
13435
14286
|
let current = null;
|
|
13436
14287
|
try {
|
|
13437
14288
|
current = fs12.readFileSync(ignore, "utf8");
|
|
@@ -13491,7 +14342,7 @@ class TaskStore {
|
|
|
13491
14342
|
withLock(lockPath, operation) {
|
|
13492
14343
|
try {
|
|
13493
14344
|
this.initializeStorage();
|
|
13494
|
-
fs12.mkdirSync(
|
|
14345
|
+
fs12.mkdirSync(path17.dirname(lockPath), { recursive: true });
|
|
13495
14346
|
} catch (error) {
|
|
13496
14347
|
return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
13497
14348
|
path: this.paths().dir
|
|
@@ -13554,7 +14405,7 @@ class TaskStore {
|
|
|
13554
14405
|
if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
|
|
13555
14406
|
return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
|
|
13556
14407
|
path: lockPath,
|
|
13557
|
-
guidance: `Check permissions and the read-only attribute on ${
|
|
14408
|
+
guidance: `Check permissions and the read-only attribute on ${path17.dirname(lockPath)}.`
|
|
13558
14409
|
});
|
|
13559
14410
|
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
13560
14411
|
let lock = null;
|
|
@@ -13612,7 +14463,7 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
|
|
|
13612
14463
|
}, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), oldestFirst = (left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id), cachedRuntimeVersion = null, runtimeVersion = () => {
|
|
13613
14464
|
if (cachedRuntimeVersion === null) {
|
|
13614
14465
|
try {
|
|
13615
|
-
const pkg = JSON.parse(fs12.readFileSync(
|
|
14466
|
+
const pkg = JSON.parse(fs12.readFileSync(path17.join(packageRoot(), "package.json"), "utf8"));
|
|
13616
14467
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
13617
14468
|
} catch {
|
|
13618
14469
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -13639,7 +14490,7 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
|
|
|
13639
14490
|
return "";
|
|
13640
14491
|
}
|
|
13641
14492
|
}, REFLOG_LINE, branchStart = (location, branch, aliases) => {
|
|
13642
|
-
const first = readText(
|
|
14493
|
+
const first = readText(path17.join(path17.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
13643
14494
|
`, 1)[0];
|
|
13644
14495
|
const created = REFLOG_LINE.exec(first);
|
|
13645
14496
|
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
@@ -13649,7 +14500,7 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
|
|
|
13649
14500
|
return start;
|
|
13650
14501
|
if (!location.git)
|
|
13651
14502
|
return null;
|
|
13652
|
-
for (const line of readText(
|
|
14503
|
+
for (const line of readText(path17.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
13653
14504
|
`)) {
|
|
13654
14505
|
const entry = REFLOG_LINE.exec(line);
|
|
13655
14506
|
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
@@ -13658,12 +14509,12 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
|
|
|
13658
14509
|
return moved[1];
|
|
13659
14510
|
}
|
|
13660
14511
|
return null;
|
|
13661
|
-
}, refExists = (common, ref) => fs12.existsSync(
|
|
14512
|
+
}, refExists = (common, ref) => fs12.existsSync(path17.join(common, ref)) || readText(path17.join(common, "packed-refs")).includes(` ${ref}
|
|
13662
14513
|
`), origins, sameDirectoryIdentity = (left, right) => {
|
|
13663
|
-
if (!
|
|
14514
|
+
if (!path17.isAbsolute(left) || !path17.isAbsolute(right))
|
|
13664
14515
|
return false;
|
|
13665
|
-
const normalizedLeft =
|
|
13666
|
-
const normalizedRight =
|
|
14516
|
+
const normalizedLeft = path17.resolve(left);
|
|
14517
|
+
const normalizedRight = path17.resolve(right);
|
|
13667
14518
|
try {
|
|
13668
14519
|
const a = fs12.statSync(normalizedLeft, { bigint: true });
|
|
13669
14520
|
const b = fs12.statSync(normalizedRight, { bigint: true });
|
|
@@ -13721,10 +14572,10 @@ var init_workers = __esm(() => {
|
|
|
13721
14572
|
// packages/workit-core/src/check-config.ts
|
|
13722
14573
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
13723
14574
|
import fs13 from "node:fs";
|
|
13724
|
-
import
|
|
14575
|
+
import path18 from "node:path";
|
|
13725
14576
|
function checksConfigFile(root) {
|
|
13726
|
-
const current =
|
|
13727
|
-
const legacy =
|
|
14577
|
+
const current = path18.join(root, CHECKS_FILE);
|
|
14578
|
+
const legacy = path18.join(root, LEGACY_CHECKS_FILE);
|
|
13728
14579
|
return !fs13.existsSync(current) && fs13.existsSync(legacy) ? legacy : current;
|
|
13729
14580
|
}
|
|
13730
14581
|
function repoHeavyCommands(root) {
|
|
@@ -13774,12 +14625,12 @@ function splitCommand(command) {
|
|
|
13774
14625
|
return argv.length ? argv : new Error("empty command");
|
|
13775
14626
|
}
|
|
13776
14627
|
function checkRoot(cwd) {
|
|
13777
|
-
return
|
|
14628
|
+
return path18.resolve(git5(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
13778
14629
|
}
|
|
13779
14630
|
function detectedChecks(root) {
|
|
13780
14631
|
const read = (file) => {
|
|
13781
14632
|
try {
|
|
13782
|
-
return fs13.readFileSync(
|
|
14633
|
+
return fs13.readFileSync(path18.join(root, file), "utf8");
|
|
13783
14634
|
} catch {
|
|
13784
14635
|
return null;
|
|
13785
14636
|
}
|
|
@@ -13818,7 +14669,7 @@ function detectedChecks(root) {
|
|
|
13818
14669
|
function loadCheckConfig(cwd) {
|
|
13819
14670
|
const root = checkRoot(cwd);
|
|
13820
14671
|
const file = checksConfigFile(root);
|
|
13821
|
-
const legacy =
|
|
14672
|
+
const legacy = path18.basename(file) === LEGACY_CHECKS_FILE;
|
|
13822
14673
|
const label = legacy ? LEGACY_CHECKS_FILE : CHECKS_FILE;
|
|
13823
14674
|
const note = legacy ? {
|
|
13824
14675
|
note: `${LEGACY_CHECKS_FILE} is deprecated; move it to ${CHECKS_FILE} (workit doctor --fix)`
|
|
@@ -13894,10 +14745,10 @@ function workitAvailable(env = process.env) {
|
|
|
13894
14745
|
return workitOnPath;
|
|
13895
14746
|
const windows = process.platform === "win32";
|
|
13896
14747
|
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
13897
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(
|
|
14748
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path18.delimiter).filter(Boolean);
|
|
13898
14749
|
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
13899
14750
|
try {
|
|
13900
|
-
return fs13.statSync(
|
|
14751
|
+
return fs13.statSync(path18.join(dir, `workit${ext}`)).isFile();
|
|
13901
14752
|
} catch {
|
|
13902
14753
|
return false;
|
|
13903
14754
|
}
|
|
@@ -13907,7 +14758,7 @@ function workitAvailable(env = process.env) {
|
|
|
13907
14758
|
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
13908
14759
|
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
13909
14760
|
}
|
|
13910
|
-
var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META,
|
|
14761
|
+
var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git5 = (cwd, args) => {
|
|
13911
14762
|
const run = spawnSync9("git", args, {
|
|
13912
14763
|
cwd,
|
|
13913
14764
|
encoding: "utf8",
|
|
@@ -13918,7 +14769,7 @@ var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "wo
|
|
|
13918
14769
|
const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
|
|
13919
14770
|
return value || null;
|
|
13920
14771
|
}, packageManager = (root) => {
|
|
13921
|
-
const has = (file) => fs13.existsSync(
|
|
14772
|
+
const has = (file) => fs13.existsSync(path18.join(root, file));
|
|
13922
14773
|
if (has("bun.lock") || has("bun.lockb"))
|
|
13923
14774
|
return "bun";
|
|
13924
14775
|
if (has("pnpm-lock.yaml"))
|
|
@@ -13944,7 +14795,7 @@ var init_check_config = __esm(() => {
|
|
|
13944
14795
|
import { createHash as createHash12 } from "node:crypto";
|
|
13945
14796
|
import * as fs14 from "node:fs";
|
|
13946
14797
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
13947
|
-
import
|
|
14798
|
+
import path19 from "node:path";
|
|
13948
14799
|
function captureCandidate(root, scope, environment = []) {
|
|
13949
14800
|
let checkout;
|
|
13950
14801
|
try {
|
|
@@ -13986,7 +14837,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
13986
14837
|
for (const item of [...names].toSorted()) {
|
|
13987
14838
|
if (!scopeMatches(item, normalizedScope))
|
|
13988
14839
|
continue;
|
|
13989
|
-
const target =
|
|
14840
|
+
const target = path19.join(checkout, item);
|
|
13990
14841
|
try {
|
|
13991
14842
|
const stat = fs14.lstatSync(target);
|
|
13992
14843
|
if (stat.isSymbolicLink()) {
|
|
@@ -14395,12 +15246,12 @@ function boundTaskSummary(summary) {
|
|
|
14395
15246
|
brief.truncated = JSON.stringify(brief).includes("…") || progress.blockers.length > BRIEF_BLOCKERS || unmet.length > BRIEF_OPEN;
|
|
14396
15247
|
return brief;
|
|
14397
15248
|
}
|
|
14398
|
-
var digestBytes = (value) => createHash12("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
15249
|
+
var digestBytes = (value) => createHash12("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path19.sep}`), relative = (root, candidate) => path19.relative(root, candidate).split(path19.sep).join("/") || ".", canonicalPath = (value) => {
|
|
14399
15250
|
if (typeof value !== "string" || !value)
|
|
14400
15251
|
return null;
|
|
14401
15252
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14402
15253
|
return null;
|
|
14403
|
-
const normalized =
|
|
15254
|
+
const normalized = path19.posix.normalize(value);
|
|
14404
15255
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14405
15256
|
return null;
|
|
14406
15257
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14442,14 +15293,14 @@ var digestBytes = (value) => createHash12("sha256").update(value).digest("hex"),
|
|
|
14442
15293
|
git: !/not a git repository/i.test(stderr)
|
|
14443
15294
|
};
|
|
14444
15295
|
}
|
|
14445
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15296
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")) : [];
|
|
14446
15297
|
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14447
15298
|
if (stagedDeleted.status !== 0) {
|
|
14448
15299
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14449
15300
|
if (!/not a git repository/i.test(stderr))
|
|
14450
15301
|
return { paths, uncertain: true, git: true };
|
|
14451
15302
|
} else if (stagedDeleted.stdout) {
|
|
14452
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15303
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")));
|
|
14453
15304
|
}
|
|
14454
15305
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14455
15306
|
}, walk = (root, directory, output) => {
|
|
@@ -14463,7 +15314,7 @@ var digestBytes = (value) => createHash12("sha256").update(value).digest("hex"),
|
|
|
14463
15314
|
for (const name of names) {
|
|
14464
15315
|
if (name === ".git" || name === ".workit")
|
|
14465
15316
|
continue;
|
|
14466
|
-
const target =
|
|
15317
|
+
const target = path19.join(directory, name);
|
|
14467
15318
|
let stat;
|
|
14468
15319
|
try {
|
|
14469
15320
|
stat = fs14.lstatSync(target);
|
|
@@ -14480,12 +15331,12 @@ var digestBytes = (value) => createHash12("sha256").update(value).digest("hex"),
|
|
|
14480
15331
|
}, scopeRoots = (root, scope) => {
|
|
14481
15332
|
const roots = [];
|
|
14482
15333
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14483
|
-
const target =
|
|
15334
|
+
const target = path19.resolve(root, item);
|
|
14484
15335
|
if (!inside(root, target))
|
|
14485
15336
|
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` }] });
|
|
14486
15337
|
let ancestor = target;
|
|
14487
15338
|
while (!fs14.existsSync(ancestor) && ancestor !== root)
|
|
14488
|
-
ancestor =
|
|
15339
|
+
ancestor = path19.dirname(ancestor);
|
|
14489
15340
|
try {
|
|
14490
15341
|
if (!inside(root, fs14.realpathSync(ancestor)))
|
|
14491
15342
|
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` }] });
|
|
@@ -14549,23 +15400,23 @@ var digestBytes = (value) => createHash12("sha256").update(value).digest("hex"),
|
|
|
14549
15400
|
continue;
|
|
14550
15401
|
if (!reference.digest)
|
|
14551
15402
|
return failure2("invalid_input", "document references require a byte digest");
|
|
14552
|
-
const target =
|
|
14553
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15403
|
+
const target = path19.resolve(root, reference.path);
|
|
15404
|
+
if (target !== root && !target.startsWith(`${root}${path19.sep}`))
|
|
14554
15405
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14555
15406
|
fields: [
|
|
14556
15407
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
14557
15408
|
]
|
|
14558
15409
|
});
|
|
14559
15410
|
try {
|
|
14560
|
-
const below =
|
|
15411
|
+
const below = path19.relative(root, target);
|
|
14561
15412
|
let current = root;
|
|
14562
|
-
for (const segment of below.split(
|
|
15413
|
+
for (const segment of below.split(path19.sep)) {
|
|
14563
15414
|
if (!segment)
|
|
14564
15415
|
continue;
|
|
14565
|
-
current =
|
|
15416
|
+
current = path19.join(current, segment);
|
|
14566
15417
|
const stat = fs14.lstatSync(current);
|
|
14567
15418
|
const real = fs14.realpathSync(current);
|
|
14568
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15419
|
+
if (real !== root && !real.startsWith(`${root}${path19.sep}`))
|
|
14569
15420
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14570
15421
|
fields: [
|
|
14571
15422
|
{
|
|
@@ -14693,9 +15544,9 @@ Autonomy contract:
|
|
|
14693
15544
|
Prose is a note: only a configured check the CLI ran (\`workit check <name>\`)
|
|
14694
15545
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
14695
15546
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
14696
|
-
self-reviewed, never verified. High risk,
|
|
14697
|
-
merge need a verifier with its
|
|
14698
|
-
\`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
15547
|
+
self-reviewed, never verified. High risk, a focused or full lane and
|
|
15548
|
+
merge under \`verification: self|independent\` need a verifier with its
|
|
15549
|
+
own host-derived session (a subagent's \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
14699
15550
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
14700
15551
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
14701
15552
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -16566,7 +17417,7 @@ var support = (descriptor, axis) => {
|
|
|
16566
17417
|
// packages/workit-core/src/hooks/context.ts
|
|
16567
17418
|
import { createHash as createHash13 } from "node:crypto";
|
|
16568
17419
|
import fs15 from "node:fs";
|
|
16569
|
-
import
|
|
17420
|
+
import path20 from "node:path";
|
|
16570
17421
|
function sessionCompactContext(store, session, context, selection = "session-bound") {
|
|
16571
17422
|
const listed = store.listTaskIndex();
|
|
16572
17423
|
if (!listed.ok)
|
|
@@ -16601,7 +17452,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
16601
17452
|
return null;
|
|
16602
17453
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
16603
17454
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
16604
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
17455
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path20.resolve(store.root, ref.path)] : []));
|
|
16605
17456
|
const observedFiles = filesKey(files);
|
|
16606
17457
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
16607
17458
|
if (!compact.ok || !compact.data)
|
|
@@ -16707,13 +17558,13 @@ ${options.addendum}` : ""}
|
|
|
16707
17558
|
if (location instanceof Error)
|
|
16708
17559
|
return null;
|
|
16709
17560
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
16710
|
-
return
|
|
17561
|
+
return path20.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
16711
17562
|
} catch {
|
|
16712
17563
|
return null;
|
|
16713
17564
|
}
|
|
16714
17565
|
}, writeTurnDigest = (file, digest) => {
|
|
16715
17566
|
try {
|
|
16716
|
-
fs15.mkdirSync(
|
|
17567
|
+
fs15.mkdirSync(path20.dirname(file), { recursive: true });
|
|
16717
17568
|
fs15.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
16718
17569
|
`);
|
|
16719
17570
|
} catch {}
|
|
@@ -16726,7 +17577,7 @@ ${options.addendum}` : ""}
|
|
|
16726
17577
|
for (const name of fs15.readdirSync(dir)) {
|
|
16727
17578
|
if (!name.startsWith("turn-"))
|
|
16728
17579
|
continue;
|
|
16729
|
-
const file =
|
|
17580
|
+
const file = path20.join(dir, name);
|
|
16730
17581
|
try {
|
|
16731
17582
|
if (now - fs15.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
16732
17583
|
fs15.rmSync(file, { force: true });
|
|
@@ -16754,7 +17605,7 @@ ${options.addendum}` : ""}
|
|
|
16754
17605
|
const file = turnMarker(input);
|
|
16755
17606
|
if (!file)
|
|
16756
17607
|
return;
|
|
16757
|
-
pruneTurnDigests(
|
|
17608
|
+
pruneTurnDigests(path20.dirname(file), Date.now());
|
|
16758
17609
|
if (text)
|
|
16759
17610
|
writeTurnDigest(file, digestOf(text));
|
|
16760
17611
|
else
|
|
@@ -16776,7 +17627,7 @@ var init_context = __esm(() => {
|
|
|
16776
17627
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
16777
17628
|
import { createHash as createHash14 } from "node:crypto";
|
|
16778
17629
|
import fs16 from "node:fs";
|
|
16779
|
-
import
|
|
17630
|
+
import path21 from "node:path";
|
|
16780
17631
|
var sessionMarkerFile = (input, session, store) => {
|
|
16781
17632
|
let dir = store;
|
|
16782
17633
|
if (dir === undefined) {
|
|
@@ -16786,7 +17637,7 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
16786
17637
|
dir = location.dir;
|
|
16787
17638
|
}
|
|
16788
17639
|
const name = createHash14("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
16789
|
-
return
|
|
17640
|
+
return path21.join(dir, "hooks", `skills-${name}.json`);
|
|
16790
17641
|
}, list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [], EMPTY, readRaw = (file) => {
|
|
16791
17642
|
try {
|
|
16792
17643
|
return fs16.readFileSync(file, "utf8");
|
|
@@ -16807,7 +17658,7 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
16807
17658
|
return { ...EMPTY };
|
|
16808
17659
|
}
|
|
16809
17660
|
}, readSessionMarker = (file) => parseMarker(readRaw(file)), writeAtomic = (file, marker) => {
|
|
16810
|
-
fs16.mkdirSync(
|
|
17661
|
+
fs16.mkdirSync(path21.dirname(file), { recursive: true });
|
|
16811
17662
|
const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
16812
17663
|
try {
|
|
16813
17664
|
fs16.writeFileSync(temp, JSON.stringify(marker));
|
|
@@ -17277,7 +18128,7 @@ import { spawnSync as spawnSync11 } from "node:child_process";
|
|
|
17277
18128
|
import { createHash as createHash15 } from "node:crypto";
|
|
17278
18129
|
import fs17 from "node:fs";
|
|
17279
18130
|
import os5 from "node:os";
|
|
17280
|
-
import
|
|
18131
|
+
import path22 from "node:path";
|
|
17281
18132
|
function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
17282
18133
|
if (depth > 0)
|
|
17283
18134
|
return walk2(command, dialect, depth);
|
|
@@ -17476,6 +18327,23 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
|
17476
18327
|
}
|
|
17477
18328
|
return found;
|
|
17478
18329
|
}
|
|
18330
|
+
function skipWorkitGlobals(args) {
|
|
18331
|
+
let cwd = null;
|
|
18332
|
+
let at = 0;
|
|
18333
|
+
for (;at < args.length; at++) {
|
|
18334
|
+
const match = WORKIT_GLOBAL.exec(args[at]);
|
|
18335
|
+
if (match?.[1] === "=") {
|
|
18336
|
+
if (args[at].startsWith("--cwd="))
|
|
18337
|
+
cwd = args[at].slice(6);
|
|
18338
|
+
} else if (match && args[at + 1] !== undefined) {
|
|
18339
|
+
if (args[at] === "--cwd")
|
|
18340
|
+
cwd = args[at + 1];
|
|
18341
|
+
at++;
|
|
18342
|
+
} else if (args[at] !== "--json")
|
|
18343
|
+
break;
|
|
18344
|
+
}
|
|
18345
|
+
return { rest: args.slice(at), cwd };
|
|
18346
|
+
}
|
|
17479
18347
|
function rawAction(invocation) {
|
|
17480
18348
|
const args = invocation.args;
|
|
17481
18349
|
if (invocation.tool !== "git" && args[0] === "api")
|
|
@@ -17559,7 +18427,7 @@ function noteRawCommit(input, command, key) {
|
|
|
17559
18427
|
if (action.kind !== "commit")
|
|
17560
18428
|
continue;
|
|
17561
18429
|
const file = markerFile(repo, input.session.id, key);
|
|
17562
|
-
fs17.mkdirSync(
|
|
18430
|
+
fs17.mkdirSync(path22.dirname(file), { recursive: true });
|
|
17563
18431
|
fs17.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
17564
18432
|
`);
|
|
17565
18433
|
}
|
|
@@ -17586,12 +18454,12 @@ function settlePendingCommit(input) {
|
|
|
17586
18454
|
settle2(input, repo, file);
|
|
17587
18455
|
} catch {}
|
|
17588
18456
|
}
|
|
17589
|
-
var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, GIT_CONFIG_ENV,
|
|
18457
|
+
var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, GIT_CONFIG_ENV, ASSIGNMENT2, MAX_DEPTH = 4, MAX_EXPANSIONS = 16, isAbsolute = (value) => path22.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), HERE, joinDir = (base, raw) => {
|
|
17590
18458
|
const next = raw.replace(HERE, ".");
|
|
17591
18459
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
17592
18460
|
return;
|
|
17593
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
17594
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
18461
|
+
const expanded = next === "~" || next.startsWith("~/") ? path22.join(os5.homedir(), next.slice(1)) : next;
|
|
18462
|
+
return base === null || isAbsolute(expanded) ? expanded : path22.join(base, expanded);
|
|
17595
18463
|
}, rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner), cdTarget = (words) => {
|
|
17596
18464
|
const args = words.slice(1);
|
|
17597
18465
|
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
@@ -17605,7 +18473,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
17605
18473
|
const first = words[0];
|
|
17606
18474
|
if (first === undefined)
|
|
17607
18475
|
return { words, env };
|
|
17608
|
-
const assignment =
|
|
18476
|
+
const assignment = ASSIGNMENT2.exec(first);
|
|
17609
18477
|
if (assignment) {
|
|
17610
18478
|
if (GIT_CONFIG_ENV.test(assignment[1]))
|
|
17611
18479
|
env[assignment[1]] = assignment[2];
|
|
@@ -17659,10 +18527,10 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
17659
18527
|
while (rest[0]?.startsWith("-"))
|
|
17660
18528
|
rest = rest.slice(1);
|
|
17661
18529
|
}
|
|
17662
|
-
if (rest.length === 0 || !rest.every((word) =>
|
|
18530
|
+
if (rest.length === 0 || !rest.every((word) => ASSIGNMENT2.test(word)))
|
|
17663
18531
|
return null;
|
|
17664
18532
|
return rest.map((word) => {
|
|
17665
|
-
const [, name, value] =
|
|
18533
|
+
const [, name, value] = ASSIGNMENT2.exec(word);
|
|
17666
18534
|
return [name, expandWord(value, vars)];
|
|
17667
18535
|
});
|
|
17668
18536
|
}, literalOutput = (segment) => {
|
|
@@ -17827,7 +18695,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
17827
18695
|
return line.slice(index).join(" ").slice(15);
|
|
17828
18696
|
if (/^-S./.test(word))
|
|
17829
18697
|
return [word.slice(2), ...line.slice(index + 1)].join(" ");
|
|
17830
|
-
if (!word.startsWith("-") && !
|
|
18698
|
+
if (!word.startsWith("-") && !ASSIGNMENT2.test(word))
|
|
17831
18699
|
return null;
|
|
17832
18700
|
}
|
|
17833
18701
|
return null;
|
|
@@ -17875,7 +18743,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
17875
18743
|
...Object.keys(env).length ? { env } : {},
|
|
17876
18744
|
...stdin === undefined ? {} : { stdin }
|
|
17877
18745
|
};
|
|
17878
|
-
}, RECENT, LAUNCHERS, WORKIT_BIN, WORKIT_PACKAGE, PACKAGE_RUNNERS, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
|
|
18746
|
+
}, RECENT, LAUNCHERS, WORKIT_BIN, WORKIT_PACKAGE, PACKAGE_RUNNERS, WORKIT_GLOBAL, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
|
|
17879
18747
|
if (destination === "HEAD" || destination === "@" || CURRENT_BRANCH.test(destination))
|
|
17880
18748
|
return { name: null };
|
|
17881
18749
|
if (destination.startsWith("refs/heads/"))
|
|
@@ -18079,17 +18947,17 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18079
18947
|
if (!git || git.reftable)
|
|
18080
18948
|
return null;
|
|
18081
18949
|
try {
|
|
18082
|
-
const head = fs17.readFileSync(
|
|
18950
|
+
const head = fs17.readFileSync(path22.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18083
18951
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18084
18952
|
return head;
|
|
18085
18953
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18086
18954
|
if (!ref)
|
|
18087
18955
|
return null;
|
|
18088
|
-
const common =
|
|
18956
|
+
const common = path22.dirname(repo.location.dir);
|
|
18089
18957
|
try {
|
|
18090
|
-
return fs17.readFileSync(
|
|
18958
|
+
return fs17.readFileSync(path22.join(common, ref), "utf8").trim() || null;
|
|
18091
18959
|
} catch {}
|
|
18092
|
-
const packed = fs17.readFileSync(
|
|
18960
|
+
const packed = fs17.readFileSync(path22.join(common, "packed-refs"), "utf8");
|
|
18093
18961
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18094
18962
|
} catch {
|
|
18095
18963
|
return null;
|
|
@@ -18155,7 +19023,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18155
19023
|
const sub = invocation.args[0];
|
|
18156
19024
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
18157
19025
|
return [invocation];
|
|
18158
|
-
const dir =
|
|
19026
|
+
const dir = path22.resolve(root, invocation.dir ?? ".");
|
|
18159
19027
|
if (!fs17.existsSync(dir))
|
|
18160
19028
|
return [invocation];
|
|
18161
19029
|
const config = gitConfig(dir, invocation, cache);
|
|
@@ -18177,11 +19045,11 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18177
19045
|
}, normalizedUrl = (url, dir) => {
|
|
18178
19046
|
const trimmed = url.trim();
|
|
18179
19047
|
if (/^file:\/\//i.test(trimmed))
|
|
18180
|
-
return
|
|
19048
|
+
return path22.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18181
19049
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
18182
19050
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
18183
19051
|
if (!scheme && !scp)
|
|
18184
|
-
return
|
|
19052
|
+
return path22.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18185
19053
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
18186
19054
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18187
19055
|
}, rewrittenUrl = (url, config) => {
|
|
@@ -18199,7 +19067,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18199
19067
|
return best.base + url.slice(best.prefix.length);
|
|
18200
19068
|
}
|
|
18201
19069
|
return url;
|
|
18202
|
-
},
|
|
19070
|
+
}, pushUrl2 = (config, remote, dir) => {
|
|
18203
19071
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
18204
19072
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
18205
19073
|
}, pushRemoteOf = (config, branch) => {
|
|
@@ -18225,7 +19093,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18225
19093
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
18226
19094
|
if (pushRemote === null || pushRemote === remote)
|
|
18227
19095
|
return true;
|
|
18228
|
-
return
|
|
19096
|
+
return pushUrl2(config, remote, repo.dir) === pushUrl2(config, pushRemote, repo.dir);
|
|
18229
19097
|
}, quote = (text) => `\`${text}\``, refusal = (action, branch, dir) => {
|
|
18230
19098
|
if (action.kind === "merge")
|
|
18231
19099
|
return `workit: raw ${quote(action.via === "api" ? `${action.forge} api` : action.forge === "gh" ? "gh pr merge" : "glab mr merge")}${action.via === "api" ? " merge" : ""} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
|
|
@@ -18387,7 +19255,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18387
19255
|
const out = [];
|
|
18388
19256
|
const repos = new Map;
|
|
18389
19257
|
const repoFor = (relative) => {
|
|
18390
|
-
const dir =
|
|
19258
|
+
const dir = path22.resolve(cwd, relative ?? ".");
|
|
18391
19259
|
if (!repos.has(dir)) {
|
|
18392
19260
|
const repo = repoAt(dir);
|
|
18393
19261
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18431,7 +19299,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18431
19299
|
kind: "deny",
|
|
18432
19300
|
reason: `workit: the shell hook could not analyze this command (${error.message}), so it does not run. ${SPLIT_IT}`,
|
|
18433
19301
|
unblock: SPLIT_IT
|
|
18434
|
-
}), NEXT_COMMAND = "next", MARKER_TTL_SECONDS = 3600, markerFile = (repo, session, key) =>
|
|
19302
|
+
}), NEXT_COMMAND = "next", MARKER_TTL_SECONDS = 3600, markerFile = (repo, session, key) => path22.join(repo.location.dir, "hooks", `commit-${createHash15("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`), callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash15("sha256").update(command).digest("hex")}`, headCommit = (dir) => {
|
|
18435
19303
|
const run = spawnSync11("git", [
|
|
18436
19304
|
"log",
|
|
18437
19305
|
"-1",
|
|
@@ -18537,7 +19405,7 @@ var init_raw_git = __esm(() => {
|
|
|
18537
19405
|
]);
|
|
18538
19406
|
GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
18539
19407
|
GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
18540
|
-
|
|
19408
|
+
ASSIGNMENT2 = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
18541
19409
|
HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
18542
19410
|
VARIABLE = /\$(?:\{(\w+)\}|(\w+))/g;
|
|
18543
19411
|
XARGS_VALUES = new Set(["-n", "-L", "-l", "-P", "-d", "-E", "-e", "-s", "-a", "--arg-file", "--delimiter"].concat([
|
|
@@ -18569,6 +19437,7 @@ var init_raw_git = __esm(() => {
|
|
|
18569
19437
|
WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
18570
19438
|
WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
|
|
18571
19439
|
PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
|
|
19440
|
+
WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
|
|
18572
19441
|
PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
18573
19442
|
CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
18574
19443
|
VALUE_OPTIONS = {
|
|
@@ -18629,8 +19498,8 @@ var init_raw_git = __esm(() => {
|
|
|
18629
19498
|
|
|
18630
19499
|
// packages/workit-core/src/core/route-intent.ts
|
|
18631
19500
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18632
|
-
import { existsSync as
|
|
18633
|
-
import
|
|
19501
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
19502
|
+
import path23 from "node:path";
|
|
18634
19503
|
var UNKNOWABLE, SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, SUBTREE_CREATE, BRANCH_NO_CREATE, BRANCH_RENAME, longOption = (name, flags) => flags.has(name) || name.length >= 3 && [...flags].some((flag) => flag.startsWith("--") && flag.startsWith(name)), shortsOf = (flags) => [...flags].filter((flag) => /^-[^-]$/u.test(flag)).join("").replaceAll("-", ""), flagValue = (args, flags) => {
|
|
18635
19504
|
const shorts = shortsOf(flags);
|
|
18636
19505
|
for (let index = 1;index < args.length; index++) {
|
|
@@ -18756,8 +19625,8 @@ var UNKNOWABLE, SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, SUBTREE_CREATE,
|
|
|
18756
19625
|
return null;
|
|
18757
19626
|
if (dir === null)
|
|
18758
19627
|
return root;
|
|
18759
|
-
const resolved =
|
|
18760
|
-
return
|
|
19628
|
+
const resolved = path23.resolve(root, dir);
|
|
19629
|
+
return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
18761
19630
|
}, shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
18762
19631
|
let judged = false;
|
|
18763
19632
|
const cache = new Map;
|
|
@@ -18811,7 +19680,7 @@ var init_policy = __esm(() => {
|
|
|
18811
19680
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
18812
19681
|
import fs18 from "node:fs";
|
|
18813
19682
|
import os6 from "node:os";
|
|
18814
|
-
import
|
|
19683
|
+
import path24 from "node:path";
|
|
18815
19684
|
function parseJsonc(text) {
|
|
18816
19685
|
let out = "";
|
|
18817
19686
|
for (let index = 0;index < text.length; index += 1) {
|
|
@@ -18841,21 +19710,21 @@ function parseJsonc(text) {
|
|
|
18841
19710
|
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
18842
19711
|
}
|
|
18843
19712
|
function gitTop(cwd) {
|
|
18844
|
-
for (let dir =
|
|
18845
|
-
if (fs18.existsSync(
|
|
19713
|
+
for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
|
|
19714
|
+
if (fs18.existsSync(path24.join(dir, ".git")))
|
|
18846
19715
|
return dir;
|
|
18847
|
-
if (
|
|
19716
|
+
if (path24.dirname(dir) === dir)
|
|
18848
19717
|
return null;
|
|
18849
19718
|
}
|
|
18850
19719
|
}
|
|
18851
19720
|
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
18852
19721
|
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
18853
|
-
const user = env.CLAUDE_CONFIG_DIR ||
|
|
19722
|
+
const user = env.CLAUDE_CONFIG_DIR || path24.join(homeOf(env), ".claude");
|
|
18854
19723
|
return [
|
|
18855
19724
|
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
18856
|
-
|
|
18857
|
-
|
|
18858
|
-
|
|
19725
|
+
path24.join(user, "settings.json"),
|
|
19726
|
+
path24.join(project, ".claude", "settings.json"),
|
|
19727
|
+
path24.join(project, ".claude", "settings.local.json")
|
|
18859
19728
|
];
|
|
18860
19729
|
}
|
|
18861
19730
|
function claudeRules(cwd, env = process.env) {
|
|
@@ -18900,10 +19769,10 @@ function claudeRules(cwd, env = process.env) {
|
|
|
18900
19769
|
function opencodeRules(cwd, env = process.env) {
|
|
18901
19770
|
const rules = EMPTY2();
|
|
18902
19771
|
try {
|
|
18903
|
-
const config = env.XDG_CONFIG_HOME ||
|
|
19772
|
+
const config = env.XDG_CONFIG_HOME || path24.join(homeOf(env), ".config");
|
|
18904
19773
|
const files = [
|
|
18905
|
-
...OPENCODE_NAMES.map((name) =>
|
|
18906
|
-
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [
|
|
19774
|
+
...OPENCODE_NAMES.map((name) => path24.join(config, "opencode", name)),
|
|
19775
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path24.join(dir, name), path24.join(dir, ".opencode", name)])),
|
|
18907
19776
|
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
18908
19777
|
];
|
|
18909
19778
|
for (const file of files) {
|
|
@@ -18921,9 +19790,9 @@ function opencodeRules(cwd, env = process.env) {
|
|
|
18921
19790
|
function codexRules(cwd, env = process.env) {
|
|
18922
19791
|
const rules = EMPTY2();
|
|
18923
19792
|
try {
|
|
18924
|
-
const home = env.CODEX_HOME ||
|
|
19793
|
+
const home = env.CODEX_HOME || path24.join(homeOf(env), ".codex");
|
|
18925
19794
|
const top = gitTop(cwd) ?? cwd;
|
|
18926
|
-
for (const dir of [
|
|
19795
|
+
for (const dir of [path24.join(home, "rules"), path24.join(top, ".codex", "rules")]) {
|
|
18927
19796
|
let files;
|
|
18928
19797
|
try {
|
|
18929
19798
|
files = fs18.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
@@ -18933,7 +19802,7 @@ function codexRules(cwd, env = process.env) {
|
|
|
18933
19802
|
return "unreadable";
|
|
18934
19803
|
}
|
|
18935
19804
|
for (const name of files) {
|
|
18936
|
-
const text = readText2(
|
|
19805
|
+
const text = readText2(path24.join(dir, name)) ?? "";
|
|
18937
19806
|
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
18938
19807
|
const rule = codexRule(part);
|
|
18939
19808
|
if (!rule)
|
|
@@ -18952,8 +19821,8 @@ function cursorRules(cwd, env = process.env) {
|
|
|
18952
19821
|
try {
|
|
18953
19822
|
const top = gitTop(cwd) ?? cwd;
|
|
18954
19823
|
for (const file of [
|
|
18955
|
-
|
|
18956
|
-
|
|
19824
|
+
path24.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
19825
|
+
path24.join(top, ".cursor", "cli.json")
|
|
18957
19826
|
]) {
|
|
18958
19827
|
const text = readText2(file);
|
|
18959
19828
|
if (text === null)
|
|
@@ -19015,9 +19884,9 @@ var EMPTY2 = () => ({ deny: [], allow: [] }), readText2 = (file) => {
|
|
|
19015
19884
|
}, isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), projectDirs = (cwd) => {
|
|
19016
19885
|
const top = gitTop(cwd);
|
|
19017
19886
|
const dirs = [];
|
|
19018
|
-
for (let dir =
|
|
19887
|
+
for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
|
|
19019
19888
|
dirs.push(dir);
|
|
19020
|
-
if (!top || dir === top ||
|
|
19889
|
+
if (!top || dir === top || path24.dirname(dir) === dir)
|
|
19021
19890
|
return dirs;
|
|
19022
19891
|
}
|
|
19023
19892
|
}, homeOf = (env) => env.HOME || os6.homedir(), CLAUDE_MANAGED, bashPattern = (entry) => {
|
|
@@ -19115,18 +19984,18 @@ var init_heavy_rules = __esm(() => {
|
|
|
19115
19984
|
// packages/workit-core/src/core/heavy.ts
|
|
19116
19985
|
import fs19 from "node:fs";
|
|
19117
19986
|
import os7 from "node:os";
|
|
19118
|
-
import
|
|
19987
|
+
import path25 from "node:path";
|
|
19119
19988
|
function stateDir(env, platform = process.platform) {
|
|
19120
19989
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
19121
19990
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
19122
19991
|
if (env.XDG_STATE_HOME)
|
|
19123
|
-
return
|
|
19992
|
+
return path25.join(env.XDG_STATE_HOME, "workit");
|
|
19124
19993
|
const home = env.HOME || os7.homedir();
|
|
19125
19994
|
if (platform === "darwin")
|
|
19126
|
-
return
|
|
19995
|
+
return path25.join(home, "Library", "Application Support", "workit");
|
|
19127
19996
|
if (platform === "win32")
|
|
19128
|
-
return
|
|
19129
|
-
return
|
|
19997
|
+
return path25.join(env.LOCALAPPDATA ?? path25.join(home, "AppData", "Local"), "workit");
|
|
19998
|
+
return path25.join(home, ".local", "state", "workit");
|
|
19130
19999
|
}
|
|
19131
20000
|
function positional2(words, from, values = PM_VALUES) {
|
|
19132
20001
|
let index = from;
|
|
@@ -19266,7 +20135,7 @@ function machinePatterns(env) {
|
|
|
19266
20135
|
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
19267
20136
|
}
|
|
19268
20137
|
function repoPatterns(cwd) {
|
|
19269
|
-
return repoHeavyCommands(gitTop(cwd) ??
|
|
20138
|
+
return repoHeavyCommands(gitTop(cwd) ?? path25.resolve(cwd));
|
|
19270
20139
|
}
|
|
19271
20140
|
function heavyLines(lines, options) {
|
|
19272
20141
|
const found = [];
|
|
@@ -19335,12 +20204,12 @@ function heavyPlan(command, options) {
|
|
|
19335
20204
|
}
|
|
19336
20205
|
function workitOnPath2(env = process.env) {
|
|
19337
20206
|
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
19338
|
-
for (const dir of (env.PATH ?? "").split(
|
|
20207
|
+
for (const dir of (env.PATH ?? "").split(path25.delimiter))
|
|
19339
20208
|
for (const name of names) {
|
|
19340
20209
|
if (!dir)
|
|
19341
20210
|
continue;
|
|
19342
20211
|
try {
|
|
19343
|
-
fs19.accessSync(
|
|
20212
|
+
fs19.accessSync(path25.join(dir, name), fs19.constants.X_OK);
|
|
19344
20213
|
return true;
|
|
19345
20214
|
} catch {}
|
|
19346
20215
|
}
|
|
@@ -19354,7 +20223,7 @@ function leaseRewrite(command) {
|
|
|
19354
20223
|
return null;
|
|
19355
20224
|
return `${prefix}workit check --heavy -- ${rest}`;
|
|
19356
20225
|
}
|
|
19357
|
-
var HEAVY_COMMANDS_FILE = "heavy-commands.json", heavyCommandsFile = (env) =>
|
|
20226
|
+
var HEAVY_COMMANDS_FILE = "heavy-commands.json", heavyCommandsFile = (env) => path25.join(stateDir(env), HEAVY_COMMANDS_FILE), HEAVY_SCRIPT, INFO_FLAGS, WATCH_FLAG, WATCH_SCRIPT, sub = (...names) => new Set(names), TOOLS, PM_VALUES, baseName2 = (word) => (word?.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/u, ""), WORKIT, PYTHONS, NPM_INSTALL, NPM_RUN, NPM_TEST, EXEC, globWord = (word) => new RegExp(`^${word.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*")}$`, "u"), 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])), readJson = (file) => {
|
|
19358
20227
|
try {
|
|
19359
20228
|
return JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
19360
20229
|
} catch {
|
|
@@ -19500,7 +20369,7 @@ var init_heavy = __esm(() => {
|
|
|
19500
20369
|
// packages/workit-core/src/core/heavy-words.ts
|
|
19501
20370
|
import fs20 from "node:fs";
|
|
19502
20371
|
import os8 from "node:os";
|
|
19503
|
-
import
|
|
20372
|
+
import path26 from "node:path";
|
|
19504
20373
|
function maybeHeavy(command, cwd, env = process.env) {
|
|
19505
20374
|
try {
|
|
19506
20375
|
const words = wordsOf(command);
|
|
@@ -19516,13 +20385,13 @@ var HEAVY_WORDS, wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((w
|
|
|
19516
20385
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
19517
20386
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
19518
20387
|
if (env.XDG_STATE_HOME)
|
|
19519
|
-
return
|
|
20388
|
+
return path26.join(env.XDG_STATE_HOME, "workit");
|
|
19520
20389
|
const home = env.HOME || os8.homedir();
|
|
19521
20390
|
if (process.platform === "darwin")
|
|
19522
|
-
return
|
|
20391
|
+
return path26.join(home, "Library", "Application Support", "workit");
|
|
19523
20392
|
if (process.platform === "win32")
|
|
19524
|
-
return
|
|
19525
|
-
return
|
|
20393
|
+
return path26.join(env.LOCALAPPDATA ?? path26.join(home, "AppData", "Local"), "workit");
|
|
20394
|
+
return path26.join(home, ".local", "state", "workit");
|
|
19526
20395
|
}, readJson2 = (file) => {
|
|
19527
20396
|
try {
|
|
19528
20397
|
return JSON.parse(fs20.readFileSync(file, "utf8"));
|
|
@@ -19531,15 +20400,15 @@ var HEAVY_WORDS, wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((w
|
|
|
19531
20400
|
}
|
|
19532
20401
|
}, patternWords = (cwd, env) => {
|
|
19533
20402
|
const patterns = [];
|
|
19534
|
-
const machine = readJson2(
|
|
20403
|
+
const machine = readJson2(path26.join(stateDir2(env), "heavy-commands.json"));
|
|
19535
20404
|
if (Array.isArray(machine?.patterns))
|
|
19536
20405
|
patterns.push(...machine.patterns);
|
|
19537
|
-
for (let dir =
|
|
19538
|
-
if (fs20.existsSync(
|
|
20406
|
+
for (let dir = path26.resolve(cwd);; dir = path26.dirname(dir)) {
|
|
20407
|
+
if (fs20.existsSync(path26.join(dir, ".git"))) {
|
|
19539
20408
|
patterns.push(...repoHeavyCommands(dir));
|
|
19540
20409
|
break;
|
|
19541
20410
|
}
|
|
19542
|
-
if (
|
|
20411
|
+
if (path26.dirname(dir) === dir)
|
|
19543
20412
|
break;
|
|
19544
20413
|
}
|
|
19545
20414
|
const words = new Set;
|
|
@@ -19609,7 +20478,7 @@ import { spawnSync as spawnSync13 } from "node:child_process";
|
|
|
19609
20478
|
import { createHash as createHash16 } from "node:crypto";
|
|
19610
20479
|
import fs21 from "node:fs";
|
|
19611
20480
|
import os9 from "node:os";
|
|
19612
|
-
import
|
|
20481
|
+
import path27 from "node:path";
|
|
19613
20482
|
var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session, familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, ""), processAlive = (pid) => {
|
|
19614
20483
|
try {
|
|
19615
20484
|
process.kill(pid, 0);
|
|
@@ -19667,15 +20536,15 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
19667
20536
|
pid: () => hostPid(transport)
|
|
19668
20537
|
};
|
|
19669
20538
|
}, checkoutOf = (target) => {
|
|
19670
|
-
let dir =
|
|
19671
|
-
while (!fs21.existsSync(dir) &&
|
|
19672
|
-
dir =
|
|
20539
|
+
let dir = path27.resolve(target);
|
|
20540
|
+
while (!fs21.existsSync(dir) && path27.dirname(dir) !== dir)
|
|
20541
|
+
dir = path27.dirname(dir);
|
|
19673
20542
|
if (fs21.existsSync(dir) && !fs21.statSync(dir).isDirectory())
|
|
19674
|
-
dir =
|
|
19675
|
-
const parts = dir.split(
|
|
20543
|
+
dir = path27.dirname(dir);
|
|
20544
|
+
const parts = dir.split(path27.sep);
|
|
19676
20545
|
const dotGit = parts.lastIndexOf(".git");
|
|
19677
20546
|
if (dotGit > 0)
|
|
19678
|
-
dir = parts.slice(0, dotGit).join(
|
|
20547
|
+
dir = parts.slice(0, dotGit).join(path27.sep) || path27.sep;
|
|
19679
20548
|
const location = resolveStore(dir);
|
|
19680
20549
|
if (location instanceof Error || !location.shared || !location.top)
|
|
19681
20550
|
return null;
|
|
@@ -19684,8 +20553,8 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
19684
20553
|
top = fs21.realpathSync.native(top);
|
|
19685
20554
|
} catch {}
|
|
19686
20555
|
return top;
|
|
19687
|
-
}, markerFile2 = (top) =>
|
|
19688
|
-
let key =
|
|
20556
|
+
}, markerFile2 = (top) => path27.join(resolveStateDir(), "live-sessions", `${createHash16("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`), checkoutKey = (top, platform = process.platform) => {
|
|
20557
|
+
let key = path27.resolve(top);
|
|
19689
20558
|
try {
|
|
19690
20559
|
key = fs21.realpathSync.native(key);
|
|
19691
20560
|
} catch {}
|
|
@@ -19773,7 +20642,7 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
|
|
|
19773
20642
|
checkout,
|
|
19774
20643
|
heartbeatAt: now.toISOString()
|
|
19775
20644
|
};
|
|
19776
|
-
fs21.mkdirSync(
|
|
20645
|
+
fs21.mkdirSync(path27.dirname(file), { recursive: true });
|
|
19777
20646
|
const temp = `${file}.${process.pid}.tmp`;
|
|
19778
20647
|
fs21.writeFileSync(temp, JSON.stringify(marker));
|
|
19779
20648
|
fs21.renameSync(temp, file);
|
|
@@ -19794,9 +20663,9 @@ var init_live_session = __esm(() => {
|
|
|
19794
20663
|
});
|
|
19795
20664
|
|
|
19796
20665
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
19797
|
-
import { realpathSync as realpathSync5 } from "node:fs";
|
|
20666
|
+
import { existsSync as existsSync8, realpathSync as realpathSync5 } from "node:fs";
|
|
19798
20667
|
import os10 from "node:os";
|
|
19799
|
-
import
|
|
20668
|
+
import path28 from "node:path";
|
|
19800
20669
|
function writeGate(cwd, paths, owner, deps) {
|
|
19801
20670
|
const forged = paths?.length ? issuedTarget(cwd, paths) : null;
|
|
19802
20671
|
if (forged)
|
|
@@ -19820,16 +20689,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
19820
20689
|
} catch {}
|
|
19821
20690
|
const writes = shellWrites(command);
|
|
19822
20691
|
let targets = [];
|
|
19823
|
-
if (owner)
|
|
19824
|
-
|
|
19825
|
-
targets = [
|
|
19826
|
-
...segmentTargets(cwd, command, dialect),
|
|
19827
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
19828
|
-
];
|
|
19829
|
-
} catch {
|
|
19830
|
-
targets = [];
|
|
19831
|
-
}
|
|
19832
|
-
}
|
|
20692
|
+
if (owner)
|
|
20693
|
+
targets = shellMutationTargets(cwd, command, dialect);
|
|
19833
20694
|
if (owner && targets.length) {
|
|
19834
20695
|
const live = liveGate(owner, targets, deps);
|
|
19835
20696
|
if (live.kind !== "none") {
|
|
@@ -19862,21 +20723,21 @@ function beforeWriteGate(cwd, paths) {
|
|
|
19862
20723
|
}
|
|
19863
20724
|
var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
19864
20725
|
let rest = "";
|
|
19865
|
-
for (let dir =
|
|
20726
|
+
for (let dir = path28.resolve(file);; dir = path28.dirname(dir)) {
|
|
19866
20727
|
try {
|
|
19867
|
-
return
|
|
20728
|
+
return path28.join(realpathSync5.native(dir), rest);
|
|
19868
20729
|
} catch {
|
|
19869
|
-
if (
|
|
19870
|
-
return
|
|
19871
|
-
rest = rest ?
|
|
20730
|
+
if (path28.dirname(dir) === dir)
|
|
20731
|
+
return path28.resolve(file);
|
|
20732
|
+
rest = rest ? path28.join(path28.basename(dir), rest) : path28.basename(dir);
|
|
19872
20733
|
}
|
|
19873
20734
|
}
|
|
19874
20735
|
}, exempt = (cwd, root, file, plans) => {
|
|
19875
20736
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
19876
|
-
const relative =
|
|
19877
|
-
if (relative.startsWith("..") ||
|
|
20737
|
+
const relative = path28.relative(fold(canonical2(root)), fold(canonical2(path28.resolve(cwd, file))));
|
|
20738
|
+
if (relative.startsWith("..") || path28.isAbsolute(relative))
|
|
19878
20739
|
return true;
|
|
19879
|
-
const posix = relative.split(
|
|
20740
|
+
const posix = relative.split(path28.sep).join("/");
|
|
19880
20741
|
const segments = posix.split("/");
|
|
19881
20742
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
19882
20743
|
}, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), SCRIPT_SHELLS, wrappedScript = (input) => {
|
|
@@ -19888,9 +20749,9 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
19888
20749
|
return null;
|
|
19889
20750
|
if ("script" in tail)
|
|
19890
20751
|
return tail.script;
|
|
19891
|
-
const
|
|
19892
|
-
if (SCRIPT_SHELLS.has((
|
|
19893
|
-
return script
|
|
20752
|
+
const script = shellScript2(tail.argv);
|
|
20753
|
+
if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
|
|
20754
|
+
return script;
|
|
19894
20755
|
return tail.argv.map(quoteWord2).join(" ");
|
|
19895
20756
|
}, editsOf = (input) => {
|
|
19896
20757
|
let words = input;
|
|
@@ -19967,6 +20828,89 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
19967
20828
|
default:
|
|
19968
20829
|
return [];
|
|
19969
20830
|
}
|
|
20831
|
+
}, shellScript2 = (words) => {
|
|
20832
|
+
if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
|
|
20833
|
+
return null;
|
|
20834
|
+
for (let index = 1;index < words.length; index++) {
|
|
20835
|
+
const word = words[index];
|
|
20836
|
+
if (/^-[a-z]*c$/.test(word))
|
|
20837
|
+
return words[index + 1] ?? null;
|
|
20838
|
+
if (/^[-+][oO]$/.test(word))
|
|
20839
|
+
index++;
|
|
20840
|
+
else if (!/^[-+]/.test(word))
|
|
20841
|
+
return null;
|
|
20842
|
+
}
|
|
20843
|
+
return null;
|
|
20844
|
+
}, XARGS_VALUE, runsWriter = (words) => {
|
|
20845
|
+
const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
|
|
20846
|
+
const edits = editsOf(filled);
|
|
20847
|
+
return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
|
|
20848
|
+
}, opaqueWrites = (input) => {
|
|
20849
|
+
let words = input;
|
|
20850
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
|
|
20851
|
+
if (words[0] === "xargs") {
|
|
20852
|
+
let at = 1;
|
|
20853
|
+
while (words[at]?.startsWith("-"))
|
|
20854
|
+
at += XARGS_VALUE.test(words[at]) ? 2 : 1;
|
|
20855
|
+
return runsWriter(words.slice(at)) ? ["."] : null;
|
|
20856
|
+
}
|
|
20857
|
+
words = words.slice(1);
|
|
20858
|
+
}
|
|
20859
|
+
const [verb, ...args] = words;
|
|
20860
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20861
|
+
const files = operands(args);
|
|
20862
|
+
const has = (pattern) => args.some((arg) => pattern.test(arg));
|
|
20863
|
+
const listed = (named) => named.length ? named : ["."];
|
|
20864
|
+
if (name === "unlink" || name === "shred")
|
|
20865
|
+
return listed(files);
|
|
20866
|
+
if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
|
|
20867
|
+
return listed(has(/^--reference/) ? files : files.slice(1));
|
|
20868
|
+
if (name === "rsync") {
|
|
20869
|
+
const dest = files.at(-1);
|
|
20870
|
+
const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
|
|
20871
|
+
return dest === undefined ? ["."] : remote ? [] : [dest];
|
|
20872
|
+
}
|
|
20873
|
+
if (name === "find") {
|
|
20874
|
+
const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
|
|
20875
|
+
const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
|
|
20876
|
+
const end = args.findIndex((arg) => /^[-(!]/.test(arg));
|
|
20877
|
+
return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
|
|
20878
|
+
}
|
|
20879
|
+
if (/^g?awk$/.test(name)) {
|
|
20880
|
+
if (!has(/^-i|^--include/))
|
|
20881
|
+
return null;
|
|
20882
|
+
const named = [];
|
|
20883
|
+
let program = !has(/^-f$|^--file/);
|
|
20884
|
+
for (let index = 0;index < args.length; index++)
|
|
20885
|
+
if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
|
|
20886
|
+
index++;
|
|
20887
|
+
else if (args[index].startsWith("-"))
|
|
20888
|
+
continue;
|
|
20889
|
+
else if (program)
|
|
20890
|
+
program = false;
|
|
20891
|
+
else
|
|
20892
|
+
named.push(args[index]);
|
|
20893
|
+
return listed(named);
|
|
20894
|
+
}
|
|
20895
|
+
if (name === "perl")
|
|
20896
|
+
return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
|
|
20897
|
+
if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
|
|
20898
|
+
return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
|
|
20899
|
+
return shellScript2(words) === null ? null : ["."];
|
|
20900
|
+
}, linkSources = (args, at) => {
|
|
20901
|
+
const names = operands([...args]);
|
|
20902
|
+
const into = args.indexOf("-t");
|
|
20903
|
+
const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
|
|
20904
|
+
const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
|
|
20905
|
+
const target = dest === undefined ? null : at(dest);
|
|
20906
|
+
const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
|
|
20907
|
+
const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync8(target) || /[\\/]$/.test(dest ?? "") ? target : path28.dirname(target);
|
|
20908
|
+
return sources.flatMap((source) => {
|
|
20909
|
+
if (symbolic && base !== null && !path28.isAbsolute(homeExpanded(source)))
|
|
20910
|
+
return [path28.resolve(base, source)];
|
|
20911
|
+
const resolved = at(source);
|
|
20912
|
+
return resolved ? [resolved] : [];
|
|
20913
|
+
});
|
|
19970
20914
|
}, shellWrites = (command, depth = 0) => {
|
|
19971
20915
|
const targets = [];
|
|
19972
20916
|
let unknown = false;
|
|
@@ -19989,7 +20933,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
19989
20933
|
if (!unknown && targets.length === 0)
|
|
19990
20934
|
return { writes: false, targets: null };
|
|
19991
20935
|
return { writes: true, targets: unknown ? null : targets };
|
|
19992
|
-
}, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
20936
|
+
}, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path28.resolve(root, file)) ?? "-").join("\x00"), gateFor = (cwd) => {
|
|
19993
20937
|
const store = new TaskStore(cwd);
|
|
19994
20938
|
const listed = store.listTaskIndex();
|
|
19995
20939
|
if (!listed.ok)
|
|
@@ -20042,25 +20986,28 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20042
20986
|
return [];
|
|
20043
20987
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
20044
20988
|
return [];
|
|
20045
|
-
return [
|
|
20989
|
+
return [path28.resolve(cwd, invocation.dir ?? ".")];
|
|
20046
20990
|
});
|
|
20047
|
-
}, CD2, PUSHD2, POPD2, cdInto = (dir, words) => {
|
|
20991
|
+
}, CD2, PUSHD2, POPD2, UNKNOWN_DIR, cdInto = (dir, words, strict = false) => {
|
|
20992
|
+
const lost = strict ? UNKNOWN_DIR : undefined;
|
|
20048
20993
|
const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
20049
20994
|
const target = named === undefined ? undefined : homeExpanded(named);
|
|
20050
|
-
if (
|
|
20051
|
-
return;
|
|
20052
|
-
|
|
20995
|
+
if (target === "-" || target && /^~|[$`*?]/.test(target))
|
|
20996
|
+
return lost;
|
|
20997
|
+
if (dir === undefined)
|
|
20998
|
+
return lost;
|
|
20999
|
+
return target === undefined ? os10.homedir() : path28.resolve(dir, target);
|
|
20053
21000
|
}, quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`, homeExpanded = (file) => {
|
|
20054
21001
|
const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
|
|
20055
21002
|
return match ? (process.env.HOME || os10.homedir()) + file.slice(match[0].length) : file;
|
|
20056
|
-
}, segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
21003
|
+
}, segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
20057
21004
|
const targets = [];
|
|
20058
21005
|
let dir = cwd;
|
|
20059
21006
|
let stack = [];
|
|
20060
21007
|
const groups = [];
|
|
20061
21008
|
const at = (file) => {
|
|
20062
21009
|
const home = homeExpanded(file);
|
|
20063
|
-
return
|
|
21010
|
+
return path28.isAbsolute(home) ? home : dir === undefined ? null : path28.resolve(dir, home);
|
|
20064
21011
|
};
|
|
20065
21012
|
for (const segment of segmentsOf(command, dialect)) {
|
|
20066
21013
|
if (segment.group === "open") {
|
|
@@ -20077,21 +21024,35 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20077
21024
|
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
20078
21025
|
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20079
21026
|
if (CD2.has(name)) {
|
|
20080
|
-
dir = cdInto(dir, words);
|
|
21027
|
+
dir = cdInto(dir, words, strict);
|
|
20081
21028
|
continue;
|
|
20082
21029
|
}
|
|
20083
21030
|
if (PUSHD2.has(name)) {
|
|
20084
21031
|
stack.push(dir);
|
|
20085
|
-
dir = cdInto(dir, words);
|
|
21032
|
+
dir = cdInto(dir, words, strict);
|
|
20086
21033
|
continue;
|
|
20087
21034
|
}
|
|
20088
21035
|
if (POPD2.has(name)) {
|
|
20089
|
-
dir = stack.length ? stack.pop() : undefined;
|
|
21036
|
+
dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
|
|
20090
21037
|
continue;
|
|
20091
21038
|
}
|
|
20092
|
-
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
21039
|
+
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
20093
21040
|
if (inner !== null && dir !== undefined)
|
|
20094
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
21041
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
21042
|
+
if (strict && dir !== undefined) {
|
|
21043
|
+
const runIn = dir;
|
|
21044
|
+
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
21045
|
+
targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
|
|
21046
|
+
const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
|
|
21047
|
+
for (const file of opaque ?? []) {
|
|
21048
|
+
const resolved = at(file);
|
|
21049
|
+
if (resolved)
|
|
21050
|
+
targets.push(file === "." ? path28.join(resolved, DIR_TARGET) : resolved);
|
|
21051
|
+
}
|
|
21052
|
+
const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
|
|
21053
|
+
if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
|
|
21054
|
+
targets.push(...linkSources(words.slice(ln + 1), at));
|
|
21055
|
+
}
|
|
20095
21056
|
for (const file of segment.redirects)
|
|
20096
21057
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
20097
21058
|
const resolved = at(file);
|
|
@@ -20101,17 +21062,33 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20101
21062
|
const edits = editsOf(segment.words);
|
|
20102
21063
|
if (edits === null) {
|
|
20103
21064
|
if (dir !== undefined)
|
|
20104
|
-
targets.push(
|
|
21065
|
+
targets.push(path28.join(dir, DIR_TARGET));
|
|
20105
21066
|
} else
|
|
20106
21067
|
for (const file of edits) {
|
|
20107
21068
|
const resolved = at(file);
|
|
20108
21069
|
if (resolved)
|
|
20109
21070
|
targets.push(resolved);
|
|
20110
21071
|
}
|
|
20111
|
-
if (dir !== undefined
|
|
20112
|
-
|
|
21072
|
+
if (dir !== undefined)
|
|
21073
|
+
for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
|
|
21074
|
+
const { at: runsIn, args } = workitCwd(dir, invocation);
|
|
21075
|
+
if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
|
|
21076
|
+
targets.push(path28.join(runsIn, DIR_TARGET));
|
|
21077
|
+
}
|
|
20113
21078
|
}
|
|
20114
21079
|
return targets;
|
|
21080
|
+
}, workitCwd = (dir, args) => {
|
|
21081
|
+
const { rest, cwd } = skipWorkitGlobals(args);
|
|
21082
|
+
return { at: cwd === null ? dir : path28.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
21083
|
+
}, shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
|
|
21084
|
+
try {
|
|
21085
|
+
return [
|
|
21086
|
+
...segmentTargets(cwd, command, dialect, 0, strict),
|
|
21087
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path28.join(dir, DIR_TARGET))
|
|
21088
|
+
];
|
|
21089
|
+
} catch {
|
|
21090
|
+
return [];
|
|
21091
|
+
}
|
|
20115
21092
|
}, overridden = (command, dialect) => {
|
|
20116
21093
|
const segments = segmentsOf(command, dialect);
|
|
20117
21094
|
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
@@ -20132,7 +21109,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20132
21109
|
}, guarded = (targets) => {
|
|
20133
21110
|
const seen = new Set;
|
|
20134
21111
|
return targets.filter((target) => {
|
|
20135
|
-
const dir =
|
|
21112
|
+
const dir = path28.dirname(target);
|
|
20136
21113
|
if (seen.has(dir))
|
|
20137
21114
|
return false;
|
|
20138
21115
|
seen.add(dir);
|
|
@@ -20150,13 +21127,13 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
20150
21127
|
}, claimAll = (owner, targets, deps) => {
|
|
20151
21128
|
for (const target of guarded(targets))
|
|
20152
21129
|
claimLiveSession(target, owner, deps);
|
|
20153
|
-
}, liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
21130
|
+
}, liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path28.resolve(cwd, file)) : [path28.join(cwd, DIR_TARGET)], issuedTarget = (cwd, targets) => {
|
|
20154
21131
|
const state = resolveStateDir();
|
|
20155
21132
|
const found = targets.find((target) => underIssued(state, cwd, homeExpanded(target), process.platform, canonical2));
|
|
20156
21133
|
return found ?? null;
|
|
20157
21134
|
}, underIssued = (state, cwd, target, platform = process.platform, real = (file) => file) => {
|
|
20158
21135
|
const win = platform === "win32";
|
|
20159
|
-
const p = win ?
|
|
21136
|
+
const p = win ? path28.win32 : path28.posix;
|
|
20160
21137
|
const fold = (value) => win || platform === "darwin" ? value.toLowerCase() : value;
|
|
20161
21138
|
const named = win ? target.replace(/^\/([a-zA-Z])(?=\/|$)/, "$1:") : target;
|
|
20162
21139
|
const relative = p.relative(fold(real(p.resolve(state))), fold(real(p.resolve(cwd, named))));
|
|
@@ -20199,8 +21176,9 @@ var init_write_gate = __esm(() => {
|
|
|
20199
21176
|
init_shell_words();
|
|
20200
21177
|
NONE3 = { kind: "none" };
|
|
20201
21178
|
CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
20202
|
-
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
21179
|
+
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
|
|
20203
21180
|
SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
21181
|
+
XARGS_VALUE = /^-[IinLPdsEa]$/;
|
|
20204
21182
|
cache2 = new Map;
|
|
20205
21183
|
MUTATING_GIT = new Set([
|
|
20206
21184
|
"add",
|
|
@@ -20226,6 +21204,7 @@ var init_write_gate = __esm(() => {
|
|
|
20226
21204
|
CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
20227
21205
|
PUSHD2 = new Set(["pushd", "push-location"]);
|
|
20228
21206
|
POPD2 = new Set(["popd", "pop-location"]);
|
|
21207
|
+
UNKNOWN_DIR = path28.resolve(path28.parse(process.cwd()).root, ".workit-unknown-dir");
|
|
20229
21208
|
HOST_LABELS = {
|
|
20230
21209
|
claude_code: "Claude Code",
|
|
20231
21210
|
opencode: "OpenCode",
|
|
@@ -20236,6 +21215,26 @@ var init_write_gate = __esm(() => {
|
|
|
20236
21215
|
};
|
|
20237
21216
|
});
|
|
20238
21217
|
|
|
21218
|
+
// packages/workit-core/src/core/policy/current-judgment.ts
|
|
21219
|
+
function currentJudgment(cwd) {
|
|
21220
|
+
try {
|
|
21221
|
+
const store = new TaskStore(cwd);
|
|
21222
|
+
const listed = store.listTaskIndex();
|
|
21223
|
+
const key = store.currentKey();
|
|
21224
|
+
if (!listed.ok || !key.ok)
|
|
21225
|
+
return null;
|
|
21226
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
21227
|
+
const task = entry ? store.readTask(entry.id) : null;
|
|
21228
|
+
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
21229
|
+
} catch {
|
|
21230
|
+
return null;
|
|
21231
|
+
}
|
|
21232
|
+
}
|
|
21233
|
+
var init_current_judgment = __esm(() => {
|
|
21234
|
+
init_task_store();
|
|
21235
|
+
init_derive();
|
|
21236
|
+
});
|
|
21237
|
+
|
|
20239
21238
|
// packages/workit-core/src/hooks/stop.ts
|
|
20240
21239
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
20241
21240
|
function stopObligation(input, descriptor) {
|
|
@@ -20274,7 +21273,7 @@ function stopObligation(input, descriptor) {
|
|
|
20274
21273
|
}
|
|
20275
21274
|
if (!delivered || endpoint !== "green" && endpoint !== "merged")
|
|
20276
21275
|
return null;
|
|
20277
|
-
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification);
|
|
21276
|
+
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification, head);
|
|
20278
21277
|
return left ? { ...left, head } : null;
|
|
20279
21278
|
} catch {
|
|
20280
21279
|
return null;
|
|
@@ -20377,28 +21376,31 @@ var NONE4, QUESTION, OFFER, YES_NO, LIST_ITEM, asksUser = (message) => {
|
|
|
20377
21376
|
kind: "checks",
|
|
20378
21377
|
reason: `PR #${pr}'s checks were last seen ${state} at ${short(head)}, and this workspace's endpoint is \`${endpoint}\`: run \`workit ci wait --pr ${pr}\`${state === "failing" ? " after fixing the failures `workit pr status` lists (then push)" : ""}.`
|
|
20379
21378
|
};
|
|
20380
|
-
},
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
20384
|
-
const key = store.currentKey();
|
|
20385
|
-
if (!listed.ok || !key.ok)
|
|
20386
|
-
return null;
|
|
20387
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
20388
|
-
const task = entry ? store.readTask(entry.id) : null;
|
|
20389
|
-
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
20390
|
-
} catch {
|
|
20391
|
-
return null;
|
|
20392
|
-
}
|
|
20393
|
-
}, unverified = (cwd, branch, all, descriptor, verification) => {
|
|
21379
|
+
}, prBase = (rows) => {
|
|
21380
|
+
const seen = rows.findLast((row) => (row.type === "pr.status" || row.type === "pr.created") && row.observer === "workit_cli" && typeof row.base === "string");
|
|
21381
|
+
return typeof seen?.base === "string" ? seen.base : null;
|
|
21382
|
+
}, unverified = (cwd, branch, all, descriptor, verification, head) => {
|
|
20394
21383
|
const check = checkVerdicts(cwd, branch, all);
|
|
20395
21384
|
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
20396
21385
|
return null;
|
|
20397
|
-
const judgment =
|
|
21386
|
+
const judgment = currentJudgment(cwd);
|
|
20398
21387
|
const high = judgment?.riskTier === "high";
|
|
20399
|
-
|
|
20400
|
-
|
|
20401
|
-
|
|
21388
|
+
let independent;
|
|
21389
|
+
if (verification === "proportional") {
|
|
21390
|
+
const lane = branchLane(cwd, {
|
|
21391
|
+
base: prBase(all.filter((row) => row.branch === branch)),
|
|
21392
|
+
head,
|
|
21393
|
+
judgment
|
|
21394
|
+
});
|
|
21395
|
+
const need = laneNeed(lane.lane);
|
|
21396
|
+
if (need === "none")
|
|
21397
|
+
return null;
|
|
21398
|
+
independent = need === "non-author";
|
|
21399
|
+
} else {
|
|
21400
|
+
if (judgment && !judgment.behaviorChange && !high)
|
|
21401
|
+
return null;
|
|
21402
|
+
independent = verification === "independent" || high;
|
|
21403
|
+
}
|
|
20402
21404
|
if (!independent && check.review === "self-reviewed")
|
|
20403
21405
|
return null;
|
|
20404
21406
|
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
@@ -20411,8 +21413,8 @@ var NONE4, QUESTION, OFFER, YES_NO, LIST_ITEM, asksUser = (message) => {
|
|
|
20411
21413
|
};
|
|
20412
21414
|
var init_stop = __esm(() => {
|
|
20413
21415
|
init_autonomy();
|
|
20414
|
-
|
|
20415
|
-
|
|
21416
|
+
init_current_judgment();
|
|
21417
|
+
init_lane();
|
|
20416
21418
|
init_rev();
|
|
20417
21419
|
init_ledger();
|
|
20418
21420
|
init_raw_git();
|
|
@@ -20653,10 +21655,10 @@ var init_skill_nudge = __esm(() => {
|
|
|
20653
21655
|
});
|
|
20654
21656
|
|
|
20655
21657
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20656
|
-
import { existsSync as
|
|
20657
|
-
import
|
|
21658
|
+
import { existsSync as existsSync9, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
21659
|
+
import path29 from "node:path";
|
|
20658
21660
|
var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", optionalText = (value) => typeof value === "string" ? value : null, existingDirectory = (value) => {
|
|
20659
|
-
if (!nonEmpty2(value) || !
|
|
21661
|
+
if (!nonEmpty2(value) || !path29.isAbsolute(value) || !existsSync9(value))
|
|
20660
21662
|
return null;
|
|
20661
21663
|
try {
|
|
20662
21664
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -20722,9 +21724,9 @@ var init_fields = __esm(() => {
|
|
|
20722
21724
|
|
|
20723
21725
|
// packages/workit-core/src/hooks/standing.ts
|
|
20724
21726
|
import fs22 from "node:fs";
|
|
20725
|
-
import
|
|
21727
|
+
import path30 from "node:path";
|
|
20726
21728
|
var readPlans = (store) => {
|
|
20727
|
-
const dir =
|
|
21729
|
+
const dir = path30.join(store, "fanouts");
|
|
20728
21730
|
let names;
|
|
20729
21731
|
try {
|
|
20730
21732
|
names = fs22.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
@@ -20734,7 +21736,7 @@ var readPlans = (store) => {
|
|
|
20734
21736
|
const plans = [];
|
|
20735
21737
|
for (const name of names) {
|
|
20736
21738
|
try {
|
|
20737
|
-
const raw = JSON.parse(fs22.readFileSync(
|
|
21739
|
+
const raw = JSON.parse(fs22.readFileSync(path30.join(dir, name), "utf8"));
|
|
20738
21740
|
if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
|
|
20739
21741
|
continue;
|
|
20740
21742
|
const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
@@ -20857,6 +21859,252 @@ var init_cwd_normalize = __esm(() => {
|
|
|
20857
21859
|
FILTERS = new Set(["tail", "head", "grep"]);
|
|
20858
21860
|
});
|
|
20859
21861
|
|
|
21862
|
+
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
21863
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
21864
|
+
import fs23 from "node:fs";
|
|
21865
|
+
import path31 from "node:path";
|
|
21866
|
+
function confineImplementer(input, targets) {
|
|
21867
|
+
try {
|
|
21868
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
21869
|
+
return NONE5;
|
|
21870
|
+
if (targets.some((target) => within(path31.resolve(target), UNKNOWN_DIR)))
|
|
21871
|
+
return unknownDirDeny(input, boundWorktree(input));
|
|
21872
|
+
const bindings = canonical3(bindingDir());
|
|
21873
|
+
const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
|
|
21874
|
+
if (judged.length === 0)
|
|
21875
|
+
return NONE5;
|
|
21876
|
+
let worktree = boundWorktree(input);
|
|
21877
|
+
if (!worktree) {
|
|
21878
|
+
for (const { real } of judged) {
|
|
21879
|
+
worktree = within(real, bindings) ? null : workitWorktreeOf(real);
|
|
21880
|
+
if (worktree)
|
|
21881
|
+
break;
|
|
21882
|
+
}
|
|
21883
|
+
if (!worktree)
|
|
21884
|
+
return unboundDeny(input, judged[0].named);
|
|
21885
|
+
bind(input, worktree);
|
|
21886
|
+
}
|
|
21887
|
+
const root = canonical3(worktree);
|
|
21888
|
+
const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
|
|
21889
|
+
return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
|
|
21890
|
+
} catch {
|
|
21891
|
+
return NONE5;
|
|
21892
|
+
}
|
|
21893
|
+
}
|
|
21894
|
+
function confineImplementerShell(input, command, dialect = "posix") {
|
|
21895
|
+
try {
|
|
21896
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
21897
|
+
return NONE5;
|
|
21898
|
+
if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
|
|
21899
|
+
return leadOwnedDeny(input, command);
|
|
21900
|
+
} catch {}
|
|
21901
|
+
return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
|
|
21902
|
+
}
|
|
21903
|
+
var FANOUT_TOKEN_FILE = "workit-fanout-token", NONE5, CASE_INSENSITIVE2, canonical3 = (file) => {
|
|
21904
|
+
let rest = "";
|
|
21905
|
+
for (let dir = path31.resolve(file);; dir = path31.dirname(dir)) {
|
|
21906
|
+
try {
|
|
21907
|
+
return path31.join(fs23.realpathSync.native(dir), rest);
|
|
21908
|
+
} catch {
|
|
21909
|
+
if (path31.dirname(dir) === dir)
|
|
21910
|
+
return path31.resolve(file);
|
|
21911
|
+
rest = rest ? path31.join(path31.basename(dir), rest) : path31.basename(dir);
|
|
21912
|
+
}
|
|
21913
|
+
}
|
|
21914
|
+
}, within = (child, parent) => {
|
|
21915
|
+
const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
|
|
21916
|
+
const relative = path31.relative(fold(parent), fold(child));
|
|
21917
|
+
return relative === "" || !relative.startsWith("..") && !path31.isAbsolute(relative);
|
|
21918
|
+
}, checkoutOf2 = (file) => {
|
|
21919
|
+
for (let dir = canonical3(file);; dir = path31.dirname(dir)) {
|
|
21920
|
+
const dotGit = path31.join(dir, ".git");
|
|
21921
|
+
if (fs23.existsSync(dotGit)) {
|
|
21922
|
+
if (!fs23.statSync(dotGit).isFile())
|
|
21923
|
+
return { root: dir, workit: false };
|
|
21924
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
21925
|
+
return {
|
|
21926
|
+
root: dir,
|
|
21927
|
+
workit: Boolean(admin) && fs23.existsSync(path31.join(path31.resolve(dir, admin), FANOUT_TOKEN_FILE))
|
|
21928
|
+
};
|
|
21929
|
+
}
|
|
21930
|
+
if (path31.dirname(dir) === dir)
|
|
21931
|
+
return null;
|
|
21932
|
+
}
|
|
21933
|
+
}, workitWorktreeOf = (file) => {
|
|
21934
|
+
try {
|
|
21935
|
+
const checkout = checkoutOf2(file);
|
|
21936
|
+
return checkout?.workit ? checkout.root : null;
|
|
21937
|
+
} catch {
|
|
21938
|
+
return null;
|
|
21939
|
+
}
|
|
21940
|
+
}, bindingDir = () => path31.join(resolveStateDir(), "worktree-bind"), bindingFile = (session, agentId) => path31.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`), readBinding = (file) => {
|
|
21941
|
+
try {
|
|
21942
|
+
const value = JSON.parse(fs23.readFileSync(file, "utf8"));
|
|
21943
|
+
return typeof value.worktree === "string" ? value : null;
|
|
21944
|
+
} catch {
|
|
21945
|
+
return null;
|
|
21946
|
+
}
|
|
21947
|
+
}, boundWorktree = (input) => {
|
|
21948
|
+
const agentId = input.session.agentId;
|
|
21949
|
+
if (!agentId)
|
|
21950
|
+
return null;
|
|
21951
|
+
const binding = readBinding(bindingFile(input.session.id, agentId));
|
|
21952
|
+
return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
|
|
21953
|
+
}, bind = (input, worktree) => {
|
|
21954
|
+
const agentId = input.session.agentId;
|
|
21955
|
+
const file = bindingFile(input.session.id, agentId);
|
|
21956
|
+
const binding = {
|
|
21957
|
+
worktree,
|
|
21958
|
+
session: input.session.id,
|
|
21959
|
+
agentId,
|
|
21960
|
+
at: new Date().toISOString()
|
|
21961
|
+
};
|
|
21962
|
+
fs23.mkdirSync(path31.dirname(file), { recursive: true });
|
|
21963
|
+
fs23.writeFileSync(file, JSON.stringify(binding));
|
|
21964
|
+
try {
|
|
21965
|
+
if (inWorkitWorkspace(input.cwd))
|
|
21966
|
+
appendHookObserved(input.cwd, {
|
|
21967
|
+
type: "worktree.bound",
|
|
21968
|
+
actor: { host: input.host, session: input.session.id, agentId },
|
|
21969
|
+
agentType: input.session.agentType,
|
|
21970
|
+
worktree
|
|
21971
|
+
});
|
|
21972
|
+
} catch {}
|
|
21973
|
+
}, hostIsolated = (cwd, agentId) => {
|
|
21974
|
+
try {
|
|
21975
|
+
if (agentId === null)
|
|
21976
|
+
return false;
|
|
21977
|
+
const checkout = checkoutOf2(cwd);
|
|
21978
|
+
if (!checkout)
|
|
21979
|
+
return false;
|
|
21980
|
+
const dotGit = path31.join(checkout.root, ".git");
|
|
21981
|
+
if (!fs23.statSync(dotGit).isFile())
|
|
21982
|
+
return false;
|
|
21983
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
21984
|
+
if (!admin)
|
|
21985
|
+
return false;
|
|
21986
|
+
const adminDir = path31.resolve(checkout.root, admin);
|
|
21987
|
+
const back = fs23.readFileSync(path31.join(adminDir, "gitdir"), "utf8").trim();
|
|
21988
|
+
if (path31.relative(canonical3(path31.resolve(adminDir, back)), canonical3(dotGit)) !== "")
|
|
21989
|
+
return false;
|
|
21990
|
+
const common = path31.join(adminDir, "commondir");
|
|
21991
|
+
const gitDir = fs23.existsSync(common) ? path31.resolve(adminDir, fs23.readFileSync(common, "utf8").trim()) : adminDir;
|
|
21992
|
+
const expected = path31.join(path31.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
|
|
21993
|
+
return path31.relative(canonical3(expected), checkout.root) === "";
|
|
21994
|
+
} catch {
|
|
21995
|
+
return false;
|
|
21996
|
+
}
|
|
21997
|
+
}, shown = (target) => path31.basename(target) === ".workit-live-target" ? path31.dirname(target) : target, unboundDeny = (input, target) => {
|
|
21998
|
+
const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree> git commit -m … -- <paths>`, `workit --cwd <worktree> check <name>`). If your brief names no worktree, stop and report that to the lead.";
|
|
21999
|
+
return {
|
|
22000
|
+
kind: "deny",
|
|
22001
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} is not working in a Workit worktree yet, and ${shown(target)} is outside every worktree \`workit fanout worktree create\` made, so it would change the lead's checkout. ${unblock}`,
|
|
22002
|
+
unblock
|
|
22003
|
+
};
|
|
22004
|
+
}, outsideDeny = (input, worktree, target) => {
|
|
22005
|
+
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree} git commit -m … -- <paths>\`, \`workit --cwd ${worktree} check <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
|
|
22006
|
+
return {
|
|
22007
|
+
kind: "deny",
|
|
22008
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
22009
|
+
unblock
|
|
22010
|
+
};
|
|
22011
|
+
}, unknownDirDeny = (input, worktree) => {
|
|
22012
|
+
const wt = worktree ?? "<worktree>";
|
|
22013
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
22014
|
+
return {
|
|
22015
|
+
kind: "deny",
|
|
22016
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
22017
|
+
unblock
|
|
22018
|
+
};
|
|
22019
|
+
}, MUTATING_REMOTE, CONFIG_READ, CONFIG_WRITE, CONFIG_VALUE, leadOwnedGit = (args) => {
|
|
22020
|
+
const [sub = "", ...rest] = args;
|
|
22021
|
+
if (rest.some((arg) => arg === "--help" || arg === "-h"))
|
|
22022
|
+
return false;
|
|
22023
|
+
const names = rest.filter((arg) => !arg.startsWith("-"));
|
|
22024
|
+
const flag = (pattern) => rest.some((arg) => pattern.test(arg));
|
|
22025
|
+
switch (sub) {
|
|
22026
|
+
case "worktree":
|
|
22027
|
+
return names[0] !== undefined && names[0] !== "list";
|
|
22028
|
+
case "update-ref":
|
|
22029
|
+
return true;
|
|
22030
|
+
case "symbolic-ref":
|
|
22031
|
+
return names.length > 1 || flag(/^(?:-d|--delete)$/);
|
|
22032
|
+
case "branch":
|
|
22033
|
+
return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
|
|
22034
|
+
case "tag":
|
|
22035
|
+
return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
|
|
22036
|
+
case "push": {
|
|
22037
|
+
if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
|
|
22038
|
+
return true;
|
|
22039
|
+
const refspecs = names.slice(1);
|
|
22040
|
+
return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
|
|
22041
|
+
}
|
|
22042
|
+
case "replace":
|
|
22043
|
+
case "filter-branch":
|
|
22044
|
+
case "gc":
|
|
22045
|
+
case "prune":
|
|
22046
|
+
case "repack":
|
|
22047
|
+
case "sparse-checkout":
|
|
22048
|
+
return true;
|
|
22049
|
+
case "notes":
|
|
22050
|
+
case "reflog":
|
|
22051
|
+
case "submodule":
|
|
22052
|
+
return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
|
|
22053
|
+
case "stash":
|
|
22054
|
+
return names[0] === "drop" || names[0] === "clear";
|
|
22055
|
+
case "remote":
|
|
22056
|
+
return MUTATING_REMOTE.has(names[0] ?? "");
|
|
22057
|
+
case "config": {
|
|
22058
|
+
if (rest.some((arg) => CONFIG_READ.test(arg)))
|
|
22059
|
+
return false;
|
|
22060
|
+
if (rest.some((arg) => CONFIG_WRITE.test(arg)))
|
|
22061
|
+
return true;
|
|
22062
|
+
let operands = 0;
|
|
22063
|
+
for (let index = 0;index < rest.length; index++)
|
|
22064
|
+
if (CONFIG_VALUE.test(rest[index]))
|
|
22065
|
+
index++;
|
|
22066
|
+
else if (!rest[index].startsWith("-"))
|
|
22067
|
+
operands++;
|
|
22068
|
+
return operands > 1;
|
|
22069
|
+
}
|
|
22070
|
+
default:
|
|
22071
|
+
return false;
|
|
22072
|
+
}
|
|
22073
|
+
}, leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
|
|
22074
|
+
if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
|
|
22075
|
+
return false;
|
|
22076
|
+
const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
|
|
22077
|
+
return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
|
|
22078
|
+
}), leadOwnedDeny = (input, command) => {
|
|
22079
|
+
const unblock = "Worktrees, branches other than your own, refs, tags, remotes, git config, the shared stash and the fan-out plan belong to the lead: report what you need changed as a follow-up instead.";
|
|
22080
|
+
return {
|
|
22081
|
+
kind: "deny",
|
|
22082
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
|
|
22083
|
+
unblock
|
|
22084
|
+
};
|
|
22085
|
+
};
|
|
22086
|
+
var init_worktree_confine = __esm(() => {
|
|
22087
|
+
init_logger();
|
|
22088
|
+
init_ledger();
|
|
22089
|
+
init_raw_git();
|
|
22090
|
+
init_write_gate();
|
|
22091
|
+
NONE5 = { kind: "none" };
|
|
22092
|
+
CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
|
|
22093
|
+
MUTATING_REMOTE = new Set([
|
|
22094
|
+
"add",
|
|
22095
|
+
"remove",
|
|
22096
|
+
"rm",
|
|
22097
|
+
"rename",
|
|
22098
|
+
"set-url",
|
|
22099
|
+
"set-head",
|
|
22100
|
+
"set-branches",
|
|
22101
|
+
"prune"
|
|
22102
|
+
]);
|
|
22103
|
+
CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
|
|
22104
|
+
CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
|
|
22105
|
+
CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
|
|
22106
|
+
});
|
|
22107
|
+
|
|
20860
22108
|
// packages/workit-core/src/hooks/handle.ts
|
|
20861
22109
|
function handleHook(input, deps) {
|
|
20862
22110
|
const decision = decide(input, deps);
|
|
@@ -20874,7 +22122,7 @@ function decide(input, deps) {
|
|
|
20874
22122
|
switch (event.kind) {
|
|
20875
22123
|
case "session.start": {
|
|
20876
22124
|
if (!usable2(descriptor.context.sessionStart))
|
|
20877
|
-
return
|
|
22125
|
+
return NONE6;
|
|
20878
22126
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
20879
22127
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
20880
22128
|
if (offer && key !== null)
|
|
@@ -20889,26 +22137,31 @@ function decide(input, deps) {
|
|
|
20889
22137
|
}
|
|
20890
22138
|
case "context.turn": {
|
|
20891
22139
|
if (!usable2(descriptor.context.perTurn))
|
|
20892
|
-
return
|
|
22140
|
+
return NONE6;
|
|
20893
22141
|
const current = turnContextText(input, descriptor);
|
|
20894
22142
|
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
20895
|
-
return withContextLine(text ? { kind: "context", text } :
|
|
22143
|
+
return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
|
|
20896
22144
|
}
|
|
20897
22145
|
case "shell.pre": {
|
|
20898
22146
|
const skillRead = skillReadIn(event.command, event.dialect);
|
|
20899
22147
|
if (skillRead)
|
|
20900
22148
|
recordSkillLoad(input, skillRead, "read");
|
|
20901
22149
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
20902
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) :
|
|
22150
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
|
|
20903
22151
|
if (policy.kind !== "none")
|
|
20904
22152
|
return policy;
|
|
20905
22153
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
20906
22154
|
if (!postTool)
|
|
20907
22155
|
settlePendingCommit(input);
|
|
20908
|
-
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) :
|
|
22156
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
|
|
20909
22157
|
if (raw.kind === "deny")
|
|
20910
22158
|
return raw;
|
|
20911
|
-
|
|
22159
|
+
if (canDeny && implementerAgent(input, descriptor)) {
|
|
22160
|
+
const confined = confineImplementerShell(input, event.command, event.dialect);
|
|
22161
|
+
if (confined.kind === "deny")
|
|
22162
|
+
return confined;
|
|
22163
|
+
}
|
|
22164
|
+
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
|
|
20912
22165
|
if (gate.kind === "deny")
|
|
20913
22166
|
return gate;
|
|
20914
22167
|
const plan = heavyGate(input.cwd, event.command, {
|
|
@@ -20931,10 +22184,10 @@ function decide(input, deps) {
|
|
|
20931
22184
|
command: rewrittenTo,
|
|
20932
22185
|
input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
|
|
20933
22186
|
text
|
|
20934
|
-
} :
|
|
22187
|
+
} : NONE6;
|
|
20935
22188
|
if (descriptor.transport !== "hook-process")
|
|
20936
22189
|
return rewritten(null);
|
|
20937
|
-
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) :
|
|
22190
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
|
|
20938
22191
|
if (!lease)
|
|
20939
22192
|
return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
|
|
20940
22193
|
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
@@ -20943,9 +22196,17 @@ function decide(input, deps) {
|
|
|
20943
22196
|
case "shell.post":
|
|
20944
22197
|
if (usable2(descriptor.events["shell.post"].support))
|
|
20945
22198
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
20946
|
-
return
|
|
20947
|
-
case "write.pre":
|
|
20948
|
-
|
|
22199
|
+
return NONE6;
|
|
22200
|
+
case "write.pre": {
|
|
22201
|
+
if (!usable2(descriptor.events["write.pre"].support))
|
|
22202
|
+
return NONE6;
|
|
22203
|
+
if (implementerAgent(input, descriptor)) {
|
|
22204
|
+
const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
|
|
22205
|
+
if (confined.kind === "deny")
|
|
22206
|
+
return confined;
|
|
22207
|
+
}
|
|
22208
|
+
return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
|
|
22209
|
+
}
|
|
20949
22210
|
case "subagent.start":
|
|
20950
22211
|
if (workitRole(descriptor, event.agentType) === "judge")
|
|
20951
22212
|
trackJudge(input, event.agentId, true);
|
|
@@ -20962,25 +22223,25 @@ function decide(input, deps) {
|
|
|
20962
22223
|
case "tool.pre":
|
|
20963
22224
|
if (event.skill)
|
|
20964
22225
|
recordSkillLoad(input, event.skill, "tool");
|
|
20965
|
-
return
|
|
22226
|
+
return NONE6;
|
|
20966
22227
|
case "stop":
|
|
20967
|
-
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) :
|
|
22228
|
+
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
|
|
20968
22229
|
case "subagent.stop":
|
|
20969
22230
|
trackJudge(input, event.agentId, false);
|
|
20970
|
-
return
|
|
22231
|
+
return NONE6;
|
|
20971
22232
|
case "prompt.submit":
|
|
20972
|
-
return
|
|
22233
|
+
return NONE6;
|
|
20973
22234
|
}
|
|
20974
22235
|
}
|
|
20975
|
-
var usable2 = (value) => value === "native" || value === "partial",
|
|
22236
|
+
var usable2 = (value) => value === "native" || value === "partial", NONE6, resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn), offered, ROLES, workitRole = (descriptor, agentType) => {
|
|
20976
22237
|
const prefix = descriptor.subagents.agentPrefix;
|
|
20977
22238
|
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
20978
22239
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
20979
|
-
}, subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), CLAUDE_WORKTREE, isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`, subagentStartText = (input, descriptor, event) => {
|
|
22240
|
+
}, implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer", subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), CLAUDE_WORKTREE, isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`, subagentStartText = (input, descriptor, event) => {
|
|
20980
22241
|
const session = input.session.id ?? null;
|
|
20981
22242
|
const role = workitRole(descriptor, event.agentType);
|
|
20982
22243
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
20983
|
-
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer"
|
|
22244
|
+
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
|
|
20984
22245
|
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
20985
22246
|
if (role === "implementer" && isolated)
|
|
20986
22247
|
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${isolatedGit(input.cwd)} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
@@ -20988,7 +22249,7 @@ var usable2 = (value) => value === "native" || value === "partial", NONE5, resen
|
|
|
20988
22249
|
return `${isolatedGit(input.cwd)}
|
|
20989
22250
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
20990
22251
|
if (role === "implementer")
|
|
20991
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes
|
|
22252
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree> git commit -m "<message>" -- <paths>\`, \`workit --cwd <worktree> check <name>\`. The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit --cwd <worktree> git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
20992
22253
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
20993
22254
|
if (role !== "judge" || !event.agentId)
|
|
20994
22255
|
return readOnly;
|
|
@@ -21003,10 +22264,10 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
21003
22264
|
return `${readOnly} Its own Workit session is ${own}, so the ledger tells its verdicts apart from the author's. Record the verdict with exactly this command (pick the result${pick}, name the candidate branch if it differs, say what you ran): \`${line}\`.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
|
|
21004
22265
|
}, PRE_TOOL, failureDecision = (descriptor, event, error) => {
|
|
21005
22266
|
if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
|
|
21006
|
-
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } :
|
|
22267
|
+
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE6;
|
|
21007
22268
|
if (event === "session.start" || event === "subagent.start")
|
|
21008
22269
|
return { kind: "context", text: `[workit diagnostic: ${error}]` };
|
|
21009
|
-
return
|
|
22270
|
+
return NONE6;
|
|
21010
22271
|
}, dispatchHook = (adapter, raw, env = process.env) => {
|
|
21011
22272
|
const parsed = adapter.parse(raw, env);
|
|
21012
22273
|
if (!parsed.ok)
|
|
@@ -21044,7 +22305,8 @@ var init_handle = __esm(() => {
|
|
|
21044
22305
|
init_stop();
|
|
21045
22306
|
init_live_session();
|
|
21046
22307
|
init_write_gate();
|
|
21047
|
-
|
|
22308
|
+
init_worktree_confine();
|
|
22309
|
+
NONE6 = { kind: "none" };
|
|
21048
22310
|
offered = new Set;
|
|
21049
22311
|
ROLES = {
|
|
21050
22312
|
implementer: "implementer",
|
|
@@ -21057,20 +22319,20 @@ var init_handle = __esm(() => {
|
|
|
21057
22319
|
|
|
21058
22320
|
// packages/workit-core/src/hooks/deadline.ts
|
|
21059
22321
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
21060
|
-
import
|
|
22322
|
+
import path32 from "node:path";
|
|
21061
22323
|
var HOOK_SKIP_TEXT = "workit context skipped: host under load", HOOK_DEADLINE_MS = 8000, HOST_DEADLINES, HOOK_SKIPS_FILE = "hook-skips.jsonl", override = (env) => {
|
|
21062
22324
|
const value = Number(env.WORKIT_HOOK_DEADLINE_MS);
|
|
21063
22325
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
21064
22326
|
}, hookDeadlineMs = (env, host = "", native) => {
|
|
21065
22327
|
const row = HOST_DEADLINES[host];
|
|
21066
22328
|
return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
|
|
21067
|
-
}, skipsFile = (stateDir) =>
|
|
22329
|
+
}, skipsFile = (stateDir) => path32.join(stateDir, HOOK_SKIPS_FILE), recordHookSkip = (skip, stateDir = resolveStateDir()) => {
|
|
21068
22330
|
try {
|
|
21069
22331
|
mkdirSync3(stateDir, { recursive: true });
|
|
21070
22332
|
appendFileSync(skipsFile(stateDir), `${JSON.stringify(skip)}
|
|
21071
22333
|
`);
|
|
21072
22334
|
} catch {}
|
|
21073
|
-
}, QUIET, skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT }, TIMED_OUT,
|
|
22335
|
+
}, QUIET, skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT }, TIMED_OUT, within2 = async (work, ms) => {
|
|
21074
22336
|
let timer;
|
|
21075
22337
|
const expired = new Promise((resolve) => {
|
|
21076
22338
|
timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
|
|
@@ -21097,7 +22359,7 @@ var HOOK_SKIP_TEXT = "workit context skipped: host under load", HOOK_DEADLINE_MS
|
|
|
21097
22359
|
text += String(chunk);
|
|
21098
22360
|
return text;
|
|
21099
22361
|
})();
|
|
21100
|
-
const text = await
|
|
22362
|
+
const text = await within2(read, budget.startedAt + budget.deadlineMs - budget.now());
|
|
21101
22363
|
return isTimedOut(text) ? null : text;
|
|
21102
22364
|
}, note = (adapter, raw, env, budget, outcome, stage) => {
|
|
21103
22365
|
const parsed = raw === undefined ? null : adapter.parse(raw, env);
|
|
@@ -21123,7 +22385,7 @@ var HOOK_SKIP_TEXT = "workit context skipped: host under load", HOOK_DEADLINE_MS
|
|
|
21123
22385
|
if (budget.host !== null && typeof native === "string")
|
|
21124
22386
|
budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
|
|
21125
22387
|
const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
|
|
21126
|
-
const result = remaining() > 0 ? await
|
|
22388
|
+
const result = remaining() > 0 ? await within2(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
|
|
21127
22389
|
if (isTimedOut(result))
|
|
21128
22390
|
return skippedHook(adapter, raw, env, budget, "dispatch");
|
|
21129
22391
|
if (remaining() < 0)
|
|
@@ -21708,8 +22970,8 @@ var init_codex = __esm(() => {
|
|
|
21708
22970
|
});
|
|
21709
22971
|
|
|
21710
22972
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
21711
|
-
import
|
|
21712
|
-
import
|
|
22973
|
+
import fs24 from "node:fs";
|
|
22974
|
+
import path33 from "node:path";
|
|
21713
22975
|
var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
|
|
21714
22976
|
if (!isRecord6(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
21715
22977
|
return { ok: false, error: "hook_event_name is required" };
|
|
@@ -21790,17 +23052,17 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
21790
23052
|
};
|
|
21791
23053
|
}
|
|
21792
23054
|
}, TRANSCRIPT_TAIL_BYTES, lastAssistantText = (file) => {
|
|
21793
|
-
if (!file || !
|
|
23055
|
+
if (!file || !path33.isAbsolute(file))
|
|
21794
23056
|
return null;
|
|
21795
23057
|
try {
|
|
21796
|
-
const size =
|
|
23058
|
+
const size = fs24.statSync(file).size;
|
|
21797
23059
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
21798
23060
|
const buffer = Buffer.alloc(size - start);
|
|
21799
|
-
const fd =
|
|
23061
|
+
const fd = fs24.openSync(file, "r");
|
|
21800
23062
|
try {
|
|
21801
|
-
|
|
23063
|
+
fs24.readSync(fd, buffer, 0, buffer.length, start);
|
|
21802
23064
|
} finally {
|
|
21803
|
-
|
|
23065
|
+
fs24.closeSync(fd);
|
|
21804
23066
|
}
|
|
21805
23067
|
const lines = buffer.toString("utf8").split(`
|
|
21806
23068
|
`);
|
|
@@ -21826,14 +23088,14 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
21826
23088
|
} catch {
|
|
21827
23089
|
return null;
|
|
21828
23090
|
}
|
|
21829
|
-
},
|
|
23091
|
+
}, within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path33.sep) ? root : `${root}${path33.sep}`), hookCwd = (input) => {
|
|
21830
23092
|
const cwd = existingDirectory(input.cwd);
|
|
21831
23093
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
21832
23094
|
return cwd;
|
|
21833
23095
|
return cursorWorkspaceRoot(input);
|
|
21834
23096
|
}, cursorWorkspaceRoot = (input) => {
|
|
21835
23097
|
const cwd = existingDirectory(input.cwd);
|
|
21836
|
-
return cwd && input.workspace_roots.find((root) =>
|
|
23098
|
+
return cwd && input.workspace_roots.find((root) => within3(root, cwd)) || input.workspace_roots[0];
|
|
21837
23099
|
}, cursorDeny = (reason) => ({
|
|
21838
23100
|
permission: "deny",
|
|
21839
23101
|
user_message: "Workit blocked this action",
|