@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
package/dist/workit-hook.js
CHANGED
|
@@ -6739,8 +6739,8 @@ function requirementId(input) {
|
|
|
6739
6739
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
6740
6740
|
|
|
6741
6741
|
// packages/workit-core/src/core/policy/derive.ts
|
|
6742
|
-
import { existsSync as
|
|
6743
|
-
import
|
|
6742
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
6743
|
+
import path11 from "node:path";
|
|
6744
6744
|
|
|
6745
6745
|
// packages/workit-core/src/git/rev.ts
|
|
6746
6746
|
import { spawnSync } from "node:child_process";
|
|
@@ -6903,6 +6903,124 @@ function resolveRef(cwd, ref) {
|
|
|
6903
6903
|
return null;
|
|
6904
6904
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
6905
6905
|
}
|
|
6906
|
+
var LANE_DIFF = [
|
|
6907
|
+
"-c",
|
|
6908
|
+
"core.quotePath=false",
|
|
6909
|
+
"diff",
|
|
6910
|
+
"--no-color",
|
|
6911
|
+
"--no-ext-diff",
|
|
6912
|
+
"--no-textconv",
|
|
6913
|
+
"--no-renames"
|
|
6914
|
+
];
|
|
6915
|
+
var laneRange = (fork, head) => {
|
|
6916
|
+
if (!safeArg(fork) || head !== null && !safeArg(head))
|
|
6917
|
+
return null;
|
|
6918
|
+
return head === null ? [fork] : [fork, head];
|
|
6919
|
+
};
|
|
6920
|
+
function diffNumstat(cwd, fork, head) {
|
|
6921
|
+
const range = laneRange(fork, head);
|
|
6922
|
+
if (!range)
|
|
6923
|
+
return null;
|
|
6924
|
+
const run = git(cwd, [...LANE_DIFF, "--numstat", "-z", ...range, "--"]);
|
|
6925
|
+
if (!run.ok)
|
|
6926
|
+
return null;
|
|
6927
|
+
const out = [];
|
|
6928
|
+
for (const record of run.stdout.split("\x00")) {
|
|
6929
|
+
const match = /^(-|\d+)\t(-|\d+)\t(.+)$/su.exec(record.replace(/^\n/u, ""));
|
|
6930
|
+
if (!match)
|
|
6931
|
+
continue;
|
|
6932
|
+
const binary = match[1] === "-";
|
|
6933
|
+
out.push({
|
|
6934
|
+
path: match[3],
|
|
6935
|
+
added: binary ? 0 : Number(match[1]),
|
|
6936
|
+
deleted: binary ? 0 : Number(match[2]),
|
|
6937
|
+
binary
|
|
6938
|
+
});
|
|
6939
|
+
}
|
|
6940
|
+
return out;
|
|
6941
|
+
}
|
|
6942
|
+
function diffRaw(cwd, fork, head) {
|
|
6943
|
+
const range = laneRange(fork, head);
|
|
6944
|
+
if (!range)
|
|
6945
|
+
return null;
|
|
6946
|
+
const run = git(cwd, [...LANE_DIFF, "--raw", "-z", "--no-abbrev", ...range, "--"]);
|
|
6947
|
+
if (!run.ok)
|
|
6948
|
+
return null;
|
|
6949
|
+
const fields = run.stdout.split("\x00");
|
|
6950
|
+
const out = [];
|
|
6951
|
+
for (let index = 0;index + 1 < fields.length; index += 2) {
|
|
6952
|
+
const meta = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) [A-Z]\d*$/u.exec(fields[index].replace(/^\n/u, ""));
|
|
6953
|
+
if (!meta)
|
|
6954
|
+
return null;
|
|
6955
|
+
out.push({
|
|
6956
|
+
path: fields[index + 1],
|
|
6957
|
+
oldMode: meta[1],
|
|
6958
|
+
newMode: meta[2],
|
|
6959
|
+
oldBlob: meta[3],
|
|
6960
|
+
newBlob: meta[4]
|
|
6961
|
+
});
|
|
6962
|
+
}
|
|
6963
|
+
return out;
|
|
6964
|
+
}
|
|
6965
|
+
function readBlobs(cwd, ids) {
|
|
6966
|
+
const out = new Map;
|
|
6967
|
+
const wanted = [...new Set(ids)].filter((id) => /^[0-9a-f]{40,64}$/u.test(id));
|
|
6968
|
+
if (!wanted.length)
|
|
6969
|
+
return out;
|
|
6970
|
+
const run = spawnSync("git", ["cat-file", "--batch"], {
|
|
6971
|
+
cwd,
|
|
6972
|
+
input: `${wanted.join(`
|
|
6973
|
+
`)}
|
|
6974
|
+
`,
|
|
6975
|
+
maxBuffer: MAX_BUFFER,
|
|
6976
|
+
timeout: GIT_TIMEOUTS.local,
|
|
6977
|
+
killSignal: "SIGKILL",
|
|
6978
|
+
windowsHide: true
|
|
6979
|
+
});
|
|
6980
|
+
if (run.status !== 0 || !run.stdout)
|
|
6981
|
+
return null;
|
|
6982
|
+
const bytes = run.stdout;
|
|
6983
|
+
let at = 0;
|
|
6984
|
+
for (const id of wanted) {
|
|
6985
|
+
const end = bytes.indexOf(10, at);
|
|
6986
|
+
if (end < 0)
|
|
6987
|
+
return null;
|
|
6988
|
+
const header = bytes.subarray(at, end).toString("utf8").split(" ");
|
|
6989
|
+
at = end + 1;
|
|
6990
|
+
if (header[1] === "missing")
|
|
6991
|
+
continue;
|
|
6992
|
+
const size = Number(header[2]);
|
|
6993
|
+
if (header[0] !== id || !Number.isSafeInteger(size))
|
|
6994
|
+
return null;
|
|
6995
|
+
out.set(id, bytes.subarray(at, at + size));
|
|
6996
|
+
at += size + 1;
|
|
6997
|
+
}
|
|
6998
|
+
return out;
|
|
6999
|
+
}
|
|
7000
|
+
function treeFilesNamed(cwd, rev, name) {
|
|
7001
|
+
if (!safeArg(rev))
|
|
7002
|
+
return null;
|
|
7003
|
+
const run = git(cwd, ["ls-tree", "-r", "-z", "--full-tree", rev]);
|
|
7004
|
+
if (!run.ok)
|
|
7005
|
+
return null;
|
|
7006
|
+
const out = new Map;
|
|
7007
|
+
for (const record of run.stdout.split("\x00")) {
|
|
7008
|
+
const match = /^\d{6} blob ([0-9a-f]+)\t(.+)$/su.exec(record);
|
|
7009
|
+
if (match && (match[2] === name || match[2].endsWith(`/${name}`)))
|
|
7010
|
+
out.set(match[2], match[1]);
|
|
7011
|
+
}
|
|
7012
|
+
return out;
|
|
7013
|
+
}
|
|
7014
|
+
function fileAt(cwd, rev, file) {
|
|
7015
|
+
if (!safeArg(rev))
|
|
7016
|
+
return null;
|
|
7017
|
+
const run = git(cwd, ["cat-file", "blob", `${rev}:${file}`]);
|
|
7018
|
+
return run.ok ? run.stdout : null;
|
|
7019
|
+
}
|
|
7020
|
+
function untrackedFiles(cwd) {
|
|
7021
|
+
const run = git(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]);
|
|
7022
|
+
return run.ok ? run.stdout.split("\x00").filter(Boolean) : [];
|
|
7023
|
+
}
|
|
6906
7024
|
var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
|
|
6907
7025
|
function currentBranch(cwd) {
|
|
6908
7026
|
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
@@ -7716,7 +7834,7 @@ var workspaceConfigSchema = object({
|
|
|
7716
7834
|
}).passthrough().optional(),
|
|
7717
7835
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
7718
7836
|
defaultEndpoint: unknown().optional(),
|
|
7719
|
-
verification: _enum(["self", "independent"]).optional(),
|
|
7837
|
+
verification: _enum(["proportional", "self", "independent"]).optional(),
|
|
7720
7838
|
delegation: unknown().optional(),
|
|
7721
7839
|
trailers: boolean2().optional(),
|
|
7722
7840
|
liveSessionGuard: boolean2().optional(),
|
|
@@ -10011,7 +10129,8 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
10011
10129
|
verdict: failing?.verdict ?? null,
|
|
10012
10130
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
10013
10131
|
};
|
|
10014
|
-
const
|
|
10132
|
+
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))));
|
|
10133
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
10015
10134
|
return {
|
|
10016
10135
|
branch,
|
|
10017
10136
|
head,
|
|
@@ -10055,12 +10174,682 @@ var OBSERVED_ONLY = new Set([
|
|
|
10055
10174
|
...PR_ROW_TYPES
|
|
10056
10175
|
]);
|
|
10057
10176
|
|
|
10177
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
10178
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
10179
|
+
import path10 from "node:path";
|
|
10180
|
+
// packages/workit-core/src/forge/redact.ts
|
|
10181
|
+
var KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
10182
|
+
var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
|
|
10183
|
+
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
10184
|
+
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
10185
|
+
|
|
10186
|
+
// packages/workit-core/src/git/ops.ts
|
|
10187
|
+
var COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
10188
|
+
|
|
10189
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
10190
|
+
function patternRegex(dir, pattern) {
|
|
10191
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
10192
|
+
return null;
|
|
10193
|
+
const anchored = pattern.includes("/");
|
|
10194
|
+
const body = pattern.replace(/^\//u, "");
|
|
10195
|
+
let source = "";
|
|
10196
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
10197
|
+
const char = body[index];
|
|
10198
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
10199
|
+
const slash = body[index + 2] === "/";
|
|
10200
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
10201
|
+
index += slash ? 2 : 1;
|
|
10202
|
+
} else if (char === "*")
|
|
10203
|
+
source += "[^/]*";
|
|
10204
|
+
else if (char === "?")
|
|
10205
|
+
source += "[^/]";
|
|
10206
|
+
else if (char === "[") {
|
|
10207
|
+
const end = body.indexOf("]", index + 2);
|
|
10208
|
+
if (end < 0)
|
|
10209
|
+
return null;
|
|
10210
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
10211
|
+
source += `[${set}]`;
|
|
10212
|
+
index = end;
|
|
10213
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
10214
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
10215
|
+
index += 1;
|
|
10216
|
+
} else
|
|
10217
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
10218
|
+
}
|
|
10219
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
10220
|
+
try {
|
|
10221
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
10222
|
+
} catch {
|
|
10223
|
+
return null;
|
|
10224
|
+
}
|
|
10225
|
+
}
|
|
10226
|
+
function rulesOf(dir, text) {
|
|
10227
|
+
const out = [];
|
|
10228
|
+
for (const raw of text.split(`
|
|
10229
|
+
`)) {
|
|
10230
|
+
const line = raw.trim();
|
|
10231
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
10232
|
+
continue;
|
|
10233
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
10234
|
+
let generated = null;
|
|
10235
|
+
for (const attribute of attributes) {
|
|
10236
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
10237
|
+
generated = true;
|
|
10238
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
10239
|
+
generated = false;
|
|
10240
|
+
}
|
|
10241
|
+
if (generated === null)
|
|
10242
|
+
continue;
|
|
10243
|
+
const regex = patternRegex(dir, pattern);
|
|
10244
|
+
if (regex)
|
|
10245
|
+
out.push({ regex, generated });
|
|
10246
|
+
}
|
|
10247
|
+
return out;
|
|
10248
|
+
}
|
|
10249
|
+
function generatedByAttributes(files, paths) {
|
|
10250
|
+
const rules = [...files].map(([file, text]) => {
|
|
10251
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
10252
|
+
return {
|
|
10253
|
+
depth: dir ? dir.split("/").length : 0,
|
|
10254
|
+
rules: rulesOf(dir, text)
|
|
10255
|
+
};
|
|
10256
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
10257
|
+
const out = new Set;
|
|
10258
|
+
for (const file of paths) {
|
|
10259
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
10260
|
+
if (last?.generated)
|
|
10261
|
+
out.add(file);
|
|
10262
|
+
}
|
|
10263
|
+
return out;
|
|
10264
|
+
}
|
|
10265
|
+
|
|
10266
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
10267
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
10268
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
10269
|
+
var SYNTAXES = Object.fromEntries([
|
|
10270
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
10271
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
10272
|
+
ext,
|
|
10273
|
+
{ ...C_LIKE, triple: true }
|
|
10274
|
+
]),
|
|
10275
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
10276
|
+
...["css", "scss", "less"].map((ext) => [
|
|
10277
|
+
ext,
|
|
10278
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
10279
|
+
]),
|
|
10280
|
+
...["py", "pyi"].map((ext) => [
|
|
10281
|
+
ext,
|
|
10282
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
10283
|
+
]),
|
|
10284
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
10285
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
10286
|
+
ext,
|
|
10287
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
10288
|
+
])
|
|
10289
|
+
]);
|
|
10290
|
+
var 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;
|
|
10291
|
+
var extensionOf = (file) => {
|
|
10292
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
10293
|
+
const dot = name.lastIndexOf(".");
|
|
10294
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
10295
|
+
};
|
|
10296
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
10297
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
10298
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
10299
|
+
var WORD = /[\w$]/u;
|
|
10300
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
10301
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
10302
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
10303
|
+
var REGEX_KEYWORDS = new Set([
|
|
10304
|
+
"return",
|
|
10305
|
+
"typeof",
|
|
10306
|
+
"instanceof",
|
|
10307
|
+
"case",
|
|
10308
|
+
"do",
|
|
10309
|
+
"else",
|
|
10310
|
+
"in",
|
|
10311
|
+
"of",
|
|
10312
|
+
"new",
|
|
10313
|
+
"delete",
|
|
10314
|
+
"void",
|
|
10315
|
+
"throw",
|
|
10316
|
+
"yield",
|
|
10317
|
+
"await"
|
|
10318
|
+
]);
|
|
10319
|
+
function substanceOf(file, text) {
|
|
10320
|
+
const syntax = syntaxOf(file);
|
|
10321
|
+
if (!syntax)
|
|
10322
|
+
return plainSubstance(text);
|
|
10323
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
10324
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
10325
|
+
return plainSubstance(text);
|
|
10326
|
+
return lex(syntax, text);
|
|
10327
|
+
}
|
|
10328
|
+
function plainSubstance(text) {
|
|
10329
|
+
return text.split(`
|
|
10330
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
10331
|
+
`);
|
|
10332
|
+
}
|
|
10333
|
+
function lex(syntax, text) {
|
|
10334
|
+
const collapse = !syntax.indented;
|
|
10335
|
+
let out = "";
|
|
10336
|
+
let lineStart = 0;
|
|
10337
|
+
let kept = 0;
|
|
10338
|
+
let pendingSpace = false;
|
|
10339
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
10340
|
+
let index = 0;
|
|
10341
|
+
if (text.startsWith("#!")) {
|
|
10342
|
+
const end = text.indexOf(`
|
|
10343
|
+
`);
|
|
10344
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
10345
|
+
kept = out.length;
|
|
10346
|
+
index = end < 0 ? text.length : end;
|
|
10347
|
+
}
|
|
10348
|
+
const emitCode = (chunk) => {
|
|
10349
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
10350
|
+
const before = out[out.length - 1];
|
|
10351
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
10352
|
+
out += " ";
|
|
10353
|
+
}
|
|
10354
|
+
pendingSpace = false;
|
|
10355
|
+
out += chunk;
|
|
10356
|
+
};
|
|
10357
|
+
const emitVerbatim = (chunk) => {
|
|
10358
|
+
emitCode(chunk);
|
|
10359
|
+
kept = out.length;
|
|
10360
|
+
};
|
|
10361
|
+
const newline = () => {
|
|
10362
|
+
if (!collapse) {
|
|
10363
|
+
let end = out.length;
|
|
10364
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
10365
|
+
end -= 1;
|
|
10366
|
+
out = out.slice(0, end);
|
|
10367
|
+
}
|
|
10368
|
+
pendingSpace = false;
|
|
10369
|
+
if (out.length > lineStart) {
|
|
10370
|
+
out += `
|
|
10371
|
+
`;
|
|
10372
|
+
lineStart = out.length;
|
|
10373
|
+
}
|
|
10374
|
+
};
|
|
10375
|
+
const regexAllowed = () => {
|
|
10376
|
+
let end = out.length;
|
|
10377
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
10378
|
+
end -= 1;
|
|
10379
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
10380
|
+
return true;
|
|
10381
|
+
let start = end;
|
|
10382
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
10383
|
+
start -= 1;
|
|
10384
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
10385
|
+
};
|
|
10386
|
+
const scanString = (quote, escapes, multiline) => {
|
|
10387
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
10388
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
10389
|
+
let end = index + quote.length;
|
|
10390
|
+
while (end < text.length) {
|
|
10391
|
+
const char = text[end];
|
|
10392
|
+
if (escapes && char === "\\") {
|
|
10393
|
+
end += 2;
|
|
10394
|
+
continue;
|
|
10395
|
+
}
|
|
10396
|
+
if (text.startsWith(quote, end)) {
|
|
10397
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
10398
|
+
return false;
|
|
10399
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
10400
|
+
index = end + quote.length;
|
|
10401
|
+
return true;
|
|
10402
|
+
}
|
|
10403
|
+
if (char === `
|
|
10404
|
+
` && !multiline)
|
|
10405
|
+
return false;
|
|
10406
|
+
end += 1;
|
|
10407
|
+
}
|
|
10408
|
+
return false;
|
|
10409
|
+
};
|
|
10410
|
+
while (index < text.length) {
|
|
10411
|
+
const frame = frames[frames.length - 1];
|
|
10412
|
+
const char = text[index];
|
|
10413
|
+
if (frame.kind === "template") {
|
|
10414
|
+
if (char === "\\") {
|
|
10415
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
10416
|
+
index += 2;
|
|
10417
|
+
} else if (char === "`") {
|
|
10418
|
+
emitVerbatim(char);
|
|
10419
|
+
frames.pop();
|
|
10420
|
+
index += 1;
|
|
10421
|
+
} else if (text.startsWith("${", index)) {
|
|
10422
|
+
emitVerbatim("${");
|
|
10423
|
+
frames.push({ kind: "code", braces: 0 });
|
|
10424
|
+
index += 2;
|
|
10425
|
+
} else {
|
|
10426
|
+
out += char;
|
|
10427
|
+
kept = out.length;
|
|
10428
|
+
index += 1;
|
|
10429
|
+
}
|
|
10430
|
+
continue;
|
|
10431
|
+
}
|
|
10432
|
+
if (char === `
|
|
10433
|
+
`) {
|
|
10434
|
+
newline();
|
|
10435
|
+
index += 1;
|
|
10436
|
+
continue;
|
|
10437
|
+
}
|
|
10438
|
+
if (SPACE.test(char)) {
|
|
10439
|
+
if (collapse)
|
|
10440
|
+
pendingSpace = true;
|
|
10441
|
+
else
|
|
10442
|
+
out += char;
|
|
10443
|
+
index += 1;
|
|
10444
|
+
continue;
|
|
10445
|
+
}
|
|
10446
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
10447
|
+
if (lineOpener) {
|
|
10448
|
+
const end = text.indexOf(`
|
|
10449
|
+
`, index);
|
|
10450
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
10451
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
10452
|
+
emitVerbatim(comment);
|
|
10453
|
+
else
|
|
10454
|
+
pendingSpace = true;
|
|
10455
|
+
index = end < 0 ? text.length : end;
|
|
10456
|
+
continue;
|
|
10457
|
+
}
|
|
10458
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
10459
|
+
if (block) {
|
|
10460
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
10461
|
+
if (end < 0)
|
|
10462
|
+
return null;
|
|
10463
|
+
const comment = text.slice(index, end + block[1].length);
|
|
10464
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
10465
|
+
emitVerbatim(comment);
|
|
10466
|
+
else
|
|
10467
|
+
pendingSpace = true;
|
|
10468
|
+
if (comment.includes(`
|
|
10469
|
+
`))
|
|
10470
|
+
newline();
|
|
10471
|
+
index = end + block[1].length;
|
|
10472
|
+
continue;
|
|
10473
|
+
}
|
|
10474
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
10475
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
10476
|
+
return null;
|
|
10477
|
+
continue;
|
|
10478
|
+
}
|
|
10479
|
+
if (syntax.quotes.includes(char)) {
|
|
10480
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
10481
|
+
return null;
|
|
10482
|
+
continue;
|
|
10483
|
+
}
|
|
10484
|
+
if (syntax.literal?.includes(char)) {
|
|
10485
|
+
if (!scanString(char, false, false))
|
|
10486
|
+
return null;
|
|
10487
|
+
continue;
|
|
10488
|
+
}
|
|
10489
|
+
if (syntax.rawBacktick && char === "`") {
|
|
10490
|
+
if (!scanString("`", false, true))
|
|
10491
|
+
return null;
|
|
10492
|
+
continue;
|
|
10493
|
+
}
|
|
10494
|
+
if (syntax.js && char === "`") {
|
|
10495
|
+
emitVerbatim(char);
|
|
10496
|
+
frames.push({ kind: "template" });
|
|
10497
|
+
index += 1;
|
|
10498
|
+
continue;
|
|
10499
|
+
}
|
|
10500
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
10501
|
+
return null;
|
|
10502
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
10503
|
+
const lineEnd = text.indexOf(`
|
|
10504
|
+
`, index);
|
|
10505
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
10506
|
+
return null;
|
|
10507
|
+
}
|
|
10508
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
10509
|
+
let end = index + 1;
|
|
10510
|
+
let inClass = false;
|
|
10511
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
10512
|
+
if (text[end] === `
|
|
10513
|
+
`)
|
|
10514
|
+
return null;
|
|
10515
|
+
if (text[end] === "\\")
|
|
10516
|
+
end += 1;
|
|
10517
|
+
else if (text[end] === "[")
|
|
10518
|
+
inClass = true;
|
|
10519
|
+
else if (text[end] === "]")
|
|
10520
|
+
inClass = false;
|
|
10521
|
+
end += 1;
|
|
10522
|
+
}
|
|
10523
|
+
if (end >= text.length)
|
|
10524
|
+
return null;
|
|
10525
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
10526
|
+
index = end + 1;
|
|
10527
|
+
continue;
|
|
10528
|
+
}
|
|
10529
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
10530
|
+
if (char === "{")
|
|
10531
|
+
frame.braces += 1;
|
|
10532
|
+
else if (char === "}") {
|
|
10533
|
+
if (frame.braces === 0) {
|
|
10534
|
+
emitVerbatim(char);
|
|
10535
|
+
frames.pop();
|
|
10536
|
+
index += 1;
|
|
10537
|
+
continue;
|
|
10538
|
+
}
|
|
10539
|
+
frame.braces -= 1;
|
|
10540
|
+
}
|
|
10541
|
+
}
|
|
10542
|
+
emitCode(char);
|
|
10543
|
+
index += 1;
|
|
10544
|
+
}
|
|
10545
|
+
if (frames.length !== 1)
|
|
10546
|
+
return null;
|
|
10547
|
+
newline();
|
|
10548
|
+
return out;
|
|
10549
|
+
}
|
|
10550
|
+
function unbalanced(body) {
|
|
10551
|
+
let depth = 0;
|
|
10552
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
10553
|
+
if (part === "{")
|
|
10554
|
+
depth += 1;
|
|
10555
|
+
else if (part === "}")
|
|
10556
|
+
depth = Math.max(0, depth - 1);
|
|
10557
|
+
}
|
|
10558
|
+
return depth > 0;
|
|
10559
|
+
}
|
|
10560
|
+
function hasOpaqueLineBreak(text) {
|
|
10561
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
10562
|
+
}
|
|
10563
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
10564
|
+
function textOf(bytes) {
|
|
10565
|
+
if (bytes.includes(0))
|
|
10566
|
+
return null;
|
|
10567
|
+
try {
|
|
10568
|
+
return decoder.decode(bytes);
|
|
10569
|
+
} catch {
|
|
10570
|
+
return null;
|
|
10571
|
+
}
|
|
10572
|
+
}
|
|
10573
|
+
function cosmeticChange(file, before, after) {
|
|
10574
|
+
const left = textOf(before);
|
|
10575
|
+
const right = textOf(after);
|
|
10576
|
+
if (left === null || right === null)
|
|
10577
|
+
return false;
|
|
10578
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
10579
|
+
return false;
|
|
10580
|
+
const a = substanceOf(file, left);
|
|
10581
|
+
return a !== null && a === substanceOf(file, right);
|
|
10582
|
+
}
|
|
10583
|
+
|
|
10584
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
10585
|
+
var LANE_THRESHOLDS = {
|
|
10586
|
+
selfMaxLines: 100,
|
|
10587
|
+
selfMaxFiles: 5,
|
|
10588
|
+
fullMinLines: 500
|
|
10589
|
+
};
|
|
10590
|
+
var RISK_TOKENS = new Set([
|
|
10591
|
+
"auth",
|
|
10592
|
+
"authn",
|
|
10593
|
+
"authz",
|
|
10594
|
+
"oauth",
|
|
10595
|
+
"security",
|
|
10596
|
+
"permission",
|
|
10597
|
+
"permissions",
|
|
10598
|
+
"secret",
|
|
10599
|
+
"secrets",
|
|
10600
|
+
"crypto",
|
|
10601
|
+
"gate",
|
|
10602
|
+
"gates",
|
|
10603
|
+
"policy",
|
|
10604
|
+
"policies",
|
|
10605
|
+
"hook",
|
|
10606
|
+
"hooks",
|
|
10607
|
+
"husky",
|
|
10608
|
+
"lefthook",
|
|
10609
|
+
"release",
|
|
10610
|
+
"releases",
|
|
10611
|
+
"migration",
|
|
10612
|
+
"migrations",
|
|
10613
|
+
"migrate",
|
|
10614
|
+
"workflows"
|
|
10615
|
+
]);
|
|
10616
|
+
var RISK_FILES = new Set([
|
|
10617
|
+
".gitlab-ci.yml",
|
|
10618
|
+
"Jenkinsfile",
|
|
10619
|
+
"azure-pipelines.yml",
|
|
10620
|
+
".pre-commit-config.yaml",
|
|
10621
|
+
".workit/risk-paths"
|
|
10622
|
+
]);
|
|
10623
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
10624
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
10625
|
+
var LOCKFILES = new Set([
|
|
10626
|
+
"bun.lock",
|
|
10627
|
+
"bun.lockb",
|
|
10628
|
+
"package-lock.json",
|
|
10629
|
+
"npm-shrinkwrap.json",
|
|
10630
|
+
"yarn.lock",
|
|
10631
|
+
"pnpm-lock.yaml",
|
|
10632
|
+
"Cargo.lock",
|
|
10633
|
+
"go.sum",
|
|
10634
|
+
"poetry.lock",
|
|
10635
|
+
"uv.lock",
|
|
10636
|
+
"Pipfile.lock",
|
|
10637
|
+
"Gemfile.lock",
|
|
10638
|
+
"composer.lock"
|
|
10639
|
+
]);
|
|
10640
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
10641
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
10642
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
10643
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
10644
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
10645
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
10646
|
+
function kindOf(file) {
|
|
10647
|
+
if (file.binary)
|
|
10648
|
+
return "behavior";
|
|
10649
|
+
if (file.generated)
|
|
10650
|
+
return "generated";
|
|
10651
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
10652
|
+
return "lockfile";
|
|
10653
|
+
if (isDoc(file.path))
|
|
10654
|
+
return "doc";
|
|
10655
|
+
if (file.cosmetic)
|
|
10656
|
+
return "cosmetic";
|
|
10657
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
10658
|
+
}
|
|
10659
|
+
var globCache = new Map;
|
|
10660
|
+
function globMatch(pattern, file) {
|
|
10661
|
+
let regex = globCache.get(pattern);
|
|
10662
|
+
if (!regex) {
|
|
10663
|
+
let source = "";
|
|
10664
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
10665
|
+
const char = pattern[index];
|
|
10666
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
10667
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
10668
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
10669
|
+
} else if (char === "*")
|
|
10670
|
+
source += "[^/]*";
|
|
10671
|
+
else if (char === "?")
|
|
10672
|
+
source += "[^/]";
|
|
10673
|
+
else
|
|
10674
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
10675
|
+
}
|
|
10676
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
10677
|
+
globCache.set(pattern, regex);
|
|
10678
|
+
}
|
|
10679
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
10680
|
+
}
|
|
10681
|
+
function isRiskPath(file, extra = []) {
|
|
10682
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
10683
|
+
return true;
|
|
10684
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
10685
|
+
return true;
|
|
10686
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
10687
|
+
return false;
|
|
10688
|
+
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)));
|
|
10689
|
+
}
|
|
10690
|
+
var parseRiskPaths = (text) => text.split(`
|
|
10691
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
10692
|
+
function computeLane(input) {
|
|
10693
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
10694
|
+
const risky = new Set(riskFiles);
|
|
10695
|
+
const kinds = input.files.map((file) => ({
|
|
10696
|
+
file,
|
|
10697
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
10698
|
+
}));
|
|
10699
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
10700
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
10701
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
10702
|
+
const result = (lane, reasons) => ({
|
|
10703
|
+
lane,
|
|
10704
|
+
reasons,
|
|
10705
|
+
lines,
|
|
10706
|
+
files: behavior.length,
|
|
10707
|
+
riskFiles
|
|
10708
|
+
});
|
|
10709
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
10710
|
+
if (input.riskTier === "high")
|
|
10711
|
+
return result("full", ["the task judgment records high risk"]);
|
|
10712
|
+
if (riskFiles.length)
|
|
10713
|
+
return result("full", [
|
|
10714
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
10715
|
+
]);
|
|
10716
|
+
if (!behavior.length)
|
|
10717
|
+
return result("none", [
|
|
10718
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
10719
|
+
]);
|
|
10720
|
+
if (lines >= fullMinLines)
|
|
10721
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
10722
|
+
if (lines > selfMaxLines)
|
|
10723
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
10724
|
+
if (behavior.length > selfMaxFiles)
|
|
10725
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
10726
|
+
if (opaque.length)
|
|
10727
|
+
return result("focused", [
|
|
10728
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
10729
|
+
]);
|
|
10730
|
+
return result("self", [
|
|
10731
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
10732
|
+
]);
|
|
10733
|
+
}
|
|
10734
|
+
function untrackedStat(cwd, file) {
|
|
10735
|
+
try {
|
|
10736
|
+
const bytes = readFileSync3(path10.join(cwd, file));
|
|
10737
|
+
if (bytes.includes(0))
|
|
10738
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10739
|
+
const text = bytes.toString("utf8");
|
|
10740
|
+
if (hasOpaqueLineBreak(text))
|
|
10741
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10742
|
+
const lines = text.split(`
|
|
10743
|
+
`).length - (text.endsWith(`
|
|
10744
|
+
`) ? 1 : 0);
|
|
10745
|
+
return { path: file, added: lines, deleted: 0 };
|
|
10746
|
+
} catch {
|
|
10747
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10748
|
+
}
|
|
10749
|
+
}
|
|
10750
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
10751
|
+
var ABSENT = /^0+$/u;
|
|
10752
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
10753
|
+
const raw = diffRaw(cwd, fork, head);
|
|
10754
|
+
if (!raw)
|
|
10755
|
+
return null;
|
|
10756
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
10757
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
10758
|
+
if (!blobs)
|
|
10759
|
+
return null;
|
|
10760
|
+
const out = new Set;
|
|
10761
|
+
const opaque = new Set;
|
|
10762
|
+
for (const entry of candidates) {
|
|
10763
|
+
const before = blobs.get(entry.oldBlob);
|
|
10764
|
+
let after = blobs.get(entry.newBlob);
|
|
10765
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
10766
|
+
try {
|
|
10767
|
+
after = readFileSync3(path10.join(cwd, entry.path));
|
|
10768
|
+
} catch {
|
|
10769
|
+
after = undefined;
|
|
10770
|
+
}
|
|
10771
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
10772
|
+
out.add(entry.path);
|
|
10773
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
10774
|
+
opaque.add(entry.path);
|
|
10775
|
+
}
|
|
10776
|
+
return { cosmetic: out, opaque };
|
|
10777
|
+
}
|
|
10778
|
+
function generatedAt(cwd, fork, paths) {
|
|
10779
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
10780
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
10781
|
+
if (!files || !blobs)
|
|
10782
|
+
return new Set;
|
|
10783
|
+
const texts = new Map;
|
|
10784
|
+
for (const [file, id] of files) {
|
|
10785
|
+
const bytes = blobs.get(id);
|
|
10786
|
+
if (bytes)
|
|
10787
|
+
texts.set(file, bytes.toString("utf8"));
|
|
10788
|
+
}
|
|
10789
|
+
return generatedByAttributes(texts, paths);
|
|
10790
|
+
}
|
|
10791
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
10792
|
+
function laneFiles(cwd, fork, head) {
|
|
10793
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
10794
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
10795
|
+
if (!stats || !cosmetic)
|
|
10796
|
+
return null;
|
|
10797
|
+
const files = [
|
|
10798
|
+
...stats.map((entry) => ({
|
|
10799
|
+
...entry,
|
|
10800
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
10801
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
10802
|
+
})),
|
|
10803
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
10804
|
+
];
|
|
10805
|
+
const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
|
|
10806
|
+
return files.map((entry) => ({
|
|
10807
|
+
...entry,
|
|
10808
|
+
generated: generated.has(entry.path)
|
|
10809
|
+
}));
|
|
10810
|
+
}
|
|
10811
|
+
function branchLane(cwd, options) {
|
|
10812
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
10813
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
10814
|
+
const head = options.workingTree ? null : options.head;
|
|
10815
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
10816
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
10817
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
10818
|
+
if (!fork || !files) {
|
|
10819
|
+
const high = riskTier === "high";
|
|
10820
|
+
return {
|
|
10821
|
+
lane: high ? "full" : "focused",
|
|
10822
|
+
reasons: [
|
|
10823
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
10824
|
+
...high ? ["the task judgment records high risk"] : []
|
|
10825
|
+
],
|
|
10826
|
+
lines: 0,
|
|
10827
|
+
files: 0,
|
|
10828
|
+
riskFiles: [],
|
|
10829
|
+
base,
|
|
10830
|
+
head: options.head
|
|
10831
|
+
};
|
|
10832
|
+
}
|
|
10833
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
10834
|
+
return {
|
|
10835
|
+
...computeLane({
|
|
10836
|
+
files,
|
|
10837
|
+
riskTier,
|
|
10838
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
10839
|
+
}),
|
|
10840
|
+
base,
|
|
10841
|
+
head: options.head
|
|
10842
|
+
};
|
|
10843
|
+
}
|
|
10844
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
10845
|
+
|
|
10058
10846
|
// packages/workit-core/src/core/policy/derive.ts
|
|
10059
10847
|
var RULES = {
|
|
10060
10848
|
test: "check:test",
|
|
10061
10849
|
self: "verdict:self",
|
|
10062
10850
|
nonAuthor: "verdict:non-author",
|
|
10063
10851
|
verified: "verdict:verified",
|
|
10852
|
+
lane: "verdict:lane",
|
|
10064
10853
|
product: "decision:product",
|
|
10065
10854
|
plan: "plan"
|
|
10066
10855
|
};
|
|
@@ -10083,6 +10872,7 @@ var normalizedScope = (scope) => ({
|
|
|
10083
10872
|
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
10084
10873
|
const out = [];
|
|
10085
10874
|
const base = { scope, dependentAction: null };
|
|
10875
|
+
const proportional = verification === "proportional";
|
|
10086
10876
|
if (judgment.behaviorChange)
|
|
10087
10877
|
out.push(derived2({
|
|
10088
10878
|
...base,
|
|
@@ -10093,7 +10883,17 @@ function deriveRequirements(judgment, scope, constraints, verification = "self")
|
|
|
10093
10883
|
before: "close",
|
|
10094
10884
|
acceptanceAllowed: false
|
|
10095
10885
|
}));
|
|
10096
|
-
if (
|
|
10886
|
+
if (proportional && judgment.riskTier === "normal")
|
|
10887
|
+
out.push(derived2({
|
|
10888
|
+
...base,
|
|
10889
|
+
ruleId: RULES.lane,
|
|
10890
|
+
dimension: "review",
|
|
10891
|
+
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.",
|
|
10892
|
+
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.",
|
|
10893
|
+
before: "close",
|
|
10894
|
+
acceptanceAllowed: false
|
|
10895
|
+
}));
|
|
10896
|
+
if (!proportional && judgment.behaviorChange && judgment.riskTier === "normal")
|
|
10097
10897
|
out.push(verification === "independent" ? derived2({
|
|
10098
10898
|
...base,
|
|
10099
10899
|
ruleId: RULES.nonAuthor,
|
|
@@ -10196,7 +10996,7 @@ var productDecision = (task, ledger) => {
|
|
|
10196
10996
|
};
|
|
10197
10997
|
};
|
|
10198
10998
|
var planRecorded = (task, root) => {
|
|
10199
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" &&
|
|
10999
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path11.resolve(root, ref.path)));
|
|
10200
11000
|
if (cited)
|
|
10201
11001
|
return {
|
|
10202
11002
|
met: true,
|
|
@@ -10210,14 +11010,30 @@ var planRecorded = (task, root) => {
|
|
|
10210
11010
|
decisionIds: []
|
|
10211
11011
|
};
|
|
10212
11012
|
};
|
|
10213
|
-
var
|
|
11013
|
+
var workingLane = (task, root) => branchLane(root, { head: null, workingTree: true, judgment: latestJudgment(task) });
|
|
11014
|
+
var laneVerdict = (task, root, ledger) => {
|
|
11015
|
+
if (!ledger.branch() && latestJudgment(task)?.behaviorChange === false)
|
|
11016
|
+
return {
|
|
11017
|
+
met: true,
|
|
11018
|
+
reason: "no branch to read a lane from, and the judgment records no behavior change",
|
|
11019
|
+
decisionIds: []
|
|
11020
|
+
};
|
|
11021
|
+
const lane = workingLane(task, root);
|
|
11022
|
+
const label = `lane ${lane.lane} (${lane.reasons.join("; ")})`;
|
|
11023
|
+
const need = laneNeed(lane.lane);
|
|
11024
|
+
if (need === "none")
|
|
11025
|
+
return { met: true, reason: `${label}: no verdict needed`, decisionIds: [] };
|
|
11026
|
+
const status = verdict(need === "self" ? RULES.self : RULES.nonAuthor, root, ledger);
|
|
11027
|
+
return { ...status, reason: `${label}: ${status.reason}` };
|
|
11028
|
+
};
|
|
11029
|
+
var verdict = (ruleId, root, ledger) => {
|
|
10214
11030
|
const branch = ledger.branch();
|
|
10215
11031
|
if (!branch)
|
|
10216
11032
|
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
10217
11033
|
const check = checkVerdicts(root, branch, ledger.rows());
|
|
10218
11034
|
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
10219
11035
|
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
10220
|
-
const met = !failing && (
|
|
11036
|
+
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)));
|
|
10221
11037
|
if (met)
|
|
10222
11038
|
return {
|
|
10223
11039
|
met: true,
|
|
@@ -10225,7 +11041,7 @@ var verdict = (requirement, root, ledger) => {
|
|
|
10225
11041
|
decisionIds: []
|
|
10226
11042
|
};
|
|
10227
11043
|
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, " ");
|
|
10228
|
-
const unblock =
|
|
11044
|
+
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>\``;
|
|
10229
11045
|
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
10230
11046
|
};
|
|
10231
11047
|
function derivedRuleStatus(task, requirement, root, ledger) {
|
|
@@ -10237,7 +11053,9 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
10237
11053
|
case RULES.self:
|
|
10238
11054
|
case RULES.nonAuthor:
|
|
10239
11055
|
case RULES.verified:
|
|
10240
|
-
return verdict(requirement, root, ledger);
|
|
11056
|
+
return verdict(requirement.ruleId, root, ledger);
|
|
11057
|
+
case RULES.lane:
|
|
11058
|
+
return laneVerdict(task, root, ledger);
|
|
10241
11059
|
default:
|
|
10242
11060
|
return null;
|
|
10243
11061
|
}
|
|
@@ -10812,7 +11630,7 @@ var judgeInputSummary = (raw) => {
|
|
|
10812
11630
|
|
|
10813
11631
|
// packages/workit-core/src/autonomy.ts
|
|
10814
11632
|
import os4 from "node:os";
|
|
10815
|
-
import
|
|
11633
|
+
import path12 from "node:path";
|
|
10816
11634
|
var accountHome = () => {
|
|
10817
11635
|
try {
|
|
10818
11636
|
return os4.userInfo().homedir;
|
|
@@ -10821,10 +11639,10 @@ var accountHome = () => {
|
|
|
10821
11639
|
}
|
|
10822
11640
|
};
|
|
10823
11641
|
var grantsHome = { resolve: accountHome };
|
|
10824
|
-
var grantsDir = () =>
|
|
11642
|
+
var grantsDir = () => path12.join(grantsHome.resolve(), ".config", "workit");
|
|
10825
11643
|
var grantsOverride = () => {
|
|
10826
|
-
const configured =
|
|
10827
|
-
return configured ===
|
|
11644
|
+
const configured = path12.resolve(resolveConfigDir());
|
|
11645
|
+
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`;
|
|
10828
11646
|
};
|
|
10829
11647
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
10830
11648
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -10877,8 +11695,16 @@ function endpointView(entry, context) {
|
|
|
10877
11695
|
...issue ? { endpointIssue: issue } : {}
|
|
10878
11696
|
};
|
|
10879
11697
|
}
|
|
10880
|
-
var
|
|
10881
|
-
|
|
11698
|
+
var VERIFICATION_MODES = [
|
|
11699
|
+
"proportional",
|
|
11700
|
+
"self",
|
|
11701
|
+
"independent"
|
|
11702
|
+
];
|
|
11703
|
+
var DEFAULT_VERIFICATION = "proportional";
|
|
11704
|
+
var verificationOf = (entry) => {
|
|
11705
|
+
const value = entry?.verification;
|
|
11706
|
+
return VERIFICATION_MODES.includes(value) ? value : DEFAULT_VERIFICATION;
|
|
11707
|
+
};
|
|
10882
11708
|
var isGrantValue = (value) => value === true || value === false || value === "verified";
|
|
10883
11709
|
var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
|
|
10884
11710
|
var fromAutoApprove = (value) => {
|
|
@@ -10950,7 +11776,7 @@ var grantHint = (workspace, kind) => `ask the user to run, in their own terminal
|
|
|
10950
11776
|
// packages/workit-core/src/core/task-store.ts
|
|
10951
11777
|
import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
|
|
10952
11778
|
import * as fs12 from "node:fs";
|
|
10953
|
-
import
|
|
11779
|
+
import path17 from "node:path";
|
|
10954
11780
|
|
|
10955
11781
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
10956
11782
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -11001,7 +11827,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
11001
11827
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
11002
11828
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
11003
11829
|
import fsSync from "node:fs";
|
|
11004
|
-
import
|
|
11830
|
+
import path13 from "node:path";
|
|
11005
11831
|
|
|
11006
11832
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
11007
11833
|
import fs8 from "node:fs";
|
|
@@ -11091,13 +11917,13 @@ ${ownershipToken}
|
|
|
11091
11917
|
return { raw, ownershipToken };
|
|
11092
11918
|
}
|
|
11093
11919
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
11094
|
-
const resolved =
|
|
11095
|
-
const lexicalRelative =
|
|
11096
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
11097
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
11920
|
+
const resolved = path13.resolve(lockPath);
|
|
11921
|
+
const lexicalRelative = path13.relative(lockRoot.rootDir, resolved);
|
|
11922
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path13.sep}`) && !path13.isAbsolute(lexicalRelative) ? lexicalRelative : path13.relative(lockRoot.rootReal, resolved);
|
|
11923
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path13.sep}`) || path13.isAbsolute(relative)) {
|
|
11098
11924
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
11099
11925
|
}
|
|
11100
|
-
return relative.split(
|
|
11926
|
+
return relative.split(path13.sep).join(path13.posix.sep);
|
|
11101
11927
|
}
|
|
11102
11928
|
function parseSidecarLockPayload(raw, parser) {
|
|
11103
11929
|
if (parser) {
|
|
@@ -11244,7 +12070,7 @@ function getFsSafeLockConfig() {
|
|
|
11244
12070
|
|
|
11245
12071
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
11246
12072
|
import fs10 from "node:fs";
|
|
11247
|
-
import
|
|
12073
|
+
import path14 from "node:path";
|
|
11248
12074
|
|
|
11249
12075
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
11250
12076
|
function sleepSync(ms) {
|
|
@@ -11335,26 +12161,26 @@ function canonicalLockParentSync(parent) {
|
|
|
11335
12161
|
return process.platform === "win32" ? fs10.realpathSync.native(parent) : fs10.realpathSync(parent);
|
|
11336
12162
|
}
|
|
11337
12163
|
function normalizeTargetPath(targetPath) {
|
|
11338
|
-
const resolved =
|
|
11339
|
-
fs10.mkdirSync(
|
|
12164
|
+
const resolved = path14.resolve(targetPath);
|
|
12165
|
+
fs10.mkdirSync(path14.dirname(resolved), { recursive: true });
|
|
11340
12166
|
try {
|
|
11341
|
-
return
|
|
12167
|
+
return path14.join(canonicalLockParentSync(path14.dirname(resolved)), path14.basename(resolved));
|
|
11342
12168
|
} catch {
|
|
11343
12169
|
return resolved;
|
|
11344
12170
|
}
|
|
11345
12171
|
}
|
|
11346
12172
|
function boundedLockPath(lockPath, lockRoot) {
|
|
11347
|
-
const resolved =
|
|
12173
|
+
const resolved = path14.resolve(lockPath);
|
|
11348
12174
|
if (!lockRoot)
|
|
11349
12175
|
return resolved;
|
|
11350
12176
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
11351
|
-
const parent =
|
|
12177
|
+
const parent = path14.dirname(resolved);
|
|
11352
12178
|
const parentReal = canonicalLockParentSync(parent);
|
|
11353
|
-
const parentRelative =
|
|
11354
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
12179
|
+
const parentRelative = path14.relative(lockRoot.rootReal, parentReal);
|
|
12180
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path14.sep}`) || path14.isAbsolute(parentRelative)) {
|
|
11355
12181
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
11356
12182
|
}
|
|
11357
|
-
return
|
|
12183
|
+
return path14.join(parentReal, path14.basename(resolved));
|
|
11358
12184
|
}
|
|
11359
12185
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
11360
12186
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -11554,14 +12380,14 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
11554
12380
|
}
|
|
11555
12381
|
}
|
|
11556
12382
|
// packages/workit-core/src/core/package-root.ts
|
|
11557
|
-
import { existsSync as
|
|
11558
|
-
import
|
|
12383
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
12384
|
+
import path15 from "node:path";
|
|
11559
12385
|
import { fileURLToPath } from "node:url";
|
|
11560
12386
|
var packageRoot = () => {
|
|
11561
|
-
let dir =
|
|
12387
|
+
let dir = path15.dirname(fileURLToPath(import.meta.url));
|
|
11562
12388
|
while (true) {
|
|
11563
|
-
const parent =
|
|
11564
|
-
if (
|
|
12389
|
+
const parent = path15.dirname(dir);
|
|
12390
|
+
if (existsSync4(path15.join(dir, "package.json")) || parent === dir)
|
|
11565
12391
|
return dir;
|
|
11566
12392
|
dir = parent;
|
|
11567
12393
|
}
|
|
@@ -11570,7 +12396,7 @@ var packageRoot = () => {
|
|
|
11570
12396
|
// packages/workit-core/src/store/event-log.ts
|
|
11571
12397
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11572
12398
|
import fs11 from "node:fs";
|
|
11573
|
-
import
|
|
12399
|
+
import path16 from "node:path";
|
|
11574
12400
|
var EVENT_VERSION = 1;
|
|
11575
12401
|
|
|
11576
12402
|
class LogDamage extends Error {
|
|
@@ -11681,7 +12507,7 @@ function appendEvent(file, event, validEnd) {
|
|
|
11681
12507
|
fs11.closeSync(fd);
|
|
11682
12508
|
}
|
|
11683
12509
|
if (created)
|
|
11684
|
-
fsyncDirectory(
|
|
12510
|
+
fsyncDirectory(path16.dirname(file));
|
|
11685
12511
|
return validEnd;
|
|
11686
12512
|
}
|
|
11687
12513
|
function replaceFile(file, bytes, durable) {
|
|
@@ -11697,7 +12523,7 @@ function replaceFile(file, bytes, durable) {
|
|
|
11697
12523
|
}
|
|
11698
12524
|
renameWithRetry(temporary, file);
|
|
11699
12525
|
if (durable)
|
|
11700
|
-
fsyncDirectory(
|
|
12526
|
+
fsyncDirectory(path16.dirname(file));
|
|
11701
12527
|
} catch (error) {
|
|
11702
12528
|
try {
|
|
11703
12529
|
fs11.unlinkSync(temporary);
|
|
@@ -12040,7 +12866,7 @@ var cachedRuntimeVersion = null;
|
|
|
12040
12866
|
var runtimeVersion = () => {
|
|
12041
12867
|
if (cachedRuntimeVersion === null) {
|
|
12042
12868
|
try {
|
|
12043
|
-
const pkg = JSON.parse(fs12.readFileSync(
|
|
12869
|
+
const pkg = JSON.parse(fs12.readFileSync(path17.join(packageRoot(), "package.json"), "utf8"));
|
|
12044
12870
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
12045
12871
|
} catch {
|
|
12046
12872
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -12073,7 +12899,7 @@ var readText = (file) => {
|
|
|
12073
12899
|
};
|
|
12074
12900
|
var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
|
|
12075
12901
|
var branchStart = (location, branch, aliases) => {
|
|
12076
|
-
const first = readText(
|
|
12902
|
+
const first = readText(path17.join(path17.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
12077
12903
|
`, 1)[0];
|
|
12078
12904
|
const created = REFLOG_LINE.exec(first);
|
|
12079
12905
|
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
@@ -12083,7 +12909,7 @@ var branchStart = (location, branch, aliases) => {
|
|
|
12083
12909
|
return start;
|
|
12084
12910
|
if (!location.git)
|
|
12085
12911
|
return null;
|
|
12086
|
-
for (const line of readText(
|
|
12912
|
+
for (const line of readText(path17.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
12087
12913
|
`)) {
|
|
12088
12914
|
const entry = REFLOG_LINE.exec(line);
|
|
12089
12915
|
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
@@ -12093,14 +12919,14 @@ var branchStart = (location, branch, aliases) => {
|
|
|
12093
12919
|
}
|
|
12094
12920
|
return null;
|
|
12095
12921
|
};
|
|
12096
|
-
var refExists = (common, ref) => fs12.existsSync(
|
|
12922
|
+
var refExists = (common, ref) => fs12.existsSync(path17.join(common, ref)) || readText(path17.join(common, "packed-refs")).includes(` ${ref}
|
|
12097
12923
|
`);
|
|
12098
12924
|
var origins = new Map;
|
|
12099
12925
|
var sameDirectoryIdentity = (left, right) => {
|
|
12100
|
-
if (!
|
|
12926
|
+
if (!path17.isAbsolute(left) || !path17.isAbsolute(right))
|
|
12101
12927
|
return false;
|
|
12102
|
-
const normalizedLeft =
|
|
12103
|
-
const normalizedRight =
|
|
12928
|
+
const normalizedLeft = path17.resolve(left);
|
|
12929
|
+
const normalizedRight = path17.resolve(right);
|
|
12104
12930
|
try {
|
|
12105
12931
|
const a = fs12.statSync(normalizedLeft, { bigint: true });
|
|
12106
12932
|
const b = fs12.statSync(normalizedRight, { bigint: true });
|
|
@@ -12146,7 +12972,7 @@ class TaskStore {
|
|
|
12146
12972
|
migrationChecked = false;
|
|
12147
12973
|
compactDue = new Set;
|
|
12148
12974
|
constructor(root, options = {}) {
|
|
12149
|
-
const resolved =
|
|
12975
|
+
const resolved = path17.resolve(root);
|
|
12150
12976
|
this.origin = fs12.existsSync(resolved) ? fs12.realpathSync(resolved) : resolved;
|
|
12151
12977
|
this.located = resolveStore(this.origin);
|
|
12152
12978
|
this.root = checkoutRootOf(this.origin, this.located);
|
|
@@ -12155,7 +12981,7 @@ class TaskStore {
|
|
|
12155
12981
|
this.migrateOnRead = options.migrateOnRead;
|
|
12156
12982
|
}
|
|
12157
12983
|
location() {
|
|
12158
|
-
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs12.existsSync(
|
|
12984
|
+
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs12.existsSync(path17.join(this.root, ".git"))) {
|
|
12159
12985
|
this.located = undefined;
|
|
12160
12986
|
this.migrationChecked = false;
|
|
12161
12987
|
}
|
|
@@ -12311,7 +13137,7 @@ class TaskStore {
|
|
|
12311
13137
|
const moved = this.bindLocked(again.data.id, key, input.now);
|
|
12312
13138
|
return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
|
|
12313
13139
|
}
|
|
12314
|
-
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" :
|
|
13140
|
+
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path17.basename(this.root) || this.root;
|
|
12315
13141
|
const intent = {
|
|
12316
13142
|
objective: input.objective?.trim() || `Work on ${label}`,
|
|
12317
13143
|
scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
|
|
@@ -12377,14 +13203,14 @@ class TaskStore {
|
|
|
12377
13203
|
const branch = key.ok ? key.data.branch : null;
|
|
12378
13204
|
if (!branch || !location.ok)
|
|
12379
13205
|
return success(null, null, null);
|
|
12380
|
-
const common =
|
|
12381
|
-
const heads =
|
|
12382
|
-
const headLog = location.data.git ?
|
|
13206
|
+
const common = path17.dirname(location.data.dir);
|
|
13207
|
+
const heads = path17.join(common, "logs", "refs", "heads");
|
|
13208
|
+
const headLog = location.data.git ? path17.join(location.data.git.gitDir, "logs", "HEAD") : "";
|
|
12383
13209
|
const signatures = [
|
|
12384
|
-
|
|
13210
|
+
path17.join(heads, branch),
|
|
12385
13211
|
headLog,
|
|
12386
13212
|
heads,
|
|
12387
|
-
|
|
13213
|
+
path17.join(common, "packed-refs")
|
|
12388
13214
|
].map((file) => fileSignature(file) ?? "-");
|
|
12389
13215
|
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
12390
13216
|
let resolved = origins.get(slot);
|
|
@@ -12396,7 +13222,7 @@ class TaskStore {
|
|
|
12396
13222
|
aliases.push(rename.from);
|
|
12397
13223
|
const start = branchStart(location.data, branch, aliases);
|
|
12398
13224
|
const slash = start?.indexOf("/") ?? -1;
|
|
12399
|
-
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(
|
|
13225
|
+
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)}"]`));
|
|
12400
13226
|
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
12401
13227
|
resolved = {
|
|
12402
13228
|
signature: signatures.join("|"),
|
|
@@ -12634,13 +13460,13 @@ class TaskStore {
|
|
|
12634
13460
|
if (!ready.ok)
|
|
12635
13461
|
return ready;
|
|
12636
13462
|
const dirs = this.paths();
|
|
12637
|
-
const recovery =
|
|
13463
|
+
const recovery = path17.join(this.root, ".workit", "recovery");
|
|
12638
13464
|
if (fs12.existsSync(recovery)) {
|
|
12639
13465
|
let files = 0;
|
|
12640
13466
|
let bytes = 0;
|
|
12641
13467
|
try {
|
|
12642
13468
|
for (const name of fs12.readdirSync(recovery)) {
|
|
12643
|
-
const stat = fs12.lstatSync(
|
|
13469
|
+
const stat = fs12.lstatSync(path17.join(recovery, name));
|
|
12644
13470
|
if (!stat.isFile())
|
|
12645
13471
|
continue;
|
|
12646
13472
|
files += 1;
|
|
@@ -12693,7 +13519,7 @@ class TaskStore {
|
|
|
12693
13519
|
if (fs12.existsSync(dirs.blobs))
|
|
12694
13520
|
for (const name of fs12.readdirSync(dirs.blobs)) {
|
|
12695
13521
|
const digest = name.replace(/\.json$/, "");
|
|
12696
|
-
const file =
|
|
13522
|
+
const file = path17.join(dirs.blobs, name);
|
|
12697
13523
|
let stat;
|
|
12698
13524
|
try {
|
|
12699
13525
|
stat = fs12.lstatSync(file);
|
|
@@ -12711,7 +13537,7 @@ class TaskStore {
|
|
|
12711
13537
|
}
|
|
12712
13538
|
return success(null, null, null);
|
|
12713
13539
|
};
|
|
12714
|
-
const swept = dryRun ? sweep() : this.withLock(
|
|
13540
|
+
const swept = dryRun ? sweep() : this.withLock(path17.join(dirs.dir, "gc.lock"), sweep);
|
|
12715
13541
|
if (!swept.ok)
|
|
12716
13542
|
return swept;
|
|
12717
13543
|
const temporaryDirs = [
|
|
@@ -12719,7 +13545,7 @@ class TaskStore {
|
|
|
12719
13545
|
dirs.tasks,
|
|
12720
13546
|
dirs.blobs,
|
|
12721
13547
|
dirs.checkout,
|
|
12722
|
-
...names.map((name) =>
|
|
13548
|
+
...names.map((name) => path17.join(dirs.tasks, name))
|
|
12723
13549
|
];
|
|
12724
13550
|
for (const directory of temporaryDirs) {
|
|
12725
13551
|
if (!fs12.existsSync(directory))
|
|
@@ -12727,7 +13553,7 @@ class TaskStore {
|
|
|
12727
13553
|
for (const name of fs12.readdirSync(directory)) {
|
|
12728
13554
|
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
12729
13555
|
continue;
|
|
12730
|
-
const file =
|
|
13556
|
+
const file = path17.join(directory, name);
|
|
12731
13557
|
try {
|
|
12732
13558
|
const stat = fs12.lstatSync(file);
|
|
12733
13559
|
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
@@ -12744,30 +13570,30 @@ class TaskStore {
|
|
|
12744
13570
|
}
|
|
12745
13571
|
paths() {
|
|
12746
13572
|
const location = this.location();
|
|
12747
|
-
const dir = location.ok ? location.data.dir :
|
|
12748
|
-
const checkout =
|
|
13573
|
+
const dir = location.ok ? location.data.dir : path17.join(this.root, ".workit");
|
|
13574
|
+
const checkout = path17.join(dir, "checkouts", checkoutSlug(this.root));
|
|
12749
13575
|
return {
|
|
12750
13576
|
dir,
|
|
12751
13577
|
shared: location.ok ? location.data.shared : false,
|
|
12752
|
-
marker:
|
|
13578
|
+
marker: path17.join(dir, "store.json"),
|
|
12753
13579
|
checkout,
|
|
12754
|
-
workspace:
|
|
12755
|
-
checkoutLock:
|
|
12756
|
-
tasks:
|
|
12757
|
-
index:
|
|
12758
|
-
blobs:
|
|
12759
|
-
legacy:
|
|
12760
|
-
v2:
|
|
13580
|
+
workspace: path17.join(checkout, "workspace.json"),
|
|
13581
|
+
checkoutLock: path17.join(checkout, "metadata.lock"),
|
|
13582
|
+
tasks: path17.join(dir, "tasks"),
|
|
13583
|
+
index: path17.join(dir, "task-index.json"),
|
|
13584
|
+
blobs: path17.join(dir, "blobs", "candidates"),
|
|
13585
|
+
legacy: path17.join(dir, "legacy", checkoutSlug(this.root), "v2"),
|
|
13586
|
+
v2: path17.join(this.root, ".workit")
|
|
12761
13587
|
};
|
|
12762
13588
|
}
|
|
12763
13589
|
eventsPath(taskId) {
|
|
12764
|
-
return
|
|
13590
|
+
return path17.join(this.paths().tasks, taskId, "events.jsonl");
|
|
12765
13591
|
}
|
|
12766
13592
|
snapshotPath(taskId) {
|
|
12767
|
-
return
|
|
13593
|
+
return path17.join(this.paths().tasks, taskId, "snapshot.json");
|
|
12768
13594
|
}
|
|
12769
13595
|
taskLockPath(taskId) {
|
|
12770
|
-
return
|
|
13596
|
+
return path17.join(this.paths().tasks, taskId, "lock");
|
|
12771
13597
|
}
|
|
12772
13598
|
ready(write = false) {
|
|
12773
13599
|
const location = this.location();
|
|
@@ -12806,13 +13632,13 @@ class TaskStore {
|
|
|
12806
13632
|
return {
|
|
12807
13633
|
read: (digest) => {
|
|
12808
13634
|
try {
|
|
12809
|
-
return JSON.parse(fs12.readFileSync(
|
|
13635
|
+
return JSON.parse(fs12.readFileSync(path17.join(dir, `${digest}.json`), "utf8"));
|
|
12810
13636
|
} catch (error) {
|
|
12811
13637
|
throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
|
|
12812
13638
|
}
|
|
12813
13639
|
},
|
|
12814
13640
|
write: (digest, value) => {
|
|
12815
|
-
const file =
|
|
13641
|
+
const file = path17.join(dir, `${digest}.json`);
|
|
12816
13642
|
if (fs12.existsSync(file)) {
|
|
12817
13643
|
const stamp = new Date;
|
|
12818
13644
|
try {
|
|
@@ -13004,7 +13830,7 @@ class TaskStore {
|
|
|
13004
13830
|
}
|
|
13005
13831
|
}
|
|
13006
13832
|
openTask(record, type, key, legacy = null) {
|
|
13007
|
-
const dir =
|
|
13833
|
+
const dir = path17.join(this.paths().tasks, record.id);
|
|
13008
13834
|
fs12.mkdirSync(dir, { recursive: true });
|
|
13009
13835
|
const blobs = this.blobStore();
|
|
13010
13836
|
const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
|
|
@@ -13097,7 +13923,7 @@ class TaskStore {
|
|
|
13097
13923
|
}
|
|
13098
13924
|
withKeyLock(key, operation) {
|
|
13099
13925
|
const digest = createHash11("sha256").update(key).digest("hex").slice(0, 32);
|
|
13100
|
-
return this.withLock(
|
|
13926
|
+
return this.withLock(path17.join(this.paths().dir, "keys", `${digest}.lock`), operation);
|
|
13101
13927
|
}
|
|
13102
13928
|
bindLocked(taskId, key, timestamp) {
|
|
13103
13929
|
const current = this.readWorkspace();
|
|
@@ -13238,7 +14064,7 @@ class TaskStore {
|
|
|
13238
14064
|
writeWorkspace(record) {
|
|
13239
14065
|
const file = this.paths().workspace;
|
|
13240
14066
|
try {
|
|
13241
|
-
fs12.mkdirSync(
|
|
14067
|
+
fs12.mkdirSync(path17.dirname(file), { recursive: true });
|
|
13242
14068
|
replaceFile(file, `${canonicalJson(record)}
|
|
13243
14069
|
`, true);
|
|
13244
14070
|
return success(null, null, null);
|
|
@@ -13316,23 +14142,23 @@ class TaskStore {
|
|
|
13316
14142
|
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
13317
14143
|
}
|
|
13318
14144
|
v2Dirs() {
|
|
13319
|
-
return [...new Set([this.root, this.origin])].map((dir) =>
|
|
14145
|
+
return [...new Set([this.root, this.origin])].map((dir) => path17.join(dir, ".workit"));
|
|
13320
14146
|
}
|
|
13321
14147
|
pendingMigrations() {
|
|
13322
14148
|
return this.v2Dirs().filter((dir) => {
|
|
13323
|
-
const raw = this.readV2Workspace(
|
|
14149
|
+
const raw = this.readV2Workspace(path17.join(dir, "workspace.json"));
|
|
13324
14150
|
return raw !== null && raw !== TOMBSTONE;
|
|
13325
14151
|
});
|
|
13326
14152
|
}
|
|
13327
14153
|
migrateV2(v2) {
|
|
13328
14154
|
const dirs = this.paths();
|
|
13329
|
-
const v2Workspace =
|
|
13330
|
-
const v2Tasks =
|
|
13331
|
-
const legacy =
|
|
14155
|
+
const v2Workspace = path17.join(v2, "workspace.json");
|
|
14156
|
+
const v2Tasks = path17.join(v2, "tasks");
|
|
14157
|
+
const legacy = path17.join(dirs.dir, "legacy", checkoutSlug(path17.dirname(v2)), "v2");
|
|
13332
14158
|
const raw = this.readV2Workspace(v2Workspace);
|
|
13333
14159
|
if (raw === null || raw === TOMBSTONE)
|
|
13334
14160
|
return success(null, null, null);
|
|
13335
|
-
return this.withLock(dirs.checkoutLock, () => this.withLock(
|
|
14161
|
+
return this.withLock(dirs.checkoutLock, () => this.withLock(path17.join(v2, "metadata.lock"), () => {
|
|
13336
14162
|
const workspace = this.readV2Workspace(v2Workspace);
|
|
13337
14163
|
if (workspace === null || workspace === TOMBSTONE)
|
|
13338
14164
|
return success(null, null, null);
|
|
@@ -13341,7 +14167,7 @@ class TaskStore {
|
|
|
13341
14167
|
return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
|
|
13342
14168
|
let files = [];
|
|
13343
14169
|
try {
|
|
13344
|
-
files = fs12.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(
|
|
14170
|
+
files = fs12.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path17.join(v2Tasks, name)));
|
|
13345
14171
|
} catch {}
|
|
13346
14172
|
const current = this.readWorkspace();
|
|
13347
14173
|
if (!current.ok)
|
|
@@ -13359,21 +14185,21 @@ class TaskStore {
|
|
|
13359
14185
|
let skipped = 0;
|
|
13360
14186
|
const done = new Map;
|
|
13361
14187
|
try {
|
|
13362
|
-
fs12.mkdirSync(
|
|
14188
|
+
fs12.mkdirSync(path17.join(legacy, "tasks"), { recursive: true });
|
|
13363
14189
|
for (const name of ["workspace.json", "index.json"])
|
|
13364
|
-
if (fs12.existsSync(
|
|
13365
|
-
fs12.copyFileSync(
|
|
14190
|
+
if (fs12.existsSync(path17.join(v2, name)))
|
|
14191
|
+
fs12.copyFileSync(path17.join(v2, name), path17.join(legacy, name));
|
|
13366
14192
|
for (const name of files) {
|
|
13367
14193
|
const id = name.slice(0, -5);
|
|
13368
|
-
const file =
|
|
14194
|
+
const file = path17.join(v2Tasks, name);
|
|
13369
14195
|
const bytes = fs12.readFileSync(file, "utf8");
|
|
13370
14196
|
const digest = sha256(bytes);
|
|
13371
|
-
fs12.writeFileSync(
|
|
14197
|
+
fs12.writeFileSync(path17.join(legacy, "tasks", name), bytes);
|
|
13372
14198
|
const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
|
|
13373
14199
|
if (!parsed.ok || parsed.data.id !== id)
|
|
13374
14200
|
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 });
|
|
13375
14201
|
const record = { ...parsed.data, workspaceId: target.id };
|
|
13376
|
-
const origin = { path:
|
|
14202
|
+
const origin = { path: path17.join(legacy, "tasks", name), digest };
|
|
13377
14203
|
if (!fs12.existsSync(this.eventsPath(id))) {
|
|
13378
14204
|
this.openTask(record, "migrated.from_v2", null, origin);
|
|
13379
14205
|
migrated += 1;
|
|
@@ -13409,7 +14235,7 @@ class TaskStore {
|
|
|
13409
14235
|
try {
|
|
13410
14236
|
let complete = true;
|
|
13411
14237
|
for (const [name, digest] of done) {
|
|
13412
|
-
const file =
|
|
14238
|
+
const file = path17.join(v2Tasks, name);
|
|
13413
14239
|
if (sha256(fs12.readFileSync(file, "utf8")) !== digest) {
|
|
13414
14240
|
complete = false;
|
|
13415
14241
|
continue;
|
|
@@ -13420,7 +14246,7 @@ class TaskStore {
|
|
|
13420
14246
|
return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
|
|
13421
14247
|
path: v2
|
|
13422
14248
|
});
|
|
13423
|
-
fs12.rmSync(
|
|
14249
|
+
fs12.rmSync(path17.join(v2, "index.json"), { force: true });
|
|
13424
14250
|
this.writeTombstone(v2, target.id);
|
|
13425
14251
|
} catch (error) {
|
|
13426
14252
|
return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
|
|
@@ -13440,7 +14266,7 @@ class TaskStore {
|
|
|
13440
14266
|
}
|
|
13441
14267
|
adoptLocalStore() {
|
|
13442
14268
|
const dirs = this.paths();
|
|
13443
|
-
const marker =
|
|
14269
|
+
const marker = path17.join(dirs.v2, "store.json");
|
|
13444
14270
|
if (!dirs.shared || !fs12.existsSync(marker))
|
|
13445
14271
|
return success(null, null, null);
|
|
13446
14272
|
return this.withLock(dirs.checkoutLock, () => {
|
|
@@ -13450,7 +14276,7 @@ class TaskStore {
|
|
|
13450
14276
|
try {
|
|
13451
14277
|
const merge = (from, to) => {
|
|
13452
14278
|
if (!fs12.existsSync(to)) {
|
|
13453
|
-
fs12.mkdirSync(
|
|
14279
|
+
fs12.mkdirSync(path17.dirname(to), { recursive: true });
|
|
13454
14280
|
try {
|
|
13455
14281
|
fs12.renameSync(from, to);
|
|
13456
14282
|
} catch {
|
|
@@ -13461,24 +14287,24 @@ class TaskStore {
|
|
|
13461
14287
|
}
|
|
13462
14288
|
if (fs12.lstatSync(from).isDirectory() && fs12.lstatSync(to).isDirectory())
|
|
13463
14289
|
for (const name of fs12.readdirSync(from))
|
|
13464
|
-
merge(
|
|
14290
|
+
merge(path17.join(from, name), path17.join(to, name));
|
|
13465
14291
|
};
|
|
13466
14292
|
for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
|
|
13467
|
-
const from =
|
|
14293
|
+
const from = path17.join(dirs.v2, sub);
|
|
13468
14294
|
if (!fs12.existsSync(from))
|
|
13469
14295
|
continue;
|
|
13470
14296
|
if (sub === "tasks") {
|
|
13471
14297
|
for (const name of fs12.readdirSync(from))
|
|
13472
|
-
if (validId(name) && !fs12.existsSync(
|
|
14298
|
+
if (validId(name) && !fs12.existsSync(path17.join(dirs.dir, sub, name)))
|
|
13473
14299
|
tasks += 1;
|
|
13474
14300
|
}
|
|
13475
|
-
merge(from,
|
|
14301
|
+
merge(from, path17.join(dirs.dir, sub));
|
|
13476
14302
|
}
|
|
13477
|
-
const ledger =
|
|
13478
|
-
if (fs12.existsSync(ledger) && !fs12.existsSync(
|
|
13479
|
-
merge(ledger,
|
|
13480
|
-
fs12.rmSync(
|
|
13481
|
-
fs12.renameSync(marker,
|
|
14303
|
+
const ledger = path17.join(dirs.v2, "ledger");
|
|
14304
|
+
if (fs12.existsSync(ledger) && !fs12.existsSync(path17.join(dirs.dir, "ledger")))
|
|
14305
|
+
merge(ledger, path17.join(dirs.dir, "ledger"));
|
|
14306
|
+
fs12.rmSync(path17.join(dirs.v2, "task-index.json"), { force: true });
|
|
14307
|
+
fs12.renameSync(marker, path17.join(dirs.v2, "store.moved.json"));
|
|
13482
14308
|
} catch (error) {
|
|
13483
14309
|
return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
|
|
13484
14310
|
path: dirs.v2
|
|
@@ -13516,7 +14342,7 @@ class TaskStore {
|
|
|
13516
14342
|
schemaVersion: SCHEMA_VERSION,
|
|
13517
14343
|
id: workspaceId ?? newId(),
|
|
13518
14344
|
revision: newRevision(),
|
|
13519
|
-
root:
|
|
14345
|
+
root: path17.dirname(v2),
|
|
13520
14346
|
runtime: { createdWith: version, updatedWith: version },
|
|
13521
14347
|
writer: null,
|
|
13522
14348
|
store: {
|
|
@@ -13528,11 +14354,11 @@ class TaskStore {
|
|
|
13528
14354
|
critical: ["store"]
|
|
13529
14355
|
};
|
|
13530
14356
|
fs12.mkdirSync(v2, { recursive: true });
|
|
13531
|
-
const ignore =
|
|
14357
|
+
const ignore = path17.join(v2, ".gitignore");
|
|
13532
14358
|
if (!fs12.existsSync(ignore))
|
|
13533
14359
|
fs12.writeFileSync(ignore, `*
|
|
13534
14360
|
`);
|
|
13535
|
-
replaceFile(
|
|
14361
|
+
replaceFile(path17.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
|
|
13536
14362
|
`, true);
|
|
13537
14363
|
}
|
|
13538
14364
|
stubFor(taskId) {
|
|
@@ -13556,8 +14382,8 @@ class TaskStore {
|
|
|
13556
14382
|
}
|
|
13557
14383
|
writeTombstoneIfMissing() {
|
|
13558
14384
|
try {
|
|
13559
|
-
const v2 =
|
|
13560
|
-
if (fs12.existsSync(
|
|
14385
|
+
const v2 = path17.join(this.root, ".workit");
|
|
14386
|
+
if (fs12.existsSync(path17.join(v2, "workspace.json")))
|
|
13561
14387
|
return;
|
|
13562
14388
|
let id = null;
|
|
13563
14389
|
try {
|
|
@@ -13574,7 +14400,7 @@ class TaskStore {
|
|
|
13574
14400
|
replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
|
|
13575
14401
|
`, false);
|
|
13576
14402
|
if (!dirs.shared) {
|
|
13577
|
-
const ignore =
|
|
14403
|
+
const ignore = path17.join(dirs.dir, ".gitignore");
|
|
13578
14404
|
let current = null;
|
|
13579
14405
|
try {
|
|
13580
14406
|
current = fs12.readFileSync(ignore, "utf8");
|
|
@@ -13634,7 +14460,7 @@ class TaskStore {
|
|
|
13634
14460
|
withLock(lockPath, operation) {
|
|
13635
14461
|
try {
|
|
13636
14462
|
this.initializeStorage();
|
|
13637
|
-
fs12.mkdirSync(
|
|
14463
|
+
fs12.mkdirSync(path17.dirname(lockPath), { recursive: true });
|
|
13638
14464
|
} catch (error) {
|
|
13639
14465
|
return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
13640
14466
|
path: this.paths().dir
|
|
@@ -13697,7 +14523,7 @@ class TaskStore {
|
|
|
13697
14523
|
if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
|
|
13698
14524
|
return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
|
|
13699
14525
|
path: lockPath,
|
|
13700
|
-
guidance: `Check permissions and the read-only attribute on ${
|
|
14526
|
+
guidance: `Check permissions and the read-only attribute on ${path17.dirname(lockPath)}.`
|
|
13701
14527
|
});
|
|
13702
14528
|
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
13703
14529
|
let lock = null;
|
|
@@ -13724,18 +14550,18 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
13724
14550
|
import { createHash as createHash12 } from "node:crypto";
|
|
13725
14551
|
import * as fs14 from "node:fs";
|
|
13726
14552
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
13727
|
-
import
|
|
14553
|
+
import path19 from "node:path";
|
|
13728
14554
|
|
|
13729
14555
|
// packages/workit-core/src/check-config.ts
|
|
13730
14556
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
13731
14557
|
import fs13 from "node:fs";
|
|
13732
|
-
import
|
|
14558
|
+
import path18 from "node:path";
|
|
13733
14559
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
13734
14560
|
var CHECKS_FILE = ".workit/checks.json";
|
|
13735
14561
|
var LEGACY_CHECKS_FILE = "workit.checks.json";
|
|
13736
14562
|
function checksConfigFile(root) {
|
|
13737
|
-
const current =
|
|
13738
|
-
const legacy =
|
|
14563
|
+
const current = path18.join(root, CHECKS_FILE);
|
|
14564
|
+
const legacy = path18.join(root, LEGACY_CHECKS_FILE);
|
|
13739
14565
|
return !fs13.existsSync(current) && fs13.existsSync(legacy) ? legacy : current;
|
|
13740
14566
|
}
|
|
13741
14567
|
function repoHeavyCommands(root) {
|
|
@@ -13786,7 +14612,7 @@ function splitCommand(command) {
|
|
|
13786
14612
|
argv.push(current);
|
|
13787
14613
|
return argv.length ? argv : new Error("empty command");
|
|
13788
14614
|
}
|
|
13789
|
-
var
|
|
14615
|
+
var git5 = (cwd, args) => {
|
|
13790
14616
|
const run = spawnSync9("git", args, {
|
|
13791
14617
|
cwd,
|
|
13792
14618
|
encoding: "utf8",
|
|
@@ -13798,10 +14624,10 @@ var git4 = (cwd, args) => {
|
|
|
13798
14624
|
return value || null;
|
|
13799
14625
|
};
|
|
13800
14626
|
function checkRoot(cwd) {
|
|
13801
|
-
return
|
|
14627
|
+
return path18.resolve(git5(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
13802
14628
|
}
|
|
13803
14629
|
var packageManager = (root) => {
|
|
13804
|
-
const has = (file) => fs13.existsSync(
|
|
14630
|
+
const has = (file) => fs13.existsSync(path18.join(root, file));
|
|
13805
14631
|
if (has("bun.lock") || has("bun.lockb"))
|
|
13806
14632
|
return "bun";
|
|
13807
14633
|
if (has("pnpm-lock.yaml"))
|
|
@@ -13821,7 +14647,7 @@ var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
|
13821
14647
|
function detectedChecks(root) {
|
|
13822
14648
|
const read = (file) => {
|
|
13823
14649
|
try {
|
|
13824
|
-
return fs13.readFileSync(
|
|
14650
|
+
return fs13.readFileSync(path18.join(root, file), "utf8");
|
|
13825
14651
|
} catch {
|
|
13826
14652
|
return null;
|
|
13827
14653
|
}
|
|
@@ -13861,7 +14687,7 @@ var GATES = ["testing", "verification"];
|
|
|
13861
14687
|
function loadCheckConfig(cwd) {
|
|
13862
14688
|
const root = checkRoot(cwd);
|
|
13863
14689
|
const file = checksConfigFile(root);
|
|
13864
|
-
const legacy =
|
|
14690
|
+
const legacy = path18.basename(file) === LEGACY_CHECKS_FILE;
|
|
13865
14691
|
const label = legacy ? LEGACY_CHECKS_FILE : CHECKS_FILE;
|
|
13866
14692
|
const note = legacy ? {
|
|
13867
14693
|
note: `${LEGACY_CHECKS_FILE} is deprecated; move it to ${CHECKS_FILE} (workit doctor --fix)`
|
|
@@ -13939,10 +14765,10 @@ function workitAvailable(env = process.env) {
|
|
|
13939
14765
|
return workitOnPath;
|
|
13940
14766
|
const windows = process.platform === "win32";
|
|
13941
14767
|
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
13942
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(
|
|
14768
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path18.delimiter).filter(Boolean);
|
|
13943
14769
|
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
13944
14770
|
try {
|
|
13945
|
-
return fs13.statSync(
|
|
14771
|
+
return fs13.statSync(path18.join(dir, `workit${ext}`)).isFile();
|
|
13946
14772
|
} catch {
|
|
13947
14773
|
return false;
|
|
13948
14774
|
}
|
|
@@ -13955,14 +14781,14 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
13955
14781
|
|
|
13956
14782
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
13957
14783
|
var digestBytes = (value) => createHash12("sha256").update(value).digest("hex");
|
|
13958
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
13959
|
-
var relative = (root, candidate) =>
|
|
14784
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path19.sep}`);
|
|
14785
|
+
var relative = (root, candidate) => path19.relative(root, candidate).split(path19.sep).join("/") || ".";
|
|
13960
14786
|
var canonicalPath = (value) => {
|
|
13961
14787
|
if (typeof value !== "string" || !value)
|
|
13962
14788
|
return null;
|
|
13963
14789
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
13964
14790
|
return null;
|
|
13965
|
-
const normalized =
|
|
14791
|
+
const normalized = path19.posix.normalize(value);
|
|
13966
14792
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
13967
14793
|
return null;
|
|
13968
14794
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14010,14 +14836,14 @@ var gitPaths = (root) => {
|
|
|
14010
14836
|
git: !/not a git repository/i.test(stderr)
|
|
14011
14837
|
};
|
|
14012
14838
|
}
|
|
14013
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14839
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")) : [];
|
|
14014
14840
|
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14015
14841
|
if (stagedDeleted.status !== 0) {
|
|
14016
14842
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14017
14843
|
if (!/not a git repository/i.test(stderr))
|
|
14018
14844
|
return { paths, uncertain: true, git: true };
|
|
14019
14845
|
} else if (stagedDeleted.stdout) {
|
|
14020
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14846
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")));
|
|
14021
14847
|
}
|
|
14022
14848
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14023
14849
|
};
|
|
@@ -14032,7 +14858,7 @@ var walk = (root, directory, output) => {
|
|
|
14032
14858
|
for (const name of names) {
|
|
14033
14859
|
if (name === ".git" || name === ".workit")
|
|
14034
14860
|
continue;
|
|
14035
|
-
const target =
|
|
14861
|
+
const target = path19.join(directory, name);
|
|
14036
14862
|
let stat;
|
|
14037
14863
|
try {
|
|
14038
14864
|
stat = fs14.lstatSync(target);
|
|
@@ -14050,12 +14876,12 @@ var walk = (root, directory, output) => {
|
|
|
14050
14876
|
var scopeRoots = (root, scope) => {
|
|
14051
14877
|
const roots = [];
|
|
14052
14878
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14053
|
-
const target =
|
|
14879
|
+
const target = path19.resolve(root, item);
|
|
14054
14880
|
if (!inside(root, target))
|
|
14055
14881
|
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` }] });
|
|
14056
14882
|
let ancestor = target;
|
|
14057
14883
|
while (!fs14.existsSync(ancestor) && ancestor !== root)
|
|
14058
|
-
ancestor =
|
|
14884
|
+
ancestor = path19.dirname(ancestor);
|
|
14059
14885
|
try {
|
|
14060
14886
|
if (!inside(root, fs14.realpathSync(ancestor)))
|
|
14061
14887
|
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` }] });
|
|
@@ -14107,7 +14933,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14107
14933
|
for (const item of [...names].toSorted()) {
|
|
14108
14934
|
if (!scopeMatches(item, normalizedScope))
|
|
14109
14935
|
continue;
|
|
14110
|
-
const target =
|
|
14936
|
+
const target = path19.join(checkout, item);
|
|
14111
14937
|
try {
|
|
14112
14938
|
const stat = fs14.lstatSync(target);
|
|
14113
14939
|
if (stat.isSymbolicLink()) {
|
|
@@ -14291,23 +15117,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14291
15117
|
continue;
|
|
14292
15118
|
if (!reference.digest)
|
|
14293
15119
|
return failure2("invalid_input", "document references require a byte digest");
|
|
14294
|
-
const target =
|
|
14295
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15120
|
+
const target = path19.resolve(root, reference.path);
|
|
15121
|
+
if (target !== root && !target.startsWith(`${root}${path19.sep}`))
|
|
14296
15122
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14297
15123
|
fields: [
|
|
14298
15124
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
14299
15125
|
]
|
|
14300
15126
|
});
|
|
14301
15127
|
try {
|
|
14302
|
-
const below =
|
|
15128
|
+
const below = path19.relative(root, target);
|
|
14303
15129
|
let current = root;
|
|
14304
|
-
for (const segment of below.split(
|
|
15130
|
+
for (const segment of below.split(path19.sep)) {
|
|
14305
15131
|
if (!segment)
|
|
14306
15132
|
continue;
|
|
14307
|
-
current =
|
|
15133
|
+
current = path19.join(current, segment);
|
|
14308
15134
|
const stat = fs14.lstatSync(current);
|
|
14309
15135
|
const real = fs14.realpathSync(current);
|
|
14310
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15136
|
+
if (real !== root && !real.startsWith(`${root}${path19.sep}`))
|
|
14311
15137
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14312
15138
|
fields: [
|
|
14313
15139
|
{
|
|
@@ -14740,9 +15566,9 @@ Autonomy contract:
|
|
|
14740
15566
|
Prose is a note: only a configured check the CLI ran (\`workit check <name>\`)
|
|
14741
15567
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
14742
15568
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
14743
|
-
self-reviewed, never verified. High risk,
|
|
14744
|
-
merge need a verifier with its
|
|
14745
|
-
\`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
15569
|
+
self-reviewed, never verified. High risk, a focused or full lane and
|
|
15570
|
+
merge under \`verification: self|independent\` need a verifier with its
|
|
15571
|
+
own host-derived session (a subagent's \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
14746
15572
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
14747
15573
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
14748
15574
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -16608,7 +17434,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
16608
17434
|
// packages/workit-core/src/hooks/context.ts
|
|
16609
17435
|
import { createHash as createHash13 } from "node:crypto";
|
|
16610
17436
|
import fs15 from "node:fs";
|
|
16611
|
-
import
|
|
17437
|
+
import path20 from "node:path";
|
|
16612
17438
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
16613
17439
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
16614
17440
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
@@ -16693,7 +17519,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
16693
17519
|
return null;
|
|
16694
17520
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
16695
17521
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
16696
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
17522
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path20.resolve(store.root, ref.path)] : []));
|
|
16697
17523
|
const observedFiles = filesKey(files);
|
|
16698
17524
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
16699
17525
|
if (!compact.ok || !compact.data)
|
|
@@ -16771,14 +17597,14 @@ var turnMarker = (input) => {
|
|
|
16771
17597
|
if (location instanceof Error)
|
|
16772
17598
|
return null;
|
|
16773
17599
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
16774
|
-
return
|
|
17600
|
+
return path20.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
16775
17601
|
} catch {
|
|
16776
17602
|
return null;
|
|
16777
17603
|
}
|
|
16778
17604
|
};
|
|
16779
17605
|
var writeTurnDigest = (file, digest) => {
|
|
16780
17606
|
try {
|
|
16781
|
-
fs15.mkdirSync(
|
|
17607
|
+
fs15.mkdirSync(path20.dirname(file), { recursive: true });
|
|
16782
17608
|
fs15.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
16783
17609
|
`);
|
|
16784
17610
|
} catch {}
|
|
@@ -16793,7 +17619,7 @@ var pruneTurnDigests = (dir, now) => {
|
|
|
16793
17619
|
for (const name of fs15.readdirSync(dir)) {
|
|
16794
17620
|
if (!name.startsWith("turn-"))
|
|
16795
17621
|
continue;
|
|
16796
|
-
const file =
|
|
17622
|
+
const file = path20.join(dir, name);
|
|
16797
17623
|
try {
|
|
16798
17624
|
if (now - fs15.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
16799
17625
|
fs15.rmSync(file, { force: true });
|
|
@@ -16823,7 +17649,7 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
16823
17649
|
const file = turnMarker(input);
|
|
16824
17650
|
if (!file)
|
|
16825
17651
|
return;
|
|
16826
|
-
pruneTurnDigests(
|
|
17652
|
+
pruneTurnDigests(path20.dirname(file), Date.now());
|
|
16827
17653
|
if (text)
|
|
16828
17654
|
writeTurnDigest(file, digestOf(text));
|
|
16829
17655
|
else
|
|
@@ -16831,20 +17657,20 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
16831
17657
|
};
|
|
16832
17658
|
// packages/workit-core/src/core/route-intent.ts
|
|
16833
17659
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
16834
|
-
import { existsSync as
|
|
16835
|
-
import
|
|
17660
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
17661
|
+
import path23 from "node:path";
|
|
16836
17662
|
|
|
16837
17663
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
16838
17664
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
16839
17665
|
import { createHash as createHash15 } from "node:crypto";
|
|
16840
17666
|
import fs17 from "node:fs";
|
|
16841
17667
|
import os5 from "node:os";
|
|
16842
|
-
import
|
|
17668
|
+
import path22 from "node:path";
|
|
16843
17669
|
|
|
16844
17670
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
16845
17671
|
import { createHash as createHash14 } from "node:crypto";
|
|
16846
17672
|
import fs16 from "node:fs";
|
|
16847
|
-
import
|
|
17673
|
+
import path21 from "node:path";
|
|
16848
17674
|
var sessionMarkerFile = (input, session, store) => {
|
|
16849
17675
|
let dir = store;
|
|
16850
17676
|
if (dir === undefined) {
|
|
@@ -16854,7 +17680,7 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
16854
17680
|
dir = location.dir;
|
|
16855
17681
|
}
|
|
16856
17682
|
const name = createHash14("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
16857
|
-
return
|
|
17683
|
+
return path21.join(dir, "hooks", `skills-${name}.json`);
|
|
16858
17684
|
};
|
|
16859
17685
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
16860
17686
|
var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
|
|
@@ -16881,7 +17707,7 @@ var parseMarker = (text) => {
|
|
|
16881
17707
|
};
|
|
16882
17708
|
var readSessionMarker = (file) => parseMarker(readRaw(file));
|
|
16883
17709
|
var writeAtomic = (file, marker) => {
|
|
16884
|
-
fs16.mkdirSync(
|
|
17710
|
+
fs16.mkdirSync(path21.dirname(file), { recursive: true });
|
|
16885
17711
|
const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
16886
17712
|
try {
|
|
16887
17713
|
fs16.writeFileSync(temp, JSON.stringify(marker));
|
|
@@ -17409,17 +18235,17 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
17409
18235
|
]);
|
|
17410
18236
|
var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
17411
18237
|
var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
17412
|
-
var
|
|
18238
|
+
var ASSIGNMENT2 = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
17413
18239
|
var MAX_DEPTH = 4;
|
|
17414
18240
|
var MAX_EXPANSIONS = 16;
|
|
17415
|
-
var isAbsolute = (value) =>
|
|
18241
|
+
var isAbsolute = (value) => path22.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
17416
18242
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
17417
18243
|
var joinDir = (base, raw) => {
|
|
17418
18244
|
const next = raw.replace(HERE, ".");
|
|
17419
18245
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
17420
18246
|
return;
|
|
17421
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
17422
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
18247
|
+
const expanded = next === "~" || next.startsWith("~/") ? path22.join(os5.homedir(), next.slice(1)) : next;
|
|
18248
|
+
return base === null || isAbsolute(expanded) ? expanded : path22.join(base, expanded);
|
|
17423
18249
|
};
|
|
17424
18250
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
17425
18251
|
var cdTarget = (words) => {
|
|
@@ -17436,7 +18262,7 @@ var stripPrefixes = (input) => {
|
|
|
17436
18262
|
const first = words[0];
|
|
17437
18263
|
if (first === undefined)
|
|
17438
18264
|
return { words, env };
|
|
17439
|
-
const assignment =
|
|
18265
|
+
const assignment = ASSIGNMENT2.exec(first);
|
|
17440
18266
|
if (assignment) {
|
|
17441
18267
|
if (GIT_CONFIG_ENV.test(assignment[1]))
|
|
17442
18268
|
env[assignment[1]] = assignment[2];
|
|
@@ -17495,10 +18321,10 @@ var assignments = (words, vars) => {
|
|
|
17495
18321
|
while (rest[0]?.startsWith("-"))
|
|
17496
18322
|
rest = rest.slice(1);
|
|
17497
18323
|
}
|
|
17498
|
-
if (rest.length === 0 || !rest.every((word) =>
|
|
18324
|
+
if (rest.length === 0 || !rest.every((word) => ASSIGNMENT2.test(word)))
|
|
17499
18325
|
return null;
|
|
17500
18326
|
return rest.map((word) => {
|
|
17501
|
-
const [, name, value] =
|
|
18327
|
+
const [, name, value] = ASSIGNMENT2.exec(word);
|
|
17502
18328
|
return [name, expandWord(value, vars)];
|
|
17503
18329
|
});
|
|
17504
18330
|
};
|
|
@@ -17686,7 +18512,7 @@ var splitString = (line) => {
|
|
|
17686
18512
|
return line.slice(index).join(" ").slice(15);
|
|
17687
18513
|
if (/^-S./.test(word))
|
|
17688
18514
|
return [word.slice(2), ...line.slice(index + 1)].join(" ");
|
|
17689
|
-
if (!word.startsWith("-") && !
|
|
18515
|
+
if (!word.startsWith("-") && !ASSIGNMENT2.test(word))
|
|
17690
18516
|
return null;
|
|
17691
18517
|
}
|
|
17692
18518
|
return null;
|
|
@@ -17953,6 +18779,24 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
|
17953
18779
|
}
|
|
17954
18780
|
return found;
|
|
17955
18781
|
}
|
|
18782
|
+
var WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
|
|
18783
|
+
function skipWorkitGlobals(args) {
|
|
18784
|
+
let cwd = null;
|
|
18785
|
+
let at = 0;
|
|
18786
|
+
for (;at < args.length; at++) {
|
|
18787
|
+
const match = WORKIT_GLOBAL.exec(args[at]);
|
|
18788
|
+
if (match?.[1] === "=") {
|
|
18789
|
+
if (args[at].startsWith("--cwd="))
|
|
18790
|
+
cwd = args[at].slice(6);
|
|
18791
|
+
} else if (match && args[at + 1] !== undefined) {
|
|
18792
|
+
if (args[at] === "--cwd")
|
|
18793
|
+
cwd = args[at + 1];
|
|
18794
|
+
at++;
|
|
18795
|
+
} else if (args[at] !== "--json")
|
|
18796
|
+
break;
|
|
18797
|
+
}
|
|
18798
|
+
return { rest: args.slice(at), cwd };
|
|
18799
|
+
}
|
|
17956
18800
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17957
18801
|
var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
17958
18802
|
var pushDestination = (destination) => {
|
|
@@ -18252,17 +19096,17 @@ var headSha2 = (repo) => {
|
|
|
18252
19096
|
if (!git || git.reftable)
|
|
18253
19097
|
return null;
|
|
18254
19098
|
try {
|
|
18255
|
-
const head = fs17.readFileSync(
|
|
19099
|
+
const head = fs17.readFileSync(path22.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18256
19100
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18257
19101
|
return head;
|
|
18258
19102
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18259
19103
|
if (!ref)
|
|
18260
19104
|
return null;
|
|
18261
|
-
const common =
|
|
19105
|
+
const common = path22.dirname(repo.location.dir);
|
|
18262
19106
|
try {
|
|
18263
|
-
return fs17.readFileSync(
|
|
19107
|
+
return fs17.readFileSync(path22.join(common, ref), "utf8").trim() || null;
|
|
18264
19108
|
} catch {}
|
|
18265
|
-
const packed = fs17.readFileSync(
|
|
19109
|
+
const packed = fs17.readFileSync(path22.join(common, "packed-refs"), "utf8");
|
|
18266
19110
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18267
19111
|
} catch {
|
|
18268
19112
|
return null;
|
|
@@ -18350,7 +19194,7 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
18350
19194
|
const sub = invocation.args[0];
|
|
18351
19195
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
18352
19196
|
return [invocation];
|
|
18353
|
-
const dir =
|
|
19197
|
+
const dir = path22.resolve(root, invocation.dir ?? ".");
|
|
18354
19198
|
if (!fs17.existsSync(dir))
|
|
18355
19199
|
return [invocation];
|
|
18356
19200
|
const config = gitConfig(dir, invocation, cache);
|
|
@@ -18373,11 +19217,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
18373
19217
|
var normalizedUrl = (url, dir) => {
|
|
18374
19218
|
const trimmed = url.trim();
|
|
18375
19219
|
if (/^file:\/\//i.test(trimmed))
|
|
18376
|
-
return
|
|
19220
|
+
return path22.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18377
19221
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
18378
19222
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
18379
19223
|
if (!scheme && !scp)
|
|
18380
|
-
return
|
|
19224
|
+
return path22.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18381
19225
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
18382
19226
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18383
19227
|
};
|
|
@@ -18397,7 +19241,7 @@ var rewrittenUrl = (url, config) => {
|
|
|
18397
19241
|
}
|
|
18398
19242
|
return url;
|
|
18399
19243
|
};
|
|
18400
|
-
var
|
|
19244
|
+
var pushUrl2 = (config, remote, dir) => {
|
|
18401
19245
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
18402
19246
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
18403
19247
|
};
|
|
@@ -18425,7 +19269,7 @@ var toPushRemote = (repo, remote, config) => {
|
|
|
18425
19269
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
18426
19270
|
if (pushRemote === null || pushRemote === remote)
|
|
18427
19271
|
return true;
|
|
18428
|
-
return
|
|
19272
|
+
return pushUrl2(config, remote, repo.dir) === pushUrl2(config, pushRemote, repo.dir);
|
|
18429
19273
|
};
|
|
18430
19274
|
var quote = (text) => `\`${text}\``;
|
|
18431
19275
|
var refusal = (action, branch, dir) => {
|
|
@@ -18601,7 +19445,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
18601
19445
|
const out = [];
|
|
18602
19446
|
const repos = new Map;
|
|
18603
19447
|
const repoFor = (relative) => {
|
|
18604
|
-
const dir =
|
|
19448
|
+
const dir = path22.resolve(cwd, relative ?? ".");
|
|
18605
19449
|
if (!repos.has(dir)) {
|
|
18606
19450
|
const repo = repoAt(dir);
|
|
18607
19451
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18687,7 +19531,7 @@ var analysisDenial = (error) => ({
|
|
|
18687
19531
|
});
|
|
18688
19532
|
var NEXT_COMMAND = "next";
|
|
18689
19533
|
var MARKER_TTL_SECONDS = 3600;
|
|
18690
|
-
var markerFile = (repo, session, key) =>
|
|
19534
|
+
var markerFile = (repo, session, key) => path22.join(repo.location.dir, "hooks", `commit-${createHash15("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18691
19535
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash15("sha256").update(command).digest("hex")}`;
|
|
18692
19536
|
function noteRawCommit(input, command, key) {
|
|
18693
19537
|
try {
|
|
@@ -18698,7 +19542,7 @@ function noteRawCommit(input, command, key) {
|
|
|
18698
19542
|
if (action.kind !== "commit")
|
|
18699
19543
|
continue;
|
|
18700
19544
|
const file = markerFile(repo, input.session.id, key);
|
|
18701
|
-
fs17.mkdirSync(
|
|
19545
|
+
fs17.mkdirSync(path22.dirname(file), { recursive: true });
|
|
18702
19546
|
fs17.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18703
19547
|
`);
|
|
18704
19548
|
}
|
|
@@ -18929,8 +19773,8 @@ var judgedDir = (root, dir) => {
|
|
|
18929
19773
|
return null;
|
|
18930
19774
|
if (dir === null)
|
|
18931
19775
|
return root;
|
|
18932
|
-
const resolved =
|
|
18933
|
-
return
|
|
19776
|
+
const resolved = path23.resolve(root, dir);
|
|
19777
|
+
return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
18934
19778
|
};
|
|
18935
19779
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
18936
19780
|
let judged = false;
|
|
@@ -18966,7 +19810,7 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
18966
19810
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
18967
19811
|
import fs18 from "node:fs";
|
|
18968
19812
|
import os6 from "node:os";
|
|
18969
|
-
import
|
|
19813
|
+
import path24 from "node:path";
|
|
18970
19814
|
var EMPTY2 = () => ({ deny: [], allow: [] });
|
|
18971
19815
|
var readText2 = (file) => {
|
|
18972
19816
|
try {
|
|
@@ -19007,19 +19851,19 @@ function parseJsonc(text) {
|
|
|
19007
19851
|
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
19008
19852
|
}
|
|
19009
19853
|
function gitTop(cwd) {
|
|
19010
|
-
for (let dir =
|
|
19011
|
-
if (fs18.existsSync(
|
|
19854
|
+
for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
|
|
19855
|
+
if (fs18.existsSync(path24.join(dir, ".git")))
|
|
19012
19856
|
return dir;
|
|
19013
|
-
if (
|
|
19857
|
+
if (path24.dirname(dir) === dir)
|
|
19014
19858
|
return null;
|
|
19015
19859
|
}
|
|
19016
19860
|
}
|
|
19017
19861
|
var projectDirs = (cwd) => {
|
|
19018
19862
|
const top = gitTop(cwd);
|
|
19019
19863
|
const dirs = [];
|
|
19020
|
-
for (let dir =
|
|
19864
|
+
for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
|
|
19021
19865
|
dirs.push(dir);
|
|
19022
|
-
if (!top || dir === top ||
|
|
19866
|
+
if (!top || dir === top || path24.dirname(dir) === dir)
|
|
19023
19867
|
return dirs;
|
|
19024
19868
|
}
|
|
19025
19869
|
};
|
|
@@ -19031,12 +19875,12 @@ var CLAUDE_MANAGED = {
|
|
|
19031
19875
|
};
|
|
19032
19876
|
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
19033
19877
|
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
19034
|
-
const user = env.CLAUDE_CONFIG_DIR ||
|
|
19878
|
+
const user = env.CLAUDE_CONFIG_DIR || path24.join(homeOf(env), ".claude");
|
|
19035
19879
|
return [
|
|
19036
19880
|
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19881
|
+
path24.join(user, "settings.json"),
|
|
19882
|
+
path24.join(project, ".claude", "settings.json"),
|
|
19883
|
+
path24.join(project, ".claude", "settings.local.json")
|
|
19040
19884
|
];
|
|
19041
19885
|
}
|
|
19042
19886
|
var bashPattern = (entry) => {
|
|
@@ -19126,10 +19970,10 @@ var opencodeConfig = (value, rules) => {
|
|
|
19126
19970
|
function opencodeRules(cwd, env = process.env) {
|
|
19127
19971
|
const rules = EMPTY2();
|
|
19128
19972
|
try {
|
|
19129
|
-
const config = env.XDG_CONFIG_HOME ||
|
|
19973
|
+
const config = env.XDG_CONFIG_HOME || path24.join(homeOf(env), ".config");
|
|
19130
19974
|
const files = [
|
|
19131
|
-
...OPENCODE_NAMES.map((name) =>
|
|
19132
|
-
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [
|
|
19975
|
+
...OPENCODE_NAMES.map((name) => path24.join(config, "opencode", name)),
|
|
19976
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path24.join(dir, name), path24.join(dir, ".opencode", name)])),
|
|
19133
19977
|
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
19134
19978
|
];
|
|
19135
19979
|
for (const file of files) {
|
|
@@ -19173,9 +20017,9 @@ var codexRule = (body) => {
|
|
|
19173
20017
|
function codexRules(cwd, env = process.env) {
|
|
19174
20018
|
const rules = EMPTY2();
|
|
19175
20019
|
try {
|
|
19176
|
-
const home = env.CODEX_HOME ||
|
|
20020
|
+
const home = env.CODEX_HOME || path24.join(homeOf(env), ".codex");
|
|
19177
20021
|
const top = gitTop(cwd) ?? cwd;
|
|
19178
|
-
for (const dir of [
|
|
20022
|
+
for (const dir of [path24.join(home, "rules"), path24.join(top, ".codex", "rules")]) {
|
|
19179
20023
|
let files;
|
|
19180
20024
|
try {
|
|
19181
20025
|
files = fs18.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
@@ -19185,7 +20029,7 @@ function codexRules(cwd, env = process.env) {
|
|
|
19185
20029
|
return "unreadable";
|
|
19186
20030
|
}
|
|
19187
20031
|
for (const name of files) {
|
|
19188
|
-
const text = readText2(
|
|
20032
|
+
const text = readText2(path24.join(dir, name)) ?? "";
|
|
19189
20033
|
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
19190
20034
|
const rule = codexRule(part);
|
|
19191
20035
|
if (!rule)
|
|
@@ -19204,8 +20048,8 @@ function cursorRules(cwd, env = process.env) {
|
|
|
19204
20048
|
try {
|
|
19205
20049
|
const top = gitTop(cwd) ?? cwd;
|
|
19206
20050
|
for (const file of [
|
|
19207
|
-
|
|
19208
|
-
|
|
20051
|
+
path24.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
20052
|
+
path24.join(top, ".cursor", "cli.json")
|
|
19209
20053
|
]) {
|
|
19210
20054
|
const text = readText2(file);
|
|
19211
20055
|
if (text === null)
|
|
@@ -19281,21 +20125,21 @@ function wrapAllowed(read, words, command, options = {}) {
|
|
|
19281
20125
|
// packages/workit-core/src/core/heavy.ts
|
|
19282
20126
|
import fs19 from "node:fs";
|
|
19283
20127
|
import os7 from "node:os";
|
|
19284
|
-
import
|
|
20128
|
+
import path25 from "node:path";
|
|
19285
20129
|
function stateDir(env, platform = process.platform) {
|
|
19286
20130
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
19287
20131
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
19288
20132
|
if (env.XDG_STATE_HOME)
|
|
19289
|
-
return
|
|
20133
|
+
return path25.join(env.XDG_STATE_HOME, "workit");
|
|
19290
20134
|
const home = env.HOME || os7.homedir();
|
|
19291
20135
|
if (platform === "darwin")
|
|
19292
|
-
return
|
|
20136
|
+
return path25.join(home, "Library", "Application Support", "workit");
|
|
19293
20137
|
if (platform === "win32")
|
|
19294
|
-
return
|
|
19295
|
-
return
|
|
20138
|
+
return path25.join(env.LOCALAPPDATA ?? path25.join(home, "AppData", "Local"), "workit");
|
|
20139
|
+
return path25.join(home, ".local", "state", "workit");
|
|
19296
20140
|
}
|
|
19297
20141
|
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
19298
|
-
var heavyCommandsFile = (env) =>
|
|
20142
|
+
var heavyCommandsFile = (env) => path25.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
19299
20143
|
var HEAVY_SCRIPT = /^(?:test|tests|t|tst|unit|integration|e2e|coverage|cov|lint|eslint|typecheck|type-check|types|tsc|check|verify|validate|build|compile|bundle|ci|sonar|cypress|playwright|jest|vitest|mocha|bench|benchmark|all)(?:$|[:_.-])/iu;
|
|
19300
20144
|
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
19301
20145
|
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
@@ -19570,7 +20414,7 @@ function machinePatterns(env) {
|
|
|
19570
20414
|
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
19571
20415
|
}
|
|
19572
20416
|
function repoPatterns(cwd) {
|
|
19573
|
-
return repoHeavyCommands(gitTop(cwd) ??
|
|
20417
|
+
return repoHeavyCommands(gitTop(cwd) ?? path25.resolve(cwd));
|
|
19574
20418
|
}
|
|
19575
20419
|
function heavyLines(lines, options) {
|
|
19576
20420
|
const found = [];
|
|
@@ -19648,12 +20492,12 @@ function heavyPlan(command, options) {
|
|
|
19648
20492
|
}
|
|
19649
20493
|
function workitOnPath2(env = process.env) {
|
|
19650
20494
|
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
19651
|
-
for (const dir of (env.PATH ?? "").split(
|
|
20495
|
+
for (const dir of (env.PATH ?? "").split(path25.delimiter))
|
|
19652
20496
|
for (const name of names) {
|
|
19653
20497
|
if (!dir)
|
|
19654
20498
|
continue;
|
|
19655
20499
|
try {
|
|
19656
|
-
fs19.accessSync(
|
|
20500
|
+
fs19.accessSync(path25.join(dir, name), fs19.constants.X_OK);
|
|
19657
20501
|
return true;
|
|
19658
20502
|
} catch {}
|
|
19659
20503
|
}
|
|
@@ -19672,20 +20516,20 @@ function leaseRewrite(command) {
|
|
|
19672
20516
|
// packages/workit-core/src/core/heavy-words.ts
|
|
19673
20517
|
import fs20 from "node:fs";
|
|
19674
20518
|
import os8 from "node:os";
|
|
19675
|
-
import
|
|
20519
|
+
import path26 from "node:path";
|
|
19676
20520
|
var HEAVY_WORDS = new Set(("npm npx pnpm pnpx yarn bun bunx deno corepack make gmake docker podman nerdctl " + "python python3 py uv poetry pdm pipenv hatch rye bundle rake nx " + "jest vitest mocha ava tap karma jasmine nyc c8 tsc vue-tsc svelte-check eslint stylelint " + "tslint webpack webpack-cli rollup parcel tsup vite next nuxt nuxi astro ng react-scripts " + "playwright cypress wdio testcafe turbo lerna sonar-scanner sonarless sonarless-mcp go " + "golangci-lint gotestsum staticcheck cargo mvn mvnw gradle gradlew ant sbt bazel bazelisk " + "buck2 dotnet msbuild pytest py.test tox nox mypy pyright pylint coverage maturin pip pip3 " + "rspec rails phpunit pest phpstan psalm composer mix swift xcodebuild flutter dart pod cmake " + "ninja meson ctest scons docker-compose buildah earthly").split(" "));
|
|
19677
20521
|
var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
|
|
19678
20522
|
var stateDir2 = (env) => {
|
|
19679
20523
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
19680
20524
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
19681
20525
|
if (env.XDG_STATE_HOME)
|
|
19682
|
-
return
|
|
20526
|
+
return path26.join(env.XDG_STATE_HOME, "workit");
|
|
19683
20527
|
const home = env.HOME || os8.homedir();
|
|
19684
20528
|
if (process.platform === "darwin")
|
|
19685
|
-
return
|
|
20529
|
+
return path26.join(home, "Library", "Application Support", "workit");
|
|
19686
20530
|
if (process.platform === "win32")
|
|
19687
|
-
return
|
|
19688
|
-
return
|
|
20531
|
+
return path26.join(env.LOCALAPPDATA ?? path26.join(home, "AppData", "Local"), "workit");
|
|
20532
|
+
return path26.join(home, ".local", "state", "workit");
|
|
19689
20533
|
};
|
|
19690
20534
|
var readJson2 = (file) => {
|
|
19691
20535
|
try {
|
|
@@ -19696,15 +20540,15 @@ var readJson2 = (file) => {
|
|
|
19696
20540
|
};
|
|
19697
20541
|
var patternWords = (cwd, env) => {
|
|
19698
20542
|
const patterns = [];
|
|
19699
|
-
const machine = readJson2(
|
|
20543
|
+
const machine = readJson2(path26.join(stateDir2(env), "heavy-commands.json"));
|
|
19700
20544
|
if (Array.isArray(machine?.patterns))
|
|
19701
20545
|
patterns.push(...machine.patterns);
|
|
19702
|
-
for (let dir =
|
|
19703
|
-
if (fs20.existsSync(
|
|
20546
|
+
for (let dir = path26.resolve(cwd);; dir = path26.dirname(dir)) {
|
|
20547
|
+
if (fs20.existsSync(path26.join(dir, ".git"))) {
|
|
19704
20548
|
patterns.push(...repoHeavyCommands(dir));
|
|
19705
20549
|
break;
|
|
19706
20550
|
}
|
|
19707
|
-
if (
|
|
20551
|
+
if (path26.dirname(dir) === dir)
|
|
19708
20552
|
break;
|
|
19709
20553
|
}
|
|
19710
20554
|
const words = new Set;
|
|
@@ -19774,16 +20618,16 @@ var splitDenial = (plan) => ({
|
|
|
19774
20618
|
unblock: plan.unblock
|
|
19775
20619
|
});
|
|
19776
20620
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
19777
|
-
import { realpathSync as realpathSync5 } from "node:fs";
|
|
20621
|
+
import { existsSync as existsSync8, realpathSync as realpathSync5 } from "node:fs";
|
|
19778
20622
|
import os10 from "node:os";
|
|
19779
|
-
import
|
|
20623
|
+
import path28 from "node:path";
|
|
19780
20624
|
|
|
19781
20625
|
// packages/workit-core/src/core/live-session.ts
|
|
19782
20626
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
19783
20627
|
import { createHash as createHash16 } from "node:crypto";
|
|
19784
20628
|
import fs21 from "node:fs";
|
|
19785
20629
|
import os9 from "node:os";
|
|
19786
|
-
import
|
|
20630
|
+
import path27 from "node:path";
|
|
19787
20631
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
19788
20632
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
19789
20633
|
var liveProbe = {
|
|
@@ -19857,15 +20701,15 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
19857
20701
|
};
|
|
19858
20702
|
};
|
|
19859
20703
|
var checkoutOf = (target) => {
|
|
19860
|
-
let dir =
|
|
19861
|
-
while (!fs21.existsSync(dir) &&
|
|
19862
|
-
dir =
|
|
20704
|
+
let dir = path27.resolve(target);
|
|
20705
|
+
while (!fs21.existsSync(dir) && path27.dirname(dir) !== dir)
|
|
20706
|
+
dir = path27.dirname(dir);
|
|
19863
20707
|
if (fs21.existsSync(dir) && !fs21.statSync(dir).isDirectory())
|
|
19864
|
-
dir =
|
|
19865
|
-
const parts = dir.split(
|
|
20708
|
+
dir = path27.dirname(dir);
|
|
20709
|
+
const parts = dir.split(path27.sep);
|
|
19866
20710
|
const dotGit = parts.lastIndexOf(".git");
|
|
19867
20711
|
if (dotGit > 0)
|
|
19868
|
-
dir = parts.slice(0, dotGit).join(
|
|
20712
|
+
dir = parts.slice(0, dotGit).join(path27.sep) || path27.sep;
|
|
19869
20713
|
const location = resolveStore(dir);
|
|
19870
20714
|
if (location instanceof Error || !location.shared || !location.top)
|
|
19871
20715
|
return null;
|
|
@@ -19875,9 +20719,9 @@ var checkoutOf = (target) => {
|
|
|
19875
20719
|
} catch {}
|
|
19876
20720
|
return top;
|
|
19877
20721
|
};
|
|
19878
|
-
var markerFile2 = (top) =>
|
|
20722
|
+
var markerFile2 = (top) => path27.join(resolveStateDir(), "live-sessions", `${createHash16("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
19879
20723
|
var checkoutKey = (top, platform = process.platform) => {
|
|
19880
|
-
let key =
|
|
20724
|
+
let key = path27.resolve(top);
|
|
19881
20725
|
try {
|
|
19882
20726
|
key = fs21.realpathSync.native(key);
|
|
19883
20727
|
} catch {}
|
|
@@ -19972,7 +20816,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
19972
20816
|
checkout,
|
|
19973
20817
|
heartbeatAt: now.toISOString()
|
|
19974
20818
|
};
|
|
19975
|
-
fs21.mkdirSync(
|
|
20819
|
+
fs21.mkdirSync(path27.dirname(file), { recursive: true });
|
|
19976
20820
|
const temp = `${file}.${process.pid}.tmp`;
|
|
19977
20821
|
fs21.writeFileSync(temp, JSON.stringify(marker));
|
|
19978
20822
|
fs21.renameSync(temp, file);
|
|
@@ -19984,26 +20828,26 @@ var NONE3 = { kind: "none" };
|
|
|
19984
20828
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
19985
20829
|
var canonical2 = (file) => {
|
|
19986
20830
|
let rest = "";
|
|
19987
|
-
for (let dir =
|
|
20831
|
+
for (let dir = path28.resolve(file);; dir = path28.dirname(dir)) {
|
|
19988
20832
|
try {
|
|
19989
|
-
return
|
|
20833
|
+
return path28.join(realpathSync5.native(dir), rest);
|
|
19990
20834
|
} catch {
|
|
19991
|
-
if (
|
|
19992
|
-
return
|
|
19993
|
-
rest = rest ?
|
|
20835
|
+
if (path28.dirname(dir) === dir)
|
|
20836
|
+
return path28.resolve(file);
|
|
20837
|
+
rest = rest ? path28.join(path28.basename(dir), rest) : path28.basename(dir);
|
|
19994
20838
|
}
|
|
19995
20839
|
}
|
|
19996
20840
|
};
|
|
19997
20841
|
var exempt = (cwd, root, file, plans) => {
|
|
19998
20842
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
19999
|
-
const relative =
|
|
20000
|
-
if (relative.startsWith("..") ||
|
|
20843
|
+
const relative = path28.relative(fold(canonical2(root)), fold(canonical2(path28.resolve(cwd, file))));
|
|
20844
|
+
if (relative.startsWith("..") || path28.isAbsolute(relative))
|
|
20001
20845
|
return true;
|
|
20002
|
-
const posix = relative.split(
|
|
20846
|
+
const posix = relative.split(path28.sep).join("/");
|
|
20003
20847
|
const segments = posix.split("/");
|
|
20004
20848
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
20005
20849
|
};
|
|
20006
|
-
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
20850
|
+
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
|
|
20007
20851
|
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
20008
20852
|
var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
20009
20853
|
var wrappedScript = (input) => {
|
|
@@ -20015,9 +20859,9 @@ var wrappedScript = (input) => {
|
|
|
20015
20859
|
return null;
|
|
20016
20860
|
if ("script" in tail)
|
|
20017
20861
|
return tail.script;
|
|
20018
|
-
const
|
|
20019
|
-
if (SCRIPT_SHELLS.has((
|
|
20020
|
-
return script
|
|
20862
|
+
const script = shellScript2(tail.argv);
|
|
20863
|
+
if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
|
|
20864
|
+
return script;
|
|
20021
20865
|
return tail.argv.map(quoteWord2).join(" ");
|
|
20022
20866
|
};
|
|
20023
20867
|
var editsOf = (input) => {
|
|
@@ -20096,6 +20940,94 @@ var editsOf = (input) => {
|
|
|
20096
20940
|
return [];
|
|
20097
20941
|
}
|
|
20098
20942
|
};
|
|
20943
|
+
var shellScript2 = (words) => {
|
|
20944
|
+
if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
|
|
20945
|
+
return null;
|
|
20946
|
+
for (let index = 1;index < words.length; index++) {
|
|
20947
|
+
const word = words[index];
|
|
20948
|
+
if (/^-[a-z]*c$/.test(word))
|
|
20949
|
+
return words[index + 1] ?? null;
|
|
20950
|
+
if (/^[-+][oO]$/.test(word))
|
|
20951
|
+
index++;
|
|
20952
|
+
else if (!/^[-+]/.test(word))
|
|
20953
|
+
return null;
|
|
20954
|
+
}
|
|
20955
|
+
return null;
|
|
20956
|
+
};
|
|
20957
|
+
var XARGS_VALUE = /^-[IinLPdsEa]$/;
|
|
20958
|
+
var runsWriter = (words) => {
|
|
20959
|
+
const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
|
|
20960
|
+
const edits = editsOf(filled);
|
|
20961
|
+
return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
|
|
20962
|
+
};
|
|
20963
|
+
var opaqueWrites = (input) => {
|
|
20964
|
+
let words = input;
|
|
20965
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
|
|
20966
|
+
if (words[0] === "xargs") {
|
|
20967
|
+
let at = 1;
|
|
20968
|
+
while (words[at]?.startsWith("-"))
|
|
20969
|
+
at += XARGS_VALUE.test(words[at]) ? 2 : 1;
|
|
20970
|
+
return runsWriter(words.slice(at)) ? ["."] : null;
|
|
20971
|
+
}
|
|
20972
|
+
words = words.slice(1);
|
|
20973
|
+
}
|
|
20974
|
+
const [verb, ...args] = words;
|
|
20975
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20976
|
+
const files = operands(args);
|
|
20977
|
+
const has = (pattern) => args.some((arg) => pattern.test(arg));
|
|
20978
|
+
const listed = (named) => named.length ? named : ["."];
|
|
20979
|
+
if (name === "unlink" || name === "shred")
|
|
20980
|
+
return listed(files);
|
|
20981
|
+
if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
|
|
20982
|
+
return listed(has(/^--reference/) ? files : files.slice(1));
|
|
20983
|
+
if (name === "rsync") {
|
|
20984
|
+
const dest = files.at(-1);
|
|
20985
|
+
const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
|
|
20986
|
+
return dest === undefined ? ["."] : remote ? [] : [dest];
|
|
20987
|
+
}
|
|
20988
|
+
if (name === "find") {
|
|
20989
|
+
const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
|
|
20990
|
+
const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
|
|
20991
|
+
const end = args.findIndex((arg) => /^[-(!]/.test(arg));
|
|
20992
|
+
return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
|
|
20993
|
+
}
|
|
20994
|
+
if (/^g?awk$/.test(name)) {
|
|
20995
|
+
if (!has(/^-i|^--include/))
|
|
20996
|
+
return null;
|
|
20997
|
+
const named = [];
|
|
20998
|
+
let program = !has(/^-f$|^--file/);
|
|
20999
|
+
for (let index = 0;index < args.length; index++)
|
|
21000
|
+
if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
|
|
21001
|
+
index++;
|
|
21002
|
+
else if (args[index].startsWith("-"))
|
|
21003
|
+
continue;
|
|
21004
|
+
else if (program)
|
|
21005
|
+
program = false;
|
|
21006
|
+
else
|
|
21007
|
+
named.push(args[index]);
|
|
21008
|
+
return listed(named);
|
|
21009
|
+
}
|
|
21010
|
+
if (name === "perl")
|
|
21011
|
+
return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
|
|
21012
|
+
if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
|
|
21013
|
+
return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
|
|
21014
|
+
return shellScript2(words) === null ? null : ["."];
|
|
21015
|
+
};
|
|
21016
|
+
var linkSources = (args, at) => {
|
|
21017
|
+
const names = operands([...args]);
|
|
21018
|
+
const into = args.indexOf("-t");
|
|
21019
|
+
const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
|
|
21020
|
+
const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
|
|
21021
|
+
const target = dest === undefined ? null : at(dest);
|
|
21022
|
+
const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
|
|
21023
|
+
const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync8(target) || /[\\/]$/.test(dest ?? "") ? target : path28.dirname(target);
|
|
21024
|
+
return sources.flatMap((source) => {
|
|
21025
|
+
if (symbolic && base !== null && !path28.isAbsolute(homeExpanded(source)))
|
|
21026
|
+
return [path28.resolve(base, source)];
|
|
21027
|
+
const resolved = at(source);
|
|
21028
|
+
return resolved ? [resolved] : [];
|
|
21029
|
+
});
|
|
21030
|
+
};
|
|
20099
21031
|
var shellWrites = (command, depth = 0) => {
|
|
20100
21032
|
const targets = [];
|
|
20101
21033
|
let unknown = false;
|
|
@@ -20120,7 +21052,7 @@ var shellWrites = (command, depth = 0) => {
|
|
|
20120
21052
|
return { writes: true, targets: unknown ? null : targets };
|
|
20121
21053
|
};
|
|
20122
21054
|
var cache2 = new Map;
|
|
20123
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
21055
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path28.resolve(root, file)) ?? "-").join("\x00");
|
|
20124
21056
|
var gateFor = (cwd) => {
|
|
20125
21057
|
const store = new TaskStore(cwd);
|
|
20126
21058
|
const listed = store.listTaskIndex();
|
|
@@ -20199,32 +21131,36 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
20199
21131
|
return [];
|
|
20200
21132
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
20201
21133
|
return [];
|
|
20202
|
-
return [
|
|
21134
|
+
return [path28.resolve(cwd, invocation.dir ?? ".")];
|
|
20203
21135
|
});
|
|
20204
21136
|
};
|
|
20205
21137
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
20206
21138
|
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
20207
21139
|
var POPD2 = new Set(["popd", "pop-location"]);
|
|
20208
|
-
var
|
|
21140
|
+
var UNKNOWN_DIR = path28.resolve(path28.parse(process.cwd()).root, ".workit-unknown-dir");
|
|
21141
|
+
var cdInto = (dir, words, strict = false) => {
|
|
21142
|
+
const lost = strict ? UNKNOWN_DIR : undefined;
|
|
20209
21143
|
const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
20210
21144
|
const target = named === undefined ? undefined : homeExpanded(named);
|
|
20211
|
-
if (
|
|
20212
|
-
return;
|
|
20213
|
-
|
|
21145
|
+
if (target === "-" || target && /^~|[$`*?]/.test(target))
|
|
21146
|
+
return lost;
|
|
21147
|
+
if (dir === undefined)
|
|
21148
|
+
return lost;
|
|
21149
|
+
return target === undefined ? os10.homedir() : path28.resolve(dir, target);
|
|
20214
21150
|
};
|
|
20215
21151
|
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
20216
21152
|
var homeExpanded = (file) => {
|
|
20217
21153
|
const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
|
|
20218
21154
|
return match ? (process.env.HOME || os10.homedir()) + file.slice(match[0].length) : file;
|
|
20219
21155
|
};
|
|
20220
|
-
var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
21156
|
+
var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
20221
21157
|
const targets = [];
|
|
20222
21158
|
let dir = cwd;
|
|
20223
21159
|
let stack = [];
|
|
20224
21160
|
const groups = [];
|
|
20225
21161
|
const at = (file) => {
|
|
20226
21162
|
const home = homeExpanded(file);
|
|
20227
|
-
return
|
|
21163
|
+
return path28.isAbsolute(home) ? home : dir === undefined ? null : path28.resolve(dir, home);
|
|
20228
21164
|
};
|
|
20229
21165
|
for (const segment of segmentsOf(command, dialect)) {
|
|
20230
21166
|
if (segment.group === "open") {
|
|
@@ -20241,21 +21177,35 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
|
20241
21177
|
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
20242
21178
|
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20243
21179
|
if (CD2.has(name)) {
|
|
20244
|
-
dir = cdInto(dir, words);
|
|
21180
|
+
dir = cdInto(dir, words, strict);
|
|
20245
21181
|
continue;
|
|
20246
21182
|
}
|
|
20247
21183
|
if (PUSHD2.has(name)) {
|
|
20248
21184
|
stack.push(dir);
|
|
20249
|
-
dir = cdInto(dir, words);
|
|
21185
|
+
dir = cdInto(dir, words, strict);
|
|
20250
21186
|
continue;
|
|
20251
21187
|
}
|
|
20252
21188
|
if (POPD2.has(name)) {
|
|
20253
|
-
dir = stack.length ? stack.pop() : undefined;
|
|
21189
|
+
dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
|
|
20254
21190
|
continue;
|
|
20255
21191
|
}
|
|
20256
|
-
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
21192
|
+
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
20257
21193
|
if (inner !== null && dir !== undefined)
|
|
20258
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
21194
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
21195
|
+
if (strict && dir !== undefined) {
|
|
21196
|
+
const runIn = dir;
|
|
21197
|
+
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
21198
|
+
targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
|
|
21199
|
+
const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
|
|
21200
|
+
for (const file of opaque ?? []) {
|
|
21201
|
+
const resolved = at(file);
|
|
21202
|
+
if (resolved)
|
|
21203
|
+
targets.push(file === "." ? path28.join(resolved, DIR_TARGET) : resolved);
|
|
21204
|
+
}
|
|
21205
|
+
const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
|
|
21206
|
+
if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
|
|
21207
|
+
targets.push(...linkSources(words.slice(ln + 1), at));
|
|
21208
|
+
}
|
|
20259
21209
|
for (const file of segment.redirects)
|
|
20260
21210
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
20261
21211
|
const resolved = at(file);
|
|
@@ -20265,18 +21215,36 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
|
20265
21215
|
const edits = editsOf(segment.words);
|
|
20266
21216
|
if (edits === null) {
|
|
20267
21217
|
if (dir !== undefined)
|
|
20268
|
-
targets.push(
|
|
21218
|
+
targets.push(path28.join(dir, DIR_TARGET));
|
|
20269
21219
|
} else
|
|
20270
21220
|
for (const file of edits) {
|
|
20271
21221
|
const resolved = at(file);
|
|
20272
21222
|
if (resolved)
|
|
20273
21223
|
targets.push(resolved);
|
|
20274
21224
|
}
|
|
20275
|
-
if (dir !== undefined
|
|
20276
|
-
|
|
21225
|
+
if (dir !== undefined)
|
|
21226
|
+
for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
|
|
21227
|
+
const { at: runsIn, args } = workitCwd(dir, invocation);
|
|
21228
|
+
if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
|
|
21229
|
+
targets.push(path28.join(runsIn, DIR_TARGET));
|
|
21230
|
+
}
|
|
20277
21231
|
}
|
|
20278
21232
|
return targets;
|
|
20279
21233
|
};
|
|
21234
|
+
var workitCwd = (dir, args) => {
|
|
21235
|
+
const { rest, cwd } = skipWorkitGlobals(args);
|
|
21236
|
+
return { at: cwd === null ? dir : path28.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
21237
|
+
};
|
|
21238
|
+
var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
|
|
21239
|
+
try {
|
|
21240
|
+
return [
|
|
21241
|
+
...segmentTargets(cwd, command, dialect, 0, strict),
|
|
21242
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path28.join(dir, DIR_TARGET))
|
|
21243
|
+
];
|
|
21244
|
+
} catch {
|
|
21245
|
+
return [];
|
|
21246
|
+
}
|
|
21247
|
+
};
|
|
20280
21248
|
var overridden = (command, dialect) => {
|
|
20281
21249
|
const segments = segmentsOf(command, dialect);
|
|
20282
21250
|
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
@@ -20308,7 +21276,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
20308
21276
|
var guarded = (targets) => {
|
|
20309
21277
|
const seen = new Set;
|
|
20310
21278
|
return targets.filter((target) => {
|
|
20311
|
-
const dir =
|
|
21279
|
+
const dir = path28.dirname(target);
|
|
20312
21280
|
if (seen.has(dir))
|
|
20313
21281
|
return false;
|
|
20314
21282
|
seen.add(dir);
|
|
@@ -20329,7 +21297,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
20329
21297
|
for (const target of guarded(targets))
|
|
20330
21298
|
claimLiveSession(target, owner, deps);
|
|
20331
21299
|
};
|
|
20332
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
21300
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path28.resolve(cwd, file)) : [path28.join(cwd, DIR_TARGET)];
|
|
20333
21301
|
var issuedTarget = (cwd, targets) => {
|
|
20334
21302
|
const state = resolveStateDir();
|
|
20335
21303
|
const found = targets.find((target) => underIssued(state, cwd, homeExpanded(target), process.platform, canonical2));
|
|
@@ -20337,7 +21305,7 @@ var issuedTarget = (cwd, targets) => {
|
|
|
20337
21305
|
};
|
|
20338
21306
|
var underIssued = (state, cwd, target, platform = process.platform, real = (file) => file) => {
|
|
20339
21307
|
const win = platform === "win32";
|
|
20340
|
-
const p = win ?
|
|
21308
|
+
const p = win ? path28.win32 : path28.posix;
|
|
20341
21309
|
const fold = (value) => win || platform === "darwin" ? value.toLowerCase() : value;
|
|
20342
21310
|
const named = win ? target.replace(/^\/([a-zA-Z])(?=\/|$)/, "$1:") : target;
|
|
20343
21311
|
const relative = p.relative(fold(real(p.resolve(state))), fold(real(p.resolve(cwd, named))));
|
|
@@ -20382,16 +21350,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
20382
21350
|
} catch {}
|
|
20383
21351
|
const writes = shellWrites(command);
|
|
20384
21352
|
let targets = [];
|
|
20385
|
-
if (owner)
|
|
20386
|
-
|
|
20387
|
-
targets = [
|
|
20388
|
-
...segmentTargets(cwd, command, dialect),
|
|
20389
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
20390
|
-
];
|
|
20391
|
-
} catch {
|
|
20392
|
-
targets = [];
|
|
20393
|
-
}
|
|
20394
|
-
}
|
|
21353
|
+
if (owner)
|
|
21354
|
+
targets = shellMutationTargets(cwd, command, dialect);
|
|
20395
21355
|
if (owner && targets.length) {
|
|
20396
21356
|
const live = liveGate(owner, targets, deps);
|
|
20397
21357
|
if (live.kind !== "none") {
|
|
@@ -20436,6 +21396,24 @@ function beforeWriteGate(cwd, paths) {
|
|
|
20436
21396
|
}
|
|
20437
21397
|
// packages/workit-core/src/hooks/stop.ts
|
|
20438
21398
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
21399
|
+
|
|
21400
|
+
// packages/workit-core/src/core/policy/current-judgment.ts
|
|
21401
|
+
function currentJudgment(cwd) {
|
|
21402
|
+
try {
|
|
21403
|
+
const store = new TaskStore(cwd);
|
|
21404
|
+
const listed = store.listTaskIndex();
|
|
21405
|
+
const key = store.currentKey();
|
|
21406
|
+
if (!listed.ok || !key.ok)
|
|
21407
|
+
return null;
|
|
21408
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
21409
|
+
const task = entry ? store.readTask(entry.id) : null;
|
|
21410
|
+
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
21411
|
+
} catch {
|
|
21412
|
+
return null;
|
|
21413
|
+
}
|
|
21414
|
+
}
|
|
21415
|
+
|
|
21416
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
20439
21417
|
var NONE4 = { kind: "none" };
|
|
20440
21418
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
20441
21419
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -20528,29 +21506,32 @@ var redChecks = (rows, pr, head, endpoint) => {
|
|
|
20528
21506
|
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)" : ""}.`
|
|
20529
21507
|
};
|
|
20530
21508
|
};
|
|
20531
|
-
var
|
|
20532
|
-
|
|
20533
|
-
|
|
20534
|
-
const listed = store.listTaskIndex();
|
|
20535
|
-
const key = store.currentKey();
|
|
20536
|
-
if (!listed.ok || !key.ok)
|
|
20537
|
-
return null;
|
|
20538
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
20539
|
-
const task = entry ? store.readTask(entry.id) : null;
|
|
20540
|
-
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
20541
|
-
} catch {
|
|
20542
|
-
return null;
|
|
20543
|
-
}
|
|
21509
|
+
var prBase = (rows) => {
|
|
21510
|
+
const seen = rows.findLast((row) => (row.type === "pr.status" || row.type === "pr.created") && row.observer === "workit_cli" && typeof row.base === "string");
|
|
21511
|
+
return typeof seen?.base === "string" ? seen.base : null;
|
|
20544
21512
|
};
|
|
20545
|
-
var unverified = (cwd, branch, all, descriptor, verification) => {
|
|
21513
|
+
var unverified = (cwd, branch, all, descriptor, verification, head) => {
|
|
20546
21514
|
const check = checkVerdicts(cwd, branch, all);
|
|
20547
21515
|
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
20548
21516
|
return null;
|
|
20549
|
-
const judgment =
|
|
21517
|
+
const judgment = currentJudgment(cwd);
|
|
20550
21518
|
const high = judgment?.riskTier === "high";
|
|
20551
|
-
|
|
20552
|
-
|
|
20553
|
-
|
|
21519
|
+
let independent;
|
|
21520
|
+
if (verification === "proportional") {
|
|
21521
|
+
const lane = branchLane(cwd, {
|
|
21522
|
+
base: prBase(all.filter((row) => row.branch === branch)),
|
|
21523
|
+
head,
|
|
21524
|
+
judgment
|
|
21525
|
+
});
|
|
21526
|
+
const need = laneNeed(lane.lane);
|
|
21527
|
+
if (need === "none")
|
|
21528
|
+
return null;
|
|
21529
|
+
independent = need === "non-author";
|
|
21530
|
+
} else {
|
|
21531
|
+
if (judgment && !judgment.behaviorChange && !high)
|
|
21532
|
+
return null;
|
|
21533
|
+
independent = verification === "independent" || high;
|
|
21534
|
+
}
|
|
20554
21535
|
if (!independent && check.review === "self-reviewed")
|
|
20555
21536
|
return null;
|
|
20556
21537
|
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
@@ -20597,7 +21578,7 @@ function stopObligation(input, descriptor) {
|
|
|
20597
21578
|
}
|
|
20598
21579
|
if (!delivered || endpoint !== "green" && endpoint !== "merged")
|
|
20599
21580
|
return null;
|
|
20600
|
-
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification);
|
|
21581
|
+
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification, head);
|
|
20601
21582
|
return left ? { ...left, head } : null;
|
|
20602
21583
|
} catch {
|
|
20603
21584
|
return null;
|
|
@@ -20858,13 +21839,13 @@ ${line}` };
|
|
|
20858
21839
|
return decision;
|
|
20859
21840
|
};
|
|
20860
21841
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20861
|
-
import { existsSync as
|
|
20862
|
-
import
|
|
21842
|
+
import { existsSync as existsSync9, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
21843
|
+
import path29 from "node:path";
|
|
20863
21844
|
var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20864
21845
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
20865
21846
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
20866
21847
|
var existingDirectory = (value) => {
|
|
20867
|
-
if (!nonEmpty2(value) || !
|
|
21848
|
+
if (!nonEmpty2(value) || !path29.isAbsolute(value) || !existsSync9(value))
|
|
20868
21849
|
return null;
|
|
20869
21850
|
try {
|
|
20870
21851
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -20932,9 +21913,9 @@ var writeTargets = (input) => {
|
|
|
20932
21913
|
};
|
|
20933
21914
|
// packages/workit-core/src/hooks/standing.ts
|
|
20934
21915
|
import fs22 from "node:fs";
|
|
20935
|
-
import
|
|
21916
|
+
import path30 from "node:path";
|
|
20936
21917
|
var readPlans = (store) => {
|
|
20937
|
-
const dir =
|
|
21918
|
+
const dir = path30.join(store, "fanouts");
|
|
20938
21919
|
let names;
|
|
20939
21920
|
try {
|
|
20940
21921
|
names = fs22.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
@@ -20944,7 +21925,7 @@ var readPlans = (store) => {
|
|
|
20944
21925
|
const plans = [];
|
|
20945
21926
|
for (const name of names) {
|
|
20946
21927
|
try {
|
|
20947
|
-
const raw = JSON.parse(fs22.readFileSync(
|
|
21928
|
+
const raw = JSON.parse(fs22.readFileSync(path30.join(dir, name), "utf8"));
|
|
20948
21929
|
if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
|
|
20949
21930
|
continue;
|
|
20950
21931
|
const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
@@ -21067,9 +22048,266 @@ var cwdRewrite = (command) => {
|
|
|
21067
22048
|
return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
|
|
21068
22049
|
};
|
|
21069
22050
|
|
|
22051
|
+
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
22052
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
22053
|
+
import fs23 from "node:fs";
|
|
22054
|
+
import path31 from "node:path";
|
|
22055
|
+
var FANOUT_TOKEN_FILE = "workit-fanout-token";
|
|
22056
|
+
var NONE5 = { kind: "none" };
|
|
22057
|
+
var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
|
|
22058
|
+
var canonical3 = (file) => {
|
|
22059
|
+
let rest = "";
|
|
22060
|
+
for (let dir = path31.resolve(file);; dir = path31.dirname(dir)) {
|
|
22061
|
+
try {
|
|
22062
|
+
return path31.join(fs23.realpathSync.native(dir), rest);
|
|
22063
|
+
} catch {
|
|
22064
|
+
if (path31.dirname(dir) === dir)
|
|
22065
|
+
return path31.resolve(file);
|
|
22066
|
+
rest = rest ? path31.join(path31.basename(dir), rest) : path31.basename(dir);
|
|
22067
|
+
}
|
|
22068
|
+
}
|
|
22069
|
+
};
|
|
22070
|
+
var within = (child, parent) => {
|
|
22071
|
+
const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
|
|
22072
|
+
const relative = path31.relative(fold(parent), fold(child));
|
|
22073
|
+
return relative === "" || !relative.startsWith("..") && !path31.isAbsolute(relative);
|
|
22074
|
+
};
|
|
22075
|
+
var checkoutOf2 = (file) => {
|
|
22076
|
+
for (let dir = canonical3(file);; dir = path31.dirname(dir)) {
|
|
22077
|
+
const dotGit = path31.join(dir, ".git");
|
|
22078
|
+
if (fs23.existsSync(dotGit)) {
|
|
22079
|
+
if (!fs23.statSync(dotGit).isFile())
|
|
22080
|
+
return { root: dir, workit: false };
|
|
22081
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
22082
|
+
return {
|
|
22083
|
+
root: dir,
|
|
22084
|
+
workit: Boolean(admin) && fs23.existsSync(path31.join(path31.resolve(dir, admin), FANOUT_TOKEN_FILE))
|
|
22085
|
+
};
|
|
22086
|
+
}
|
|
22087
|
+
if (path31.dirname(dir) === dir)
|
|
22088
|
+
return null;
|
|
22089
|
+
}
|
|
22090
|
+
};
|
|
22091
|
+
var workitWorktreeOf = (file) => {
|
|
22092
|
+
try {
|
|
22093
|
+
const checkout = checkoutOf2(file);
|
|
22094
|
+
return checkout?.workit ? checkout.root : null;
|
|
22095
|
+
} catch {
|
|
22096
|
+
return null;
|
|
22097
|
+
}
|
|
22098
|
+
};
|
|
22099
|
+
var bindingDir = () => path31.join(resolveStateDir(), "worktree-bind");
|
|
22100
|
+
var bindingFile = (session, agentId) => path31.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
|
|
22101
|
+
var readBinding = (file) => {
|
|
22102
|
+
try {
|
|
22103
|
+
const value = JSON.parse(fs23.readFileSync(file, "utf8"));
|
|
22104
|
+
return typeof value.worktree === "string" ? value : null;
|
|
22105
|
+
} catch {
|
|
22106
|
+
return null;
|
|
22107
|
+
}
|
|
22108
|
+
};
|
|
22109
|
+
var boundWorktree = (input) => {
|
|
22110
|
+
const agentId = input.session.agentId;
|
|
22111
|
+
if (!agentId)
|
|
22112
|
+
return null;
|
|
22113
|
+
const binding = readBinding(bindingFile(input.session.id, agentId));
|
|
22114
|
+
return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
|
|
22115
|
+
};
|
|
22116
|
+
var bind = (input, worktree) => {
|
|
22117
|
+
const agentId = input.session.agentId;
|
|
22118
|
+
const file = bindingFile(input.session.id, agentId);
|
|
22119
|
+
const binding = {
|
|
22120
|
+
worktree,
|
|
22121
|
+
session: input.session.id,
|
|
22122
|
+
agentId,
|
|
22123
|
+
at: new Date().toISOString()
|
|
22124
|
+
};
|
|
22125
|
+
fs23.mkdirSync(path31.dirname(file), { recursive: true });
|
|
22126
|
+
fs23.writeFileSync(file, JSON.stringify(binding));
|
|
22127
|
+
try {
|
|
22128
|
+
if (inWorkitWorkspace(input.cwd))
|
|
22129
|
+
appendHookObserved(input.cwd, {
|
|
22130
|
+
type: "worktree.bound",
|
|
22131
|
+
actor: { host: input.host, session: input.session.id, agentId },
|
|
22132
|
+
agentType: input.session.agentType,
|
|
22133
|
+
worktree
|
|
22134
|
+
});
|
|
22135
|
+
} catch {}
|
|
22136
|
+
};
|
|
22137
|
+
var hostIsolated = (cwd, agentId) => {
|
|
22138
|
+
try {
|
|
22139
|
+
if (agentId === null)
|
|
22140
|
+
return false;
|
|
22141
|
+
const checkout = checkoutOf2(cwd);
|
|
22142
|
+
if (!checkout)
|
|
22143
|
+
return false;
|
|
22144
|
+
const dotGit = path31.join(checkout.root, ".git");
|
|
22145
|
+
if (!fs23.statSync(dotGit).isFile())
|
|
22146
|
+
return false;
|
|
22147
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
22148
|
+
if (!admin)
|
|
22149
|
+
return false;
|
|
22150
|
+
const adminDir = path31.resolve(checkout.root, admin);
|
|
22151
|
+
const back = fs23.readFileSync(path31.join(adminDir, "gitdir"), "utf8").trim();
|
|
22152
|
+
if (path31.relative(canonical3(path31.resolve(adminDir, back)), canonical3(dotGit)) !== "")
|
|
22153
|
+
return false;
|
|
22154
|
+
const common = path31.join(adminDir, "commondir");
|
|
22155
|
+
const gitDir = fs23.existsSync(common) ? path31.resolve(adminDir, fs23.readFileSync(common, "utf8").trim()) : adminDir;
|
|
22156
|
+
const expected = path31.join(path31.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
|
|
22157
|
+
return path31.relative(canonical3(expected), checkout.root) === "";
|
|
22158
|
+
} catch {
|
|
22159
|
+
return false;
|
|
22160
|
+
}
|
|
22161
|
+
};
|
|
22162
|
+
var shown = (target) => path31.basename(target) === ".workit-live-target" ? path31.dirname(target) : target;
|
|
22163
|
+
var unboundDeny = (input, target) => {
|
|
22164
|
+
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.";
|
|
22165
|
+
return {
|
|
22166
|
+
kind: "deny",
|
|
22167
|
+
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}`,
|
|
22168
|
+
unblock
|
|
22169
|
+
};
|
|
22170
|
+
};
|
|
22171
|
+
var outsideDeny = (input, worktree, target) => {
|
|
22172
|
+
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.`;
|
|
22173
|
+
return {
|
|
22174
|
+
kind: "deny",
|
|
22175
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
22176
|
+
unblock
|
|
22177
|
+
};
|
|
22178
|
+
};
|
|
22179
|
+
var unknownDirDeny = (input, worktree) => {
|
|
22180
|
+
const wt = worktree ?? "<worktree>";
|
|
22181
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
22182
|
+
return {
|
|
22183
|
+
kind: "deny",
|
|
22184
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
22185
|
+
unblock
|
|
22186
|
+
};
|
|
22187
|
+
};
|
|
22188
|
+
function confineImplementer(input, targets) {
|
|
22189
|
+
try {
|
|
22190
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
22191
|
+
return NONE5;
|
|
22192
|
+
if (targets.some((target) => within(path31.resolve(target), UNKNOWN_DIR)))
|
|
22193
|
+
return unknownDirDeny(input, boundWorktree(input));
|
|
22194
|
+
const bindings = canonical3(bindingDir());
|
|
22195
|
+
const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
|
|
22196
|
+
if (judged.length === 0)
|
|
22197
|
+
return NONE5;
|
|
22198
|
+
let worktree = boundWorktree(input);
|
|
22199
|
+
if (!worktree) {
|
|
22200
|
+
for (const { real } of judged) {
|
|
22201
|
+
worktree = within(real, bindings) ? null : workitWorktreeOf(real);
|
|
22202
|
+
if (worktree)
|
|
22203
|
+
break;
|
|
22204
|
+
}
|
|
22205
|
+
if (!worktree)
|
|
22206
|
+
return unboundDeny(input, judged[0].named);
|
|
22207
|
+
bind(input, worktree);
|
|
22208
|
+
}
|
|
22209
|
+
const root = canonical3(worktree);
|
|
22210
|
+
const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
|
|
22211
|
+
return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
|
|
22212
|
+
} catch {
|
|
22213
|
+
return NONE5;
|
|
22214
|
+
}
|
|
22215
|
+
}
|
|
22216
|
+
var MUTATING_REMOTE = new Set([
|
|
22217
|
+
"add",
|
|
22218
|
+
"remove",
|
|
22219
|
+
"rm",
|
|
22220
|
+
"rename",
|
|
22221
|
+
"set-url",
|
|
22222
|
+
"set-head",
|
|
22223
|
+
"set-branches",
|
|
22224
|
+
"prune"
|
|
22225
|
+
]);
|
|
22226
|
+
var CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
|
|
22227
|
+
var CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
|
|
22228
|
+
var CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
|
|
22229
|
+
var leadOwnedGit = (args) => {
|
|
22230
|
+
const [sub = "", ...rest] = args;
|
|
22231
|
+
if (rest.some((arg) => arg === "--help" || arg === "-h"))
|
|
22232
|
+
return false;
|
|
22233
|
+
const names = rest.filter((arg) => !arg.startsWith("-"));
|
|
22234
|
+
const flag = (pattern) => rest.some((arg) => pattern.test(arg));
|
|
22235
|
+
switch (sub) {
|
|
22236
|
+
case "worktree":
|
|
22237
|
+
return names[0] !== undefined && names[0] !== "list";
|
|
22238
|
+
case "update-ref":
|
|
22239
|
+
return true;
|
|
22240
|
+
case "symbolic-ref":
|
|
22241
|
+
return names.length > 1 || flag(/^(?:-d|--delete)$/);
|
|
22242
|
+
case "branch":
|
|
22243
|
+
return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
|
|
22244
|
+
case "tag":
|
|
22245
|
+
return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
|
|
22246
|
+
case "push": {
|
|
22247
|
+
if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
|
|
22248
|
+
return true;
|
|
22249
|
+
const refspecs = names.slice(1);
|
|
22250
|
+
return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
|
|
22251
|
+
}
|
|
22252
|
+
case "replace":
|
|
22253
|
+
case "filter-branch":
|
|
22254
|
+
case "gc":
|
|
22255
|
+
case "prune":
|
|
22256
|
+
case "repack":
|
|
22257
|
+
case "sparse-checkout":
|
|
22258
|
+
return true;
|
|
22259
|
+
case "notes":
|
|
22260
|
+
case "reflog":
|
|
22261
|
+
case "submodule":
|
|
22262
|
+
return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
|
|
22263
|
+
case "stash":
|
|
22264
|
+
return names[0] === "drop" || names[0] === "clear";
|
|
22265
|
+
case "remote":
|
|
22266
|
+
return MUTATING_REMOTE.has(names[0] ?? "");
|
|
22267
|
+
case "config": {
|
|
22268
|
+
if (rest.some((arg) => CONFIG_READ.test(arg)))
|
|
22269
|
+
return false;
|
|
22270
|
+
if (rest.some((arg) => CONFIG_WRITE.test(arg)))
|
|
22271
|
+
return true;
|
|
22272
|
+
let operands = 0;
|
|
22273
|
+
for (let index = 0;index < rest.length; index++)
|
|
22274
|
+
if (CONFIG_VALUE.test(rest[index]))
|
|
22275
|
+
index++;
|
|
22276
|
+
else if (!rest[index].startsWith("-"))
|
|
22277
|
+
operands++;
|
|
22278
|
+
return operands > 1;
|
|
22279
|
+
}
|
|
22280
|
+
default:
|
|
22281
|
+
return false;
|
|
22282
|
+
}
|
|
22283
|
+
};
|
|
22284
|
+
var leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
|
|
22285
|
+
if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
|
|
22286
|
+
return false;
|
|
22287
|
+
const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
|
|
22288
|
+
return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
|
|
22289
|
+
});
|
|
22290
|
+
var leadOwnedDeny = (input, command) => {
|
|
22291
|
+
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.";
|
|
22292
|
+
return {
|
|
22293
|
+
kind: "deny",
|
|
22294
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
|
|
22295
|
+
unblock
|
|
22296
|
+
};
|
|
22297
|
+
};
|
|
22298
|
+
function confineImplementerShell(input, command, dialect = "posix") {
|
|
22299
|
+
try {
|
|
22300
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
22301
|
+
return NONE5;
|
|
22302
|
+
if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
|
|
22303
|
+
return leadOwnedDeny(input, command);
|
|
22304
|
+
} catch {}
|
|
22305
|
+
return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
|
|
22306
|
+
}
|
|
22307
|
+
|
|
21070
22308
|
// packages/workit-core/src/hooks/handle.ts
|
|
21071
22309
|
var usable2 = (value) => value === "native" || value === "partial";
|
|
21072
|
-
var
|
|
22310
|
+
var NONE6 = { kind: "none" };
|
|
21073
22311
|
var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
|
|
21074
22312
|
var offered = new Set;
|
|
21075
22313
|
var ROLES = {
|
|
@@ -21082,6 +22320,7 @@ var workitRole = (descriptor, agentType) => {
|
|
|
21082
22320
|
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
21083
22321
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
21084
22322
|
};
|
|
22323
|
+
var implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer";
|
|
21085
22324
|
var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
|
|
21086
22325
|
var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
21087
22326
|
var 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\`).`;
|
|
@@ -21089,7 +22328,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
21089
22328
|
const session = input.session.id ?? null;
|
|
21090
22329
|
const role = workitRole(descriptor, event.agentType);
|
|
21091
22330
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
21092
|
-
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer"
|
|
22331
|
+
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
|
|
21093
22332
|
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
21094
22333
|
if (role === "implementer" && isolated)
|
|
21095
22334
|
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.`;
|
|
@@ -21097,7 +22336,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
21097
22336
|
return `${isolatedGit(input.cwd)}
|
|
21098
22337
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
21099
22338
|
if (role === "implementer")
|
|
21100
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes
|
|
22339
|
+
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.`;
|
|
21101
22340
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
21102
22341
|
if (role !== "judge" || !event.agentId)
|
|
21103
22342
|
return readOnly;
|
|
@@ -21128,7 +22367,7 @@ function decide(input, deps) {
|
|
|
21128
22367
|
switch (event.kind) {
|
|
21129
22368
|
case "session.start": {
|
|
21130
22369
|
if (!usable2(descriptor.context.sessionStart))
|
|
21131
|
-
return
|
|
22370
|
+
return NONE6;
|
|
21132
22371
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
21133
22372
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
21134
22373
|
if (offer && key !== null)
|
|
@@ -21143,26 +22382,31 @@ function decide(input, deps) {
|
|
|
21143
22382
|
}
|
|
21144
22383
|
case "context.turn": {
|
|
21145
22384
|
if (!usable2(descriptor.context.perTurn))
|
|
21146
|
-
return
|
|
22385
|
+
return NONE6;
|
|
21147
22386
|
const current = turnContextText(input, descriptor);
|
|
21148
22387
|
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
21149
|
-
return withContextLine(text ? { kind: "context", text } :
|
|
22388
|
+
return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
|
|
21150
22389
|
}
|
|
21151
22390
|
case "shell.pre": {
|
|
21152
22391
|
const skillRead = skillReadIn(event.command, event.dialect);
|
|
21153
22392
|
if (skillRead)
|
|
21154
22393
|
recordSkillLoad(input, skillRead, "read");
|
|
21155
22394
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
21156
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) :
|
|
22395
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
|
|
21157
22396
|
if (policy.kind !== "none")
|
|
21158
22397
|
return policy;
|
|
21159
22398
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
21160
22399
|
if (!postTool)
|
|
21161
22400
|
settlePendingCommit(input);
|
|
21162
|
-
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) :
|
|
22401
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
|
|
21163
22402
|
if (raw.kind === "deny")
|
|
21164
22403
|
return raw;
|
|
21165
|
-
|
|
22404
|
+
if (canDeny && implementerAgent(input, descriptor)) {
|
|
22405
|
+
const confined = confineImplementerShell(input, event.command, event.dialect);
|
|
22406
|
+
if (confined.kind === "deny")
|
|
22407
|
+
return confined;
|
|
22408
|
+
}
|
|
22409
|
+
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
|
|
21166
22410
|
if (gate.kind === "deny")
|
|
21167
22411
|
return gate;
|
|
21168
22412
|
const plan = heavyGate(input.cwd, event.command, {
|
|
@@ -21185,10 +22429,10 @@ function decide(input, deps) {
|
|
|
21185
22429
|
command: rewrittenTo,
|
|
21186
22430
|
input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
|
|
21187
22431
|
text
|
|
21188
|
-
} :
|
|
22432
|
+
} : NONE6;
|
|
21189
22433
|
if (descriptor.transport !== "hook-process")
|
|
21190
22434
|
return rewritten(null);
|
|
21191
|
-
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) :
|
|
22435
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
|
|
21192
22436
|
if (!lease)
|
|
21193
22437
|
return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
|
|
21194
22438
|
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
@@ -21197,9 +22441,17 @@ function decide(input, deps) {
|
|
|
21197
22441
|
case "shell.post":
|
|
21198
22442
|
if (usable2(descriptor.events["shell.post"].support))
|
|
21199
22443
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
21200
|
-
return
|
|
21201
|
-
case "write.pre":
|
|
21202
|
-
|
|
22444
|
+
return NONE6;
|
|
22445
|
+
case "write.pre": {
|
|
22446
|
+
if (!usable2(descriptor.events["write.pre"].support))
|
|
22447
|
+
return NONE6;
|
|
22448
|
+
if (implementerAgent(input, descriptor)) {
|
|
22449
|
+
const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
|
|
22450
|
+
if (confined.kind === "deny")
|
|
22451
|
+
return confined;
|
|
22452
|
+
}
|
|
22453
|
+
return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
|
|
22454
|
+
}
|
|
21203
22455
|
case "subagent.start":
|
|
21204
22456
|
if (workitRole(descriptor, event.agentType) === "judge")
|
|
21205
22457
|
trackJudge(input, event.agentId, true);
|
|
@@ -21216,22 +22468,22 @@ function decide(input, deps) {
|
|
|
21216
22468
|
case "tool.pre":
|
|
21217
22469
|
if (event.skill)
|
|
21218
22470
|
recordSkillLoad(input, event.skill, "tool");
|
|
21219
|
-
return
|
|
22471
|
+
return NONE6;
|
|
21220
22472
|
case "stop":
|
|
21221
|
-
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) :
|
|
22473
|
+
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
|
|
21222
22474
|
case "subagent.stop":
|
|
21223
22475
|
trackJudge(input, event.agentId, false);
|
|
21224
|
-
return
|
|
22476
|
+
return NONE6;
|
|
21225
22477
|
case "prompt.submit":
|
|
21226
|
-
return
|
|
22478
|
+
return NONE6;
|
|
21227
22479
|
}
|
|
21228
22480
|
}
|
|
21229
22481
|
var failureDecision = (descriptor, event, error) => {
|
|
21230
22482
|
if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
|
|
21231
|
-
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } :
|
|
22483
|
+
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE6;
|
|
21232
22484
|
if (event === "session.start" || event === "subagent.start")
|
|
21233
22485
|
return { kind: "context", text: `[workit diagnostic: ${error}]` };
|
|
21234
|
-
return
|
|
22486
|
+
return NONE6;
|
|
21235
22487
|
};
|
|
21236
22488
|
var dispatchHook = (adapter, raw, env = process.env) => {
|
|
21237
22489
|
const parsed = adapter.parse(raw, env);
|
|
@@ -21257,7 +22509,7 @@ var dispatchHook = (adapter, raw, env = process.env) => {
|
|
|
21257
22509
|
};
|
|
21258
22510
|
// packages/workit-core/src/hooks/deadline.ts
|
|
21259
22511
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
21260
|
-
import
|
|
22512
|
+
import path32 from "node:path";
|
|
21261
22513
|
var HOOK_SKIP_TEXT = "workit context skipped: host under load";
|
|
21262
22514
|
var HOOK_DEADLINE_MS = 8000;
|
|
21263
22515
|
var HOST_DEADLINES = {
|
|
@@ -21275,7 +22527,7 @@ var hookDeadlineMs = (env, host = "", native) => {
|
|
|
21275
22527
|
const row = HOST_DEADLINES[host];
|
|
21276
22528
|
return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
|
|
21277
22529
|
};
|
|
21278
|
-
var skipsFile = (stateDir) =>
|
|
22530
|
+
var skipsFile = (stateDir) => path32.join(stateDir, HOOK_SKIPS_FILE);
|
|
21279
22531
|
var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
|
|
21280
22532
|
try {
|
|
21281
22533
|
mkdirSync3(stateDir, { recursive: true });
|
|
@@ -21293,7 +22545,7 @@ var QUIET = new Set([
|
|
|
21293
22545
|
]);
|
|
21294
22546
|
var skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT };
|
|
21295
22547
|
var TIMED_OUT = Symbol("timed out");
|
|
21296
|
-
var
|
|
22548
|
+
var within2 = async (work, ms) => {
|
|
21297
22549
|
let timer;
|
|
21298
22550
|
const expired = new Promise((resolve) => {
|
|
21299
22551
|
timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
|
|
@@ -21324,7 +22576,7 @@ var readWithin = async (stdin, budget) => {
|
|
|
21324
22576
|
text += String(chunk);
|
|
21325
22577
|
return text;
|
|
21326
22578
|
})();
|
|
21327
|
-
const text = await
|
|
22579
|
+
const text = await within2(read, budget.startedAt + budget.deadlineMs - budget.now());
|
|
21328
22580
|
return isTimedOut(text) ? null : text;
|
|
21329
22581
|
};
|
|
21330
22582
|
var note = (adapter, raw, env, budget, outcome, stage) => {
|
|
@@ -21353,7 +22605,7 @@ var dispatchWithin = async (adapter, raw, env, budget, dispatch = dispatchHook)
|
|
|
21353
22605
|
if (budget.host !== null && typeof native === "string")
|
|
21354
22606
|
budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
|
|
21355
22607
|
const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
|
|
21356
|
-
const result = remaining() > 0 ? await
|
|
22608
|
+
const result = remaining() > 0 ? await within2(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
|
|
21357
22609
|
if (isTimedOut(result))
|
|
21358
22610
|
return skippedHook(adapter, raw, env, budget, "dispatch");
|
|
21359
22611
|
if (remaining() < 0)
|
|
@@ -21919,8 +23171,8 @@ var codexAdapter = {
|
|
|
21919
23171
|
};
|
|
21920
23172
|
|
|
21921
23173
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
21922
|
-
import
|
|
21923
|
-
import
|
|
23174
|
+
import fs24 from "node:fs";
|
|
23175
|
+
import path33 from "node:path";
|
|
21924
23176
|
var undocumented = { support: "undocumented", native: null };
|
|
21925
23177
|
var none = { support: "none", native: null };
|
|
21926
23178
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -22117,17 +23369,17 @@ var protocolEvent2 = (input) => {
|
|
|
22117
23369
|
};
|
|
22118
23370
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
22119
23371
|
var lastAssistantText = (file) => {
|
|
22120
|
-
if (!file || !
|
|
23372
|
+
if (!file || !path33.isAbsolute(file))
|
|
22121
23373
|
return null;
|
|
22122
23374
|
try {
|
|
22123
|
-
const size =
|
|
23375
|
+
const size = fs24.statSync(file).size;
|
|
22124
23376
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
22125
23377
|
const buffer = Buffer.alloc(size - start);
|
|
22126
|
-
const fd =
|
|
23378
|
+
const fd = fs24.openSync(file, "r");
|
|
22127
23379
|
try {
|
|
22128
|
-
|
|
23380
|
+
fs24.readSync(fd, buffer, 0, buffer.length, start);
|
|
22129
23381
|
} finally {
|
|
22130
|
-
|
|
23382
|
+
fs24.closeSync(fd);
|
|
22131
23383
|
}
|
|
22132
23384
|
const lines = buffer.toString("utf8").split(`
|
|
22133
23385
|
`);
|
|
@@ -22154,7 +23406,7 @@ var lastAssistantText = (file) => {
|
|
|
22154
23406
|
return null;
|
|
22155
23407
|
}
|
|
22156
23408
|
};
|
|
22157
|
-
var
|
|
23409
|
+
var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path33.sep) ? root : `${root}${path33.sep}`);
|
|
22158
23410
|
var hookCwd = (input) => {
|
|
22159
23411
|
const cwd = existingDirectory(input.cwd);
|
|
22160
23412
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
@@ -22163,7 +23415,7 @@ var hookCwd = (input) => {
|
|
|
22163
23415
|
};
|
|
22164
23416
|
var cursorWorkspaceRoot = (input) => {
|
|
22165
23417
|
const cwd = existingDirectory(input.cwd);
|
|
22166
|
-
return cwd && input.workspace_roots.find((root) =>
|
|
23418
|
+
return cwd && input.workspace_roots.find((root) => within3(root, cwd)) || input.workspace_roots[0];
|
|
22167
23419
|
};
|
|
22168
23420
|
var cursorDeny = (reason) => ({
|
|
22169
23421
|
permission: "deny",
|