@brainervirus/workit-cursor 8.23.0 → 8.25.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 +1521 -400
- package/dist/mcp-server.js +1122 -140
- package/dist/workit-hook.js +1516 -400
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +1 -1
- package/skills/workit-fanout/references/brief.md +4 -4
- package/skills/workit-implement/SKILL.md +5 -5
- package/skills/workit-review/SKILL.md +5 -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,144 @@ function resolveRef(cwd, ref) {
|
|
|
6903
6903
|
return null;
|
|
6904
6904
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
6905
6905
|
}
|
|
6906
|
+
function originHeadRef(cwd) {
|
|
6907
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
6908
|
+
}
|
|
6909
|
+
function ownChangeDrift(cwd, range) {
|
|
6910
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
6911
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
6912
|
+
if (!before || !after)
|
|
6913
|
+
return null;
|
|
6914
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
6915
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
6916
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
6917
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
6918
|
+
}
|
|
6919
|
+
function sameOwnChange(cwd, range) {
|
|
6920
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
6921
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
6922
|
+
if (!fromFork || !toFork)
|
|
6923
|
+
return false;
|
|
6924
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
6925
|
+
}
|
|
6926
|
+
var LANE_DIFF = [
|
|
6927
|
+
"-c",
|
|
6928
|
+
"core.quotePath=false",
|
|
6929
|
+
"diff",
|
|
6930
|
+
"--no-color",
|
|
6931
|
+
"--no-ext-diff",
|
|
6932
|
+
"--no-textconv",
|
|
6933
|
+
"--no-renames"
|
|
6934
|
+
];
|
|
6935
|
+
var laneRange = (fork, head) => {
|
|
6936
|
+
if (!safeArg(fork) || head !== null && !safeArg(head))
|
|
6937
|
+
return null;
|
|
6938
|
+
return head === null ? [fork] : [fork, head];
|
|
6939
|
+
};
|
|
6940
|
+
function diffNumstat(cwd, fork, head) {
|
|
6941
|
+
const range = laneRange(fork, head);
|
|
6942
|
+
if (!range)
|
|
6943
|
+
return null;
|
|
6944
|
+
const run = git(cwd, [...LANE_DIFF, "--numstat", "-z", ...range, "--"]);
|
|
6945
|
+
if (!run.ok)
|
|
6946
|
+
return null;
|
|
6947
|
+
const out = [];
|
|
6948
|
+
for (const record of run.stdout.split("\x00")) {
|
|
6949
|
+
const match = /^(-|\d+)\t(-|\d+)\t(.+)$/su.exec(record.replace(/^\n/u, ""));
|
|
6950
|
+
if (!match)
|
|
6951
|
+
continue;
|
|
6952
|
+
const binary = match[1] === "-";
|
|
6953
|
+
out.push({
|
|
6954
|
+
path: match[3],
|
|
6955
|
+
added: binary ? 0 : Number(match[1]),
|
|
6956
|
+
deleted: binary ? 0 : Number(match[2]),
|
|
6957
|
+
binary
|
|
6958
|
+
});
|
|
6959
|
+
}
|
|
6960
|
+
return out;
|
|
6961
|
+
}
|
|
6962
|
+
function diffRaw(cwd, fork, head) {
|
|
6963
|
+
const range = laneRange(fork, head);
|
|
6964
|
+
if (!range)
|
|
6965
|
+
return null;
|
|
6966
|
+
const run = git(cwd, [...LANE_DIFF, "--raw", "-z", "--no-abbrev", ...range, "--"]);
|
|
6967
|
+
if (!run.ok)
|
|
6968
|
+
return null;
|
|
6969
|
+
const fields = run.stdout.split("\x00");
|
|
6970
|
+
const out = [];
|
|
6971
|
+
for (let index = 0;index + 1 < fields.length; index += 2) {
|
|
6972
|
+
const meta = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) [A-Z]\d*$/u.exec(fields[index].replace(/^\n/u, ""));
|
|
6973
|
+
if (!meta)
|
|
6974
|
+
return null;
|
|
6975
|
+
out.push({
|
|
6976
|
+
path: fields[index + 1],
|
|
6977
|
+
oldMode: meta[1],
|
|
6978
|
+
newMode: meta[2],
|
|
6979
|
+
oldBlob: meta[3],
|
|
6980
|
+
newBlob: meta[4]
|
|
6981
|
+
});
|
|
6982
|
+
}
|
|
6983
|
+
return out;
|
|
6984
|
+
}
|
|
6985
|
+
function readBlobs(cwd, ids) {
|
|
6986
|
+
const out = new Map;
|
|
6987
|
+
const wanted = [...new Set(ids)].filter((id) => /^[0-9a-f]{40,64}$/u.test(id));
|
|
6988
|
+
if (!wanted.length)
|
|
6989
|
+
return out;
|
|
6990
|
+
const run = spawnSync("git", ["cat-file", "--batch"], {
|
|
6991
|
+
cwd,
|
|
6992
|
+
input: `${wanted.join(`
|
|
6993
|
+
`)}
|
|
6994
|
+
`,
|
|
6995
|
+
maxBuffer: MAX_BUFFER,
|
|
6996
|
+
timeout: GIT_TIMEOUTS.local,
|
|
6997
|
+
killSignal: "SIGKILL",
|
|
6998
|
+
windowsHide: true
|
|
6999
|
+
});
|
|
7000
|
+
if (run.status !== 0 || !run.stdout)
|
|
7001
|
+
return null;
|
|
7002
|
+
const bytes = run.stdout;
|
|
7003
|
+
let at = 0;
|
|
7004
|
+
for (const id of wanted) {
|
|
7005
|
+
const end = bytes.indexOf(10, at);
|
|
7006
|
+
if (end < 0)
|
|
7007
|
+
return null;
|
|
7008
|
+
const header = bytes.subarray(at, end).toString("utf8").split(" ");
|
|
7009
|
+
at = end + 1;
|
|
7010
|
+
if (header[1] === "missing")
|
|
7011
|
+
continue;
|
|
7012
|
+
const size = Number(header[2]);
|
|
7013
|
+
if (header[0] !== id || !Number.isSafeInteger(size))
|
|
7014
|
+
return null;
|
|
7015
|
+
out.set(id, bytes.subarray(at, at + size));
|
|
7016
|
+
at += size + 1;
|
|
7017
|
+
}
|
|
7018
|
+
return out;
|
|
7019
|
+
}
|
|
7020
|
+
function treeFilesNamed(cwd, rev, name) {
|
|
7021
|
+
if (!safeArg(rev))
|
|
7022
|
+
return null;
|
|
7023
|
+
const run = git(cwd, ["ls-tree", "-r", "-z", "--full-tree", rev]);
|
|
7024
|
+
if (!run.ok)
|
|
7025
|
+
return null;
|
|
7026
|
+
const out = new Map;
|
|
7027
|
+
for (const record of run.stdout.split("\x00")) {
|
|
7028
|
+
const match = /^\d{6} blob ([0-9a-f]+)\t(.+)$/su.exec(record);
|
|
7029
|
+
if (match && (match[2] === name || match[2].endsWith(`/${name}`)))
|
|
7030
|
+
out.set(match[2], match[1]);
|
|
7031
|
+
}
|
|
7032
|
+
return out;
|
|
7033
|
+
}
|
|
7034
|
+
function fileAt(cwd, rev, file) {
|
|
7035
|
+
if (!safeArg(rev))
|
|
7036
|
+
return null;
|
|
7037
|
+
const run = git(cwd, ["cat-file", "blob", `${rev}:${file}`]);
|
|
7038
|
+
return run.ok ? run.stdout : null;
|
|
7039
|
+
}
|
|
7040
|
+
function untrackedFiles(cwd) {
|
|
7041
|
+
const run = git(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]);
|
|
7042
|
+
return run.ok ? run.stdout.split("\x00").filter(Boolean) : [];
|
|
7043
|
+
}
|
|
6906
7044
|
var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
|
|
6907
7045
|
function currentBranch(cwd) {
|
|
6908
7046
|
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
@@ -6927,7 +7065,7 @@ function pushRemoteName(cwd, branch) {
|
|
|
6927
7065
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
6928
7066
|
import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
|
|
6929
7067
|
import fs7 from "node:fs";
|
|
6930
|
-
import
|
|
7068
|
+
import path10 from "node:path";
|
|
6931
7069
|
|
|
6932
7070
|
// packages/workit-core/src/core/store-lock.ts
|
|
6933
7071
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -7353,6 +7491,10 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
7353
7491
|
}
|
|
7354
7492
|
};
|
|
7355
7493
|
|
|
7494
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
7495
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
7496
|
+
import path6 from "node:path";
|
|
7497
|
+
|
|
7356
7498
|
// packages/workit-core/src/core/vcs-config.ts
|
|
7357
7499
|
import fs4 from "node:fs";
|
|
7358
7500
|
import path5 from "node:path";
|
|
@@ -7716,7 +7858,7 @@ var workspaceConfigSchema = object({
|
|
|
7716
7858
|
}).passthrough().optional(),
|
|
7717
7859
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
7718
7860
|
defaultEndpoint: unknown().optional(),
|
|
7719
|
-
verification: _enum(["self", "independent"]).optional(),
|
|
7861
|
+
verification: _enum(["proportional", "self", "independent"]).optional(),
|
|
7720
7862
|
delegation: unknown().optional(),
|
|
7721
7863
|
trailers: boolean2().optional(),
|
|
7722
7864
|
liveSessionGuard: boolean2().optional(),
|
|
@@ -8672,10 +8814,747 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
8672
8814
|
return out;
|
|
8673
8815
|
}
|
|
8674
8816
|
|
|
8817
|
+
// packages/workit-core/src/git/default-base.ts
|
|
8818
|
+
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
8819
|
+
var trackTrunk = (cwd) => {
|
|
8820
|
+
try {
|
|
8821
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
8822
|
+
if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
|
|
8823
|
+
return null;
|
|
8824
|
+
const target = String(resolved.defaultTargetBranch ?? "").trim();
|
|
8825
|
+
return target && safeRef(target) ? target : null;
|
|
8826
|
+
} catch {
|
|
8827
|
+
return null;
|
|
8828
|
+
}
|
|
8829
|
+
};
|
|
8830
|
+
function defaultBase(cwd) {
|
|
8831
|
+
const trunk = trackTrunk(cwd);
|
|
8832
|
+
if (trunk) {
|
|
8833
|
+
for (const candidate of [`origin/${trunk}`, trunk])
|
|
8834
|
+
if (resolveRef(cwd, candidate))
|
|
8835
|
+
return candidate;
|
|
8836
|
+
}
|
|
8837
|
+
const originHead = originHeadRef(cwd);
|
|
8838
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
8839
|
+
return originHead;
|
|
8840
|
+
for (const candidate of [
|
|
8841
|
+
"origin/main",
|
|
8842
|
+
"origin/master",
|
|
8843
|
+
"main",
|
|
8844
|
+
"master",
|
|
8845
|
+
"origin/develop",
|
|
8846
|
+
"develop"
|
|
8847
|
+
])
|
|
8848
|
+
if (resolveRef(cwd, candidate))
|
|
8849
|
+
return candidate;
|
|
8850
|
+
return null;
|
|
8851
|
+
}
|
|
8852
|
+
|
|
8853
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
8854
|
+
function patternRegex(dir, pattern) {
|
|
8855
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
8856
|
+
return null;
|
|
8857
|
+
const anchored = pattern.includes("/");
|
|
8858
|
+
const body = pattern.replace(/^\//u, "");
|
|
8859
|
+
let source = "";
|
|
8860
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
8861
|
+
const char = body[index];
|
|
8862
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
8863
|
+
const slash = body[index + 2] === "/";
|
|
8864
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
8865
|
+
index += slash ? 2 : 1;
|
|
8866
|
+
} else if (char === "*")
|
|
8867
|
+
source += "[^/]*";
|
|
8868
|
+
else if (char === "?")
|
|
8869
|
+
source += "[^/]";
|
|
8870
|
+
else if (char === "[") {
|
|
8871
|
+
const end = body.indexOf("]", index + 2);
|
|
8872
|
+
if (end < 0)
|
|
8873
|
+
return null;
|
|
8874
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
8875
|
+
source += `[${set}]`;
|
|
8876
|
+
index = end;
|
|
8877
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
8878
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
8879
|
+
index += 1;
|
|
8880
|
+
} else
|
|
8881
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
8882
|
+
}
|
|
8883
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
8884
|
+
try {
|
|
8885
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
8886
|
+
} catch {
|
|
8887
|
+
return null;
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
function rulesOf(dir, text) {
|
|
8891
|
+
const out = [];
|
|
8892
|
+
for (const raw of text.split(`
|
|
8893
|
+
`)) {
|
|
8894
|
+
const line = raw.trim();
|
|
8895
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
8896
|
+
continue;
|
|
8897
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
8898
|
+
let generated = null;
|
|
8899
|
+
for (const attribute of attributes) {
|
|
8900
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
8901
|
+
generated = true;
|
|
8902
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
8903
|
+
generated = false;
|
|
8904
|
+
}
|
|
8905
|
+
if (generated === null)
|
|
8906
|
+
continue;
|
|
8907
|
+
const regex = patternRegex(dir, pattern);
|
|
8908
|
+
if (regex)
|
|
8909
|
+
out.push({ regex, generated });
|
|
8910
|
+
}
|
|
8911
|
+
return out;
|
|
8912
|
+
}
|
|
8913
|
+
function generatedByAttributes(files, paths) {
|
|
8914
|
+
const rules = [...files].map(([file, text]) => {
|
|
8915
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
8916
|
+
return {
|
|
8917
|
+
depth: dir ? dir.split("/").length : 0,
|
|
8918
|
+
rules: rulesOf(dir, text)
|
|
8919
|
+
};
|
|
8920
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
8921
|
+
const out = new Set;
|
|
8922
|
+
for (const file of paths) {
|
|
8923
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
8924
|
+
if (last?.generated)
|
|
8925
|
+
out.add(file);
|
|
8926
|
+
}
|
|
8927
|
+
return out;
|
|
8928
|
+
}
|
|
8929
|
+
|
|
8930
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
8931
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
8932
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
8933
|
+
var SYNTAXES = Object.fromEntries([
|
|
8934
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
8935
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
8936
|
+
ext,
|
|
8937
|
+
{ ...C_LIKE, triple: true }
|
|
8938
|
+
]),
|
|
8939
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
8940
|
+
...["css", "scss", "less"].map((ext) => [
|
|
8941
|
+
ext,
|
|
8942
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
8943
|
+
]),
|
|
8944
|
+
...["py", "pyi"].map((ext) => [
|
|
8945
|
+
ext,
|
|
8946
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
8947
|
+
]),
|
|
8948
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
8949
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
8950
|
+
ext,
|
|
8951
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
8952
|
+
])
|
|
8953
|
+
]);
|
|
8954
|
+
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;
|
|
8955
|
+
var extensionOf = (file) => {
|
|
8956
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
8957
|
+
const dot = name.lastIndexOf(".");
|
|
8958
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
8959
|
+
};
|
|
8960
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
8961
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
8962
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
8963
|
+
var WORD = /[\w$]/u;
|
|
8964
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
8965
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
8966
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
8967
|
+
var REGEX_KEYWORDS = new Set([
|
|
8968
|
+
"return",
|
|
8969
|
+
"typeof",
|
|
8970
|
+
"instanceof",
|
|
8971
|
+
"case",
|
|
8972
|
+
"do",
|
|
8973
|
+
"else",
|
|
8974
|
+
"in",
|
|
8975
|
+
"of",
|
|
8976
|
+
"new",
|
|
8977
|
+
"delete",
|
|
8978
|
+
"void",
|
|
8979
|
+
"throw",
|
|
8980
|
+
"yield",
|
|
8981
|
+
"await"
|
|
8982
|
+
]);
|
|
8983
|
+
function substanceOf(file, text) {
|
|
8984
|
+
const syntax = syntaxOf(file);
|
|
8985
|
+
if (!syntax)
|
|
8986
|
+
return plainSubstance(text);
|
|
8987
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
8988
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
8989
|
+
return plainSubstance(text);
|
|
8990
|
+
return lex(syntax, text);
|
|
8991
|
+
}
|
|
8992
|
+
function plainSubstance(text) {
|
|
8993
|
+
return text.split(`
|
|
8994
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
8995
|
+
`);
|
|
8996
|
+
}
|
|
8997
|
+
function lex(syntax, text) {
|
|
8998
|
+
const collapse = !syntax.indented;
|
|
8999
|
+
let out = "";
|
|
9000
|
+
let lineStart = 0;
|
|
9001
|
+
let kept = 0;
|
|
9002
|
+
let pendingSpace = false;
|
|
9003
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
9004
|
+
let index = 0;
|
|
9005
|
+
if (text.startsWith("#!")) {
|
|
9006
|
+
const end = text.indexOf(`
|
|
9007
|
+
`);
|
|
9008
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
9009
|
+
kept = out.length;
|
|
9010
|
+
index = end < 0 ? text.length : end;
|
|
9011
|
+
}
|
|
9012
|
+
const emitCode = (chunk) => {
|
|
9013
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
9014
|
+
const before = out[out.length - 1];
|
|
9015
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
9016
|
+
out += " ";
|
|
9017
|
+
}
|
|
9018
|
+
pendingSpace = false;
|
|
9019
|
+
out += chunk;
|
|
9020
|
+
};
|
|
9021
|
+
const emitVerbatim = (chunk) => {
|
|
9022
|
+
emitCode(chunk);
|
|
9023
|
+
kept = out.length;
|
|
9024
|
+
};
|
|
9025
|
+
const newline = () => {
|
|
9026
|
+
if (!collapse) {
|
|
9027
|
+
let end = out.length;
|
|
9028
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
9029
|
+
end -= 1;
|
|
9030
|
+
out = out.slice(0, end);
|
|
9031
|
+
}
|
|
9032
|
+
pendingSpace = false;
|
|
9033
|
+
if (out.length > lineStart) {
|
|
9034
|
+
out += `
|
|
9035
|
+
`;
|
|
9036
|
+
lineStart = out.length;
|
|
9037
|
+
}
|
|
9038
|
+
};
|
|
9039
|
+
const regexAllowed = () => {
|
|
9040
|
+
let end = out.length;
|
|
9041
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
9042
|
+
end -= 1;
|
|
9043
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
9044
|
+
return true;
|
|
9045
|
+
let start = end;
|
|
9046
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
9047
|
+
start -= 1;
|
|
9048
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
9049
|
+
};
|
|
9050
|
+
const scanString = (quote, escapes, multiline) => {
|
|
9051
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
9052
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
9053
|
+
let end = index + quote.length;
|
|
9054
|
+
while (end < text.length) {
|
|
9055
|
+
const char = text[end];
|
|
9056
|
+
if (escapes && char === "\\") {
|
|
9057
|
+
end += 2;
|
|
9058
|
+
continue;
|
|
9059
|
+
}
|
|
9060
|
+
if (text.startsWith(quote, end)) {
|
|
9061
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
9062
|
+
return false;
|
|
9063
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
9064
|
+
index = end + quote.length;
|
|
9065
|
+
return true;
|
|
9066
|
+
}
|
|
9067
|
+
if (char === `
|
|
9068
|
+
` && !multiline)
|
|
9069
|
+
return false;
|
|
9070
|
+
end += 1;
|
|
9071
|
+
}
|
|
9072
|
+
return false;
|
|
9073
|
+
};
|
|
9074
|
+
while (index < text.length) {
|
|
9075
|
+
const frame = frames[frames.length - 1];
|
|
9076
|
+
const char = text[index];
|
|
9077
|
+
if (frame.kind === "template") {
|
|
9078
|
+
if (char === "\\") {
|
|
9079
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
9080
|
+
index += 2;
|
|
9081
|
+
} else if (char === "`") {
|
|
9082
|
+
emitVerbatim(char);
|
|
9083
|
+
frames.pop();
|
|
9084
|
+
index += 1;
|
|
9085
|
+
} else if (text.startsWith("${", index)) {
|
|
9086
|
+
emitVerbatim("${");
|
|
9087
|
+
frames.push({ kind: "code", braces: 0 });
|
|
9088
|
+
index += 2;
|
|
9089
|
+
} else {
|
|
9090
|
+
out += char;
|
|
9091
|
+
kept = out.length;
|
|
9092
|
+
index += 1;
|
|
9093
|
+
}
|
|
9094
|
+
continue;
|
|
9095
|
+
}
|
|
9096
|
+
if (char === `
|
|
9097
|
+
`) {
|
|
9098
|
+
newline();
|
|
9099
|
+
index += 1;
|
|
9100
|
+
continue;
|
|
9101
|
+
}
|
|
9102
|
+
if (SPACE.test(char)) {
|
|
9103
|
+
if (collapse)
|
|
9104
|
+
pendingSpace = true;
|
|
9105
|
+
else
|
|
9106
|
+
out += char;
|
|
9107
|
+
index += 1;
|
|
9108
|
+
continue;
|
|
9109
|
+
}
|
|
9110
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
9111
|
+
if (lineOpener) {
|
|
9112
|
+
const end = text.indexOf(`
|
|
9113
|
+
`, index);
|
|
9114
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
9115
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
9116
|
+
emitVerbatim(comment);
|
|
9117
|
+
else
|
|
9118
|
+
pendingSpace = true;
|
|
9119
|
+
index = end < 0 ? text.length : end;
|
|
9120
|
+
continue;
|
|
9121
|
+
}
|
|
9122
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
9123
|
+
if (block) {
|
|
9124
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
9125
|
+
if (end < 0)
|
|
9126
|
+
return null;
|
|
9127
|
+
const comment = text.slice(index, end + block[1].length);
|
|
9128
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
9129
|
+
emitVerbatim(comment);
|
|
9130
|
+
else
|
|
9131
|
+
pendingSpace = true;
|
|
9132
|
+
if (comment.includes(`
|
|
9133
|
+
`))
|
|
9134
|
+
newline();
|
|
9135
|
+
index = end + block[1].length;
|
|
9136
|
+
continue;
|
|
9137
|
+
}
|
|
9138
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
9139
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
9140
|
+
return null;
|
|
9141
|
+
continue;
|
|
9142
|
+
}
|
|
9143
|
+
if (syntax.quotes.includes(char)) {
|
|
9144
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
9145
|
+
return null;
|
|
9146
|
+
continue;
|
|
9147
|
+
}
|
|
9148
|
+
if (syntax.literal?.includes(char)) {
|
|
9149
|
+
if (!scanString(char, false, false))
|
|
9150
|
+
return null;
|
|
9151
|
+
continue;
|
|
9152
|
+
}
|
|
9153
|
+
if (syntax.rawBacktick && char === "`") {
|
|
9154
|
+
if (!scanString("`", false, true))
|
|
9155
|
+
return null;
|
|
9156
|
+
continue;
|
|
9157
|
+
}
|
|
9158
|
+
if (syntax.js && char === "`") {
|
|
9159
|
+
emitVerbatim(char);
|
|
9160
|
+
frames.push({ kind: "template" });
|
|
9161
|
+
index += 1;
|
|
9162
|
+
continue;
|
|
9163
|
+
}
|
|
9164
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
9165
|
+
return null;
|
|
9166
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
9167
|
+
const lineEnd = text.indexOf(`
|
|
9168
|
+
`, index);
|
|
9169
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
9170
|
+
return null;
|
|
9171
|
+
}
|
|
9172
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
9173
|
+
let end = index + 1;
|
|
9174
|
+
let inClass = false;
|
|
9175
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
9176
|
+
if (text[end] === `
|
|
9177
|
+
`)
|
|
9178
|
+
return null;
|
|
9179
|
+
if (text[end] === "\\")
|
|
9180
|
+
end += 1;
|
|
9181
|
+
else if (text[end] === "[")
|
|
9182
|
+
inClass = true;
|
|
9183
|
+
else if (text[end] === "]")
|
|
9184
|
+
inClass = false;
|
|
9185
|
+
end += 1;
|
|
9186
|
+
}
|
|
9187
|
+
if (end >= text.length)
|
|
9188
|
+
return null;
|
|
9189
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
9190
|
+
index = end + 1;
|
|
9191
|
+
continue;
|
|
9192
|
+
}
|
|
9193
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
9194
|
+
if (char === "{")
|
|
9195
|
+
frame.braces += 1;
|
|
9196
|
+
else if (char === "}") {
|
|
9197
|
+
if (frame.braces === 0) {
|
|
9198
|
+
emitVerbatim(char);
|
|
9199
|
+
frames.pop();
|
|
9200
|
+
index += 1;
|
|
9201
|
+
continue;
|
|
9202
|
+
}
|
|
9203
|
+
frame.braces -= 1;
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
emitCode(char);
|
|
9207
|
+
index += 1;
|
|
9208
|
+
}
|
|
9209
|
+
if (frames.length !== 1)
|
|
9210
|
+
return null;
|
|
9211
|
+
newline();
|
|
9212
|
+
return out;
|
|
9213
|
+
}
|
|
9214
|
+
function unbalanced(body) {
|
|
9215
|
+
let depth = 0;
|
|
9216
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
9217
|
+
if (part === "{")
|
|
9218
|
+
depth += 1;
|
|
9219
|
+
else if (part === "}")
|
|
9220
|
+
depth = Math.max(0, depth - 1);
|
|
9221
|
+
}
|
|
9222
|
+
return depth > 0;
|
|
9223
|
+
}
|
|
9224
|
+
function hasOpaqueLineBreak(text) {
|
|
9225
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
9226
|
+
}
|
|
9227
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
9228
|
+
function textOf(bytes) {
|
|
9229
|
+
if (bytes.includes(0))
|
|
9230
|
+
return null;
|
|
9231
|
+
try {
|
|
9232
|
+
return decoder.decode(bytes);
|
|
9233
|
+
} catch {
|
|
9234
|
+
return null;
|
|
9235
|
+
}
|
|
9236
|
+
}
|
|
9237
|
+
function cosmeticChange(file, before, after) {
|
|
9238
|
+
const left = textOf(before);
|
|
9239
|
+
const right = textOf(after);
|
|
9240
|
+
if (left === null || right === null)
|
|
9241
|
+
return false;
|
|
9242
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
9243
|
+
return false;
|
|
9244
|
+
const a = substanceOf(file, left);
|
|
9245
|
+
return a !== null && a === substanceOf(file, right);
|
|
9246
|
+
}
|
|
9247
|
+
|
|
9248
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
9249
|
+
var LANE_THRESHOLDS = {
|
|
9250
|
+
selfMaxLines: 100,
|
|
9251
|
+
selfMaxFiles: 5,
|
|
9252
|
+
fullMinLines: 500
|
|
9253
|
+
};
|
|
9254
|
+
var RISK_TOKENS = new Set([
|
|
9255
|
+
"auth",
|
|
9256
|
+
"authn",
|
|
9257
|
+
"authz",
|
|
9258
|
+
"oauth",
|
|
9259
|
+
"security",
|
|
9260
|
+
"permission",
|
|
9261
|
+
"permissions",
|
|
9262
|
+
"secret",
|
|
9263
|
+
"secrets",
|
|
9264
|
+
"crypto",
|
|
9265
|
+
"gate",
|
|
9266
|
+
"gates",
|
|
9267
|
+
"policy",
|
|
9268
|
+
"policies",
|
|
9269
|
+
"hook",
|
|
9270
|
+
"hooks",
|
|
9271
|
+
"husky",
|
|
9272
|
+
"lefthook",
|
|
9273
|
+
"release",
|
|
9274
|
+
"releases",
|
|
9275
|
+
"migration",
|
|
9276
|
+
"migrations",
|
|
9277
|
+
"migrate",
|
|
9278
|
+
"workflows"
|
|
9279
|
+
]);
|
|
9280
|
+
var RISK_FILES = new Set([
|
|
9281
|
+
".gitlab-ci.yml",
|
|
9282
|
+
"Jenkinsfile",
|
|
9283
|
+
"azure-pipelines.yml",
|
|
9284
|
+
".pre-commit-config.yaml",
|
|
9285
|
+
".workit/risk-paths"
|
|
9286
|
+
]);
|
|
9287
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
9288
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
9289
|
+
var LOCKFILES = new Set([
|
|
9290
|
+
"bun.lock",
|
|
9291
|
+
"bun.lockb",
|
|
9292
|
+
"package-lock.json",
|
|
9293
|
+
"npm-shrinkwrap.json",
|
|
9294
|
+
"yarn.lock",
|
|
9295
|
+
"pnpm-lock.yaml",
|
|
9296
|
+
"Cargo.lock",
|
|
9297
|
+
"go.sum",
|
|
9298
|
+
"poetry.lock",
|
|
9299
|
+
"uv.lock",
|
|
9300
|
+
"Pipfile.lock",
|
|
9301
|
+
"Gemfile.lock",
|
|
9302
|
+
"composer.lock"
|
|
9303
|
+
]);
|
|
9304
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
9305
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
9306
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
9307
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
9308
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
9309
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
9310
|
+
function kindOf(file) {
|
|
9311
|
+
if (file.binary)
|
|
9312
|
+
return "behavior";
|
|
9313
|
+
if (file.generated)
|
|
9314
|
+
return "generated";
|
|
9315
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
9316
|
+
return "lockfile";
|
|
9317
|
+
if (isDoc(file.path))
|
|
9318
|
+
return "doc";
|
|
9319
|
+
if (file.cosmetic)
|
|
9320
|
+
return "cosmetic";
|
|
9321
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
9322
|
+
}
|
|
9323
|
+
var globCache = new Map;
|
|
9324
|
+
function globMatch(pattern, file) {
|
|
9325
|
+
let regex = globCache.get(pattern);
|
|
9326
|
+
if (!regex) {
|
|
9327
|
+
let source = "";
|
|
9328
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
9329
|
+
const char = pattern[index];
|
|
9330
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
9331
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
9332
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
9333
|
+
} else if (char === "*")
|
|
9334
|
+
source += "[^/]*";
|
|
9335
|
+
else if (char === "?")
|
|
9336
|
+
source += "[^/]";
|
|
9337
|
+
else
|
|
9338
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
9339
|
+
}
|
|
9340
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
9341
|
+
globCache.set(pattern, regex);
|
|
9342
|
+
}
|
|
9343
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
9344
|
+
}
|
|
9345
|
+
function isRiskPath(file, extra = []) {
|
|
9346
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
9347
|
+
return true;
|
|
9348
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
9349
|
+
return true;
|
|
9350
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
9351
|
+
return false;
|
|
9352
|
+
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)));
|
|
9353
|
+
}
|
|
9354
|
+
var parseRiskPaths = (text) => text.split(`
|
|
9355
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
9356
|
+
function computeLane(input) {
|
|
9357
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
9358
|
+
const risky = new Set(riskFiles);
|
|
9359
|
+
const kinds = input.files.map((file) => ({
|
|
9360
|
+
file,
|
|
9361
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
9362
|
+
}));
|
|
9363
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
9364
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
9365
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
9366
|
+
const result = (lane, reasons) => ({
|
|
9367
|
+
lane,
|
|
9368
|
+
reasons,
|
|
9369
|
+
lines,
|
|
9370
|
+
files: behavior.length,
|
|
9371
|
+
riskFiles
|
|
9372
|
+
});
|
|
9373
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
9374
|
+
if (input.riskTier === "high")
|
|
9375
|
+
return result("full", ["the task judgment records high risk"]);
|
|
9376
|
+
if (riskFiles.length)
|
|
9377
|
+
return result("full", [
|
|
9378
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
9379
|
+
]);
|
|
9380
|
+
if (!behavior.length)
|
|
9381
|
+
return result("none", [
|
|
9382
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
9383
|
+
]);
|
|
9384
|
+
if (lines >= fullMinLines)
|
|
9385
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
9386
|
+
if (lines > selfMaxLines)
|
|
9387
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
9388
|
+
if (behavior.length > selfMaxFiles)
|
|
9389
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
9390
|
+
if (opaque.length)
|
|
9391
|
+
return result("focused", [
|
|
9392
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
9393
|
+
]);
|
|
9394
|
+
return result("self", [
|
|
9395
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
9396
|
+
]);
|
|
9397
|
+
}
|
|
9398
|
+
function untrackedStat(cwd, file) {
|
|
9399
|
+
try {
|
|
9400
|
+
const bytes = readFileSync3(path6.join(cwd, file));
|
|
9401
|
+
if (bytes.includes(0))
|
|
9402
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9403
|
+
const text = bytes.toString("utf8");
|
|
9404
|
+
if (hasOpaqueLineBreak(text))
|
|
9405
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9406
|
+
const lines = text.split(`
|
|
9407
|
+
`).length - (text.endsWith(`
|
|
9408
|
+
`) ? 1 : 0);
|
|
9409
|
+
return { path: file, added: lines, deleted: 0 };
|
|
9410
|
+
} catch {
|
|
9411
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9412
|
+
}
|
|
9413
|
+
}
|
|
9414
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
9415
|
+
var ABSENT = /^0+$/u;
|
|
9416
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
9417
|
+
const raw = diffRaw(cwd, fork, head);
|
|
9418
|
+
if (!raw)
|
|
9419
|
+
return null;
|
|
9420
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
9421
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
9422
|
+
if (!blobs)
|
|
9423
|
+
return null;
|
|
9424
|
+
const out = new Set;
|
|
9425
|
+
const opaque = new Set;
|
|
9426
|
+
for (const entry of candidates) {
|
|
9427
|
+
const before = blobs.get(entry.oldBlob);
|
|
9428
|
+
let after = blobs.get(entry.newBlob);
|
|
9429
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
9430
|
+
try {
|
|
9431
|
+
after = readFileSync3(path6.join(cwd, entry.path));
|
|
9432
|
+
} catch {
|
|
9433
|
+
after = undefined;
|
|
9434
|
+
}
|
|
9435
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
9436
|
+
out.add(entry.path);
|
|
9437
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
9438
|
+
opaque.add(entry.path);
|
|
9439
|
+
}
|
|
9440
|
+
return { cosmetic: out, opaque };
|
|
9441
|
+
}
|
|
9442
|
+
function generatedAt(cwd, fork, paths) {
|
|
9443
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
9444
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
9445
|
+
if (!files || !blobs)
|
|
9446
|
+
return new Set;
|
|
9447
|
+
const texts = new Map;
|
|
9448
|
+
for (const [file, id] of files) {
|
|
9449
|
+
const bytes = blobs.get(id);
|
|
9450
|
+
if (bytes)
|
|
9451
|
+
texts.set(file, bytes.toString("utf8"));
|
|
9452
|
+
}
|
|
9453
|
+
return generatedByAttributes(texts, paths);
|
|
9454
|
+
}
|
|
9455
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
9456
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
9457
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
9458
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
9459
|
+
if (!stats || !cosmetic)
|
|
9460
|
+
return null;
|
|
9461
|
+
const files = [
|
|
9462
|
+
...stats.map((entry) => ({
|
|
9463
|
+
...entry,
|
|
9464
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
9465
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
9466
|
+
})),
|
|
9467
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
9468
|
+
];
|
|
9469
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
9470
|
+
return files.map((entry) => ({
|
|
9471
|
+
...entry,
|
|
9472
|
+
generated: generated.has(entry.path)
|
|
9473
|
+
}));
|
|
9474
|
+
}
|
|
9475
|
+
function driftFiles(cwd, range) {
|
|
9476
|
+
if (range.fromFork === range.toFork)
|
|
9477
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
9478
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
9479
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
9480
|
+
if (!drifted || !stats)
|
|
9481
|
+
return null;
|
|
9482
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
9483
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
9484
|
+
return drifted.map((file) => ({
|
|
9485
|
+
path: file,
|
|
9486
|
+
added: byPath.get(file)?.added ?? 0,
|
|
9487
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
9488
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
9489
|
+
cosmetic: false,
|
|
9490
|
+
generated: generated.has(file)
|
|
9491
|
+
}));
|
|
9492
|
+
}
|
|
9493
|
+
function branchLane(cwd, options) {
|
|
9494
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
9495
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
9496
|
+
const head = options.workingTree ? null : options.head;
|
|
9497
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
9498
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
9499
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
9500
|
+
if (!fork || !files) {
|
|
9501
|
+
const high = riskTier === "high";
|
|
9502
|
+
return {
|
|
9503
|
+
lane: high ? "full" : "focused",
|
|
9504
|
+
reasons: [
|
|
9505
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
9506
|
+
...high ? ["the task judgment records high risk"] : []
|
|
9507
|
+
],
|
|
9508
|
+
lines: 0,
|
|
9509
|
+
files: 0,
|
|
9510
|
+
riskFiles: [],
|
|
9511
|
+
base,
|
|
9512
|
+
head: options.head
|
|
9513
|
+
};
|
|
9514
|
+
}
|
|
9515
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
9516
|
+
return {
|
|
9517
|
+
...computeLane({
|
|
9518
|
+
files,
|
|
9519
|
+
riskTier,
|
|
9520
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
9521
|
+
}),
|
|
9522
|
+
base,
|
|
9523
|
+
head: options.head
|
|
9524
|
+
};
|
|
9525
|
+
}
|
|
9526
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
9527
|
+
|
|
9528
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
9529
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
9530
|
+
function verdictDrift(cwd, input) {
|
|
9531
|
+
const since = input.from;
|
|
9532
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
9533
|
+
carries: false,
|
|
9534
|
+
since,
|
|
9535
|
+
files,
|
|
9536
|
+
focused,
|
|
9537
|
+
reason
|
|
9538
|
+
});
|
|
9539
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
9540
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
9541
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
9542
|
+
if (!fromFork || !toFork || !files)
|
|
9543
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
9544
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
9545
|
+
const lane = computeLane({ files, riskPaths });
|
|
9546
|
+
const paths = files.map((file) => file.path);
|
|
9547
|
+
if (lane.lane === "none")
|
|
9548
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
9549
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
9550
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
9551
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
9552
|
+
}
|
|
9553
|
+
|
|
8675
9554
|
// packages/workit-core/src/host-session.ts
|
|
8676
9555
|
import { createHash as createHash7, randomBytes, timingSafeEqual } from "node:crypto";
|
|
8677
9556
|
import fs5 from "node:fs";
|
|
8678
|
-
import
|
|
9557
|
+
import path8 from "node:path";
|
|
8679
9558
|
|
|
8680
9559
|
// packages/workit-core/src/core/branch.ts
|
|
8681
9560
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -8896,7 +9775,7 @@ var isProtectedTarget = (root, name) => {
|
|
|
8896
9775
|
|
|
8897
9776
|
// packages/workit-core/src/core/logger.ts
|
|
8898
9777
|
import os3 from "node:os";
|
|
8899
|
-
import
|
|
9778
|
+
import path7 from "node:path";
|
|
8900
9779
|
var SENSITIVE_WORDS = new Set([
|
|
8901
9780
|
"token",
|
|
8902
9781
|
"secret",
|
|
@@ -8941,14 +9820,14 @@ var resolveStateDir = () => {
|
|
|
8941
9820
|
return override;
|
|
8942
9821
|
const home = os3.homedir();
|
|
8943
9822
|
if (process.env.XDG_STATE_HOME)
|
|
8944
|
-
return
|
|
9823
|
+
return path7.join(process.env.XDG_STATE_HOME, "workit");
|
|
8945
9824
|
switch (os3.platform()) {
|
|
8946
9825
|
case "darwin":
|
|
8947
|
-
return
|
|
9826
|
+
return path7.join(home, "Library", "Application Support", "workit");
|
|
8948
9827
|
case "win32":
|
|
8949
|
-
return
|
|
9828
|
+
return path7.join(process.env.LOCALAPPDATA ?? path7.join(home, "AppData", "Local"), "workit");
|
|
8950
9829
|
default:
|
|
8951
|
-
return
|
|
9830
|
+
return path7.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
8952
9831
|
}
|
|
8953
9832
|
};
|
|
8954
9833
|
var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
@@ -8984,8 +9863,8 @@ function hostSessionFromEnv(env) {
|
|
|
8984
9863
|
var ISSUED_PER_LEAD = 64;
|
|
8985
9864
|
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
|
|
8986
9865
|
var hash = (value) => createHash7("sha256").update(value).digest("hex").slice(0, 32);
|
|
8987
|
-
var userIssuedRoot = () =>
|
|
8988
|
-
var userTokenRoot = () =>
|
|
9866
|
+
var userIssuedRoot = () => path8.join(resolveStateDir(), "issued");
|
|
9867
|
+
var userTokenRoot = () => path8.join(resolveStateDir(), "issued-tokens");
|
|
8989
9868
|
function migrateRepoIssued(cwd) {
|
|
8990
9869
|
if (cwd === undefined)
|
|
8991
9870
|
return;
|
|
@@ -8994,7 +9873,7 @@ function migrateRepoIssued(cwd) {
|
|
|
8994
9873
|
const location = resolveStore(cwd);
|
|
8995
9874
|
if (location instanceof Error)
|
|
8996
9875
|
return;
|
|
8997
|
-
old =
|
|
9876
|
+
old = path8.join(location.dir, "hooks", "issued");
|
|
8998
9877
|
if (!fs5.existsSync(old))
|
|
8999
9878
|
return;
|
|
9000
9879
|
} catch {
|
|
@@ -9002,19 +9881,19 @@ function migrateRepoIssued(cwd) {
|
|
|
9002
9881
|
}
|
|
9003
9882
|
for (const lead of safeList(old)) {
|
|
9004
9883
|
try {
|
|
9005
|
-
const from =
|
|
9884
|
+
const from = path8.join(old, lead);
|
|
9006
9885
|
if (!fs5.statSync(from).isDirectory())
|
|
9007
9886
|
continue;
|
|
9008
|
-
const to =
|
|
9887
|
+
const to = path8.join(userIssuedRoot(), lead);
|
|
9009
9888
|
fs5.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9010
9889
|
for (const id of fs5.readdirSync(from)) {
|
|
9011
9890
|
try {
|
|
9012
|
-
fs5.copyFileSync(
|
|
9891
|
+
fs5.copyFileSync(path8.join(from, id), path8.join(to, id), fs5.constants.COPYFILE_EXCL);
|
|
9013
9892
|
} catch (error) {
|
|
9014
9893
|
if (error.code !== "EEXIST")
|
|
9015
9894
|
throw error;
|
|
9016
9895
|
}
|
|
9017
|
-
fs5.rmSync(
|
|
9896
|
+
fs5.rmSync(path8.join(from, id), { force: true });
|
|
9018
9897
|
}
|
|
9019
9898
|
removeIfEmpty(from);
|
|
9020
9899
|
} catch {}
|
|
@@ -9039,13 +9918,13 @@ function recordIssuedSession(cwd, lead, issued) {
|
|
|
9039
9918
|
try {
|
|
9040
9919
|
migrateRepoIssued(cwd);
|
|
9041
9920
|
const own = hash(issued);
|
|
9042
|
-
const tokens =
|
|
9921
|
+
const tokens = path8.join(userTokenRoot(), hash(lead));
|
|
9043
9922
|
fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
9044
|
-
fs5.writeFileSync(
|
|
9923
|
+
fs5.writeFileSync(path8.join(tokens, own), `${tokenHash(token)}
|
|
9045
9924
|
`, { mode: 384, flag: "wx" });
|
|
9046
|
-
const dir =
|
|
9925
|
+
const dir = path8.join(userIssuedRoot(), hash(lead));
|
|
9047
9926
|
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9048
|
-
fs5.writeFileSync(
|
|
9927
|
+
fs5.writeFileSync(path8.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
9049
9928
|
`, { mode: 384 });
|
|
9050
9929
|
pruneIssued(dir, own, tokens);
|
|
9051
9930
|
return token;
|
|
@@ -9073,10 +9952,10 @@ function pruneIssued(dir, own, tokens) {
|
|
|
9073
9952
|
const entries = fs5.readdirSync(dir);
|
|
9074
9953
|
if (entries.length <= ISSUED_PER_LEAD)
|
|
9075
9954
|
return;
|
|
9076
|
-
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs5.statSync(
|
|
9955
|
+
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs5.statSync(path8.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
9077
9956
|
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
|
|
9078
|
-
fs5.rmSync(
|
|
9079
|
-
fs5.rmSync(
|
|
9957
|
+
fs5.rmSync(path8.join(dir, name), { force: true });
|
|
9958
|
+
fs5.rmSync(path8.join(tokens, name), { force: true });
|
|
9080
9959
|
}
|
|
9081
9960
|
} catch {}
|
|
9082
9961
|
}
|
|
@@ -9086,7 +9965,7 @@ var issuedRecord = (cwd, claimed) => {
|
|
|
9086
9965
|
return null;
|
|
9087
9966
|
migrateRepoIssued(cwd);
|
|
9088
9967
|
try {
|
|
9089
|
-
return fs5.readFileSync(
|
|
9968
|
+
return fs5.readFileSync(path8.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
9090
9969
|
} catch {
|
|
9091
9970
|
return null;
|
|
9092
9971
|
}
|
|
@@ -9096,9 +9975,9 @@ var issuedToken = (claimed) => {
|
|
|
9096
9975
|
const cut = claimed.lastIndexOf(":");
|
|
9097
9976
|
if (cut <= 0)
|
|
9098
9977
|
return { stored: null, leadBound: false };
|
|
9099
|
-
const dir =
|
|
9978
|
+
const dir = path8.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
9100
9979
|
try {
|
|
9101
|
-
const text = fs5.readFileSync(
|
|
9980
|
+
const text = fs5.readFileSync(path8.join(dir, hash(claimed)), "utf8");
|
|
9102
9981
|
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
9103
9982
|
} catch {
|
|
9104
9983
|
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
@@ -9118,33 +9997,33 @@ var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
|
9118
9997
|
// packages/workit-core/src/hooks/standing-signal.ts
|
|
9119
9998
|
import { createHash as createHash8 } from "node:crypto";
|
|
9120
9999
|
import fs6 from "node:fs";
|
|
9121
|
-
import
|
|
10000
|
+
import path9 from "node:path";
|
|
9122
10001
|
var gitStoreDir = (cwd) => {
|
|
9123
10002
|
try {
|
|
9124
|
-
for (let dir =
|
|
9125
|
-
const dotGit =
|
|
10003
|
+
for (let dir = path9.resolve(cwd);; dir = path9.dirname(dir)) {
|
|
10004
|
+
const dotGit = path9.join(dir, ".git");
|
|
9126
10005
|
if (fs6.existsSync(dotGit)) {
|
|
9127
10006
|
let gitDir = dotGit;
|
|
9128
10007
|
if (fs6.statSync(dotGit).isFile()) {
|
|
9129
10008
|
const target = /^gitdir:\s*(.+)$/mu.exec(fs6.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
9130
10009
|
if (!target)
|
|
9131
10010
|
return null;
|
|
9132
|
-
gitDir =
|
|
9133
|
-
const common =
|
|
10011
|
+
gitDir = path9.resolve(dir, target);
|
|
10012
|
+
const common = path9.join(gitDir, "commondir");
|
|
9134
10013
|
if (fs6.existsSync(common))
|
|
9135
|
-
gitDir =
|
|
10014
|
+
gitDir = path9.resolve(gitDir, fs6.readFileSync(common, "utf8").trim());
|
|
9136
10015
|
}
|
|
9137
|
-
return
|
|
10016
|
+
return path9.join(gitDir, "workit");
|
|
9138
10017
|
}
|
|
9139
|
-
if (
|
|
10018
|
+
if (path9.dirname(dir) === dir)
|
|
9140
10019
|
return null;
|
|
9141
10020
|
}
|
|
9142
10021
|
} catch {
|
|
9143
10022
|
return null;
|
|
9144
10023
|
}
|
|
9145
10024
|
};
|
|
9146
|
-
var standingRevFile = (store) =>
|
|
9147
|
-
var standingSeenFile = (store, session, agentId) =>
|
|
10025
|
+
var standingRevFile = (store) => path9.join(store, "standing", "rev");
|
|
10026
|
+
var standingSeenFile = (store, session, agentId) => path9.join(store, "standing", "seen", `${createHash8("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
|
|
9148
10027
|
var readStandingRev = (store) => {
|
|
9149
10028
|
try {
|
|
9150
10029
|
return fs6.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
@@ -9165,7 +10044,7 @@ var readStandingSeen = (file) => {
|
|
|
9165
10044
|
};
|
|
9166
10045
|
var writeStandingSeen = (file, seen) => {
|
|
9167
10046
|
try {
|
|
9168
|
-
fs6.mkdirSync(
|
|
10047
|
+
fs6.mkdirSync(path9.dirname(file), { recursive: true });
|
|
9169
10048
|
fs6.writeFileSync(file, JSON.stringify(seen));
|
|
9170
10049
|
} catch {}
|
|
9171
10050
|
};
|
|
@@ -9215,52 +10094,19 @@ var git3 = (cwd, args) => {
|
|
|
9215
10094
|
const value = run.ok ? run.stdout.trim() : "";
|
|
9216
10095
|
return value ? value : null;
|
|
9217
10096
|
};
|
|
9218
|
-
var
|
|
10097
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
9219
10098
|
function resolveCommit(cwd, ref) {
|
|
9220
|
-
if (!
|
|
10099
|
+
if (!safeRef2(ref))
|
|
9221
10100
|
return null;
|
|
9222
10101
|
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
9223
10102
|
}
|
|
9224
10103
|
function branchHead(cwd, branch) {
|
|
9225
|
-
if (!
|
|
10104
|
+
if (!safeRef2(branch))
|
|
9226
10105
|
return null;
|
|
9227
10106
|
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
9228
10107
|
}
|
|
9229
|
-
var trackTrunk = (cwd) => {
|
|
9230
|
-
try {
|
|
9231
|
-
const resolved = vcsConfig("resolve", cwd);
|
|
9232
|
-
if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
|
|
9233
|
-
return null;
|
|
9234
|
-
const target = String(resolved.defaultTargetBranch ?? "").trim();
|
|
9235
|
-
return target && safeRef(target) ? target : null;
|
|
9236
|
-
} catch {
|
|
9237
|
-
return null;
|
|
9238
|
-
}
|
|
9239
|
-
};
|
|
9240
|
-
function defaultBase(cwd) {
|
|
9241
|
-
const trunk = trackTrunk(cwd);
|
|
9242
|
-
if (trunk) {
|
|
9243
|
-
for (const candidate of [`origin/${trunk}`, trunk])
|
|
9244
|
-
if (resolveCommit(cwd, candidate))
|
|
9245
|
-
return candidate;
|
|
9246
|
-
}
|
|
9247
|
-
const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
9248
|
-
if (originHead && resolveCommit(cwd, originHead))
|
|
9249
|
-
return originHead;
|
|
9250
|
-
for (const candidate of [
|
|
9251
|
-
"origin/main",
|
|
9252
|
-
"origin/master",
|
|
9253
|
-
"main",
|
|
9254
|
-
"master",
|
|
9255
|
-
"origin/develop",
|
|
9256
|
-
"develop"
|
|
9257
|
-
])
|
|
9258
|
-
if (resolveCommit(cwd, candidate))
|
|
9259
|
-
return candidate;
|
|
9260
|
-
return null;
|
|
9261
|
-
}
|
|
9262
10108
|
function diffHash(cwd, base, head) {
|
|
9263
|
-
if (!
|
|
10109
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
9264
10110
|
return null;
|
|
9265
10111
|
const diff = gitRun2(cwd, [
|
|
9266
10112
|
"diff",
|
|
@@ -9292,14 +10138,14 @@ function diffHash(cwd, base, head) {
|
|
|
9292
10138
|
return `sha256:${hash.digest("hex")}`;
|
|
9293
10139
|
}
|
|
9294
10140
|
function storeRoot(cwd) {
|
|
9295
|
-
const location = resolveStore(
|
|
10141
|
+
const location = resolveStore(path10.resolve(cwd));
|
|
9296
10142
|
if (location instanceof Error)
|
|
9297
10143
|
return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
|
|
9298
10144
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
9299
10145
|
}
|
|
9300
10146
|
function ledgerPath(cwd) {
|
|
9301
10147
|
const root = storeRoot(cwd);
|
|
9302
|
-
return root.ok ? { ok: true, value:
|
|
10148
|
+
return root.ok ? { ok: true, value: path10.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
9303
10149
|
}
|
|
9304
10150
|
var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
|
|
9305
10151
|
var NETWORK_FS_TYPES = new Set([
|
|
@@ -9329,13 +10175,13 @@ var LOCK_STALE_MS = 30000;
|
|
|
9329
10175
|
var LOCK_OWNER = "owner";
|
|
9330
10176
|
var lockToken = (lock) => {
|
|
9331
10177
|
try {
|
|
9332
|
-
return fs7.readFileSync(
|
|
10178
|
+
return fs7.readFileSync(path10.join(lock, LOCK_OWNER), "utf8");
|
|
9333
10179
|
} catch {
|
|
9334
10180
|
return null;
|
|
9335
10181
|
}
|
|
9336
10182
|
};
|
|
9337
10183
|
var lockAgeMs = (lock) => {
|
|
9338
|
-
for (const target of [
|
|
10184
|
+
for (const target of [path10.join(lock, LOCK_OWNER), lock]) {
|
|
9339
10185
|
try {
|
|
9340
10186
|
return Date.now() - fs7.statSync(target).mtimeMs;
|
|
9341
10187
|
} catch {}
|
|
@@ -9352,7 +10198,7 @@ var ledgerLock = {
|
|
|
9352
10198
|
throw error;
|
|
9353
10199
|
}
|
|
9354
10200
|
const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
|
|
9355
|
-
fs7.writeFileSync(
|
|
10201
|
+
fs7.writeFileSync(path10.join(lock, LOCK_OWNER), token);
|
|
9356
10202
|
return token;
|
|
9357
10203
|
},
|
|
9358
10204
|
release(lock, token) {
|
|
@@ -9485,8 +10331,8 @@ function appendLine(file, build, options) {
|
|
|
9485
10331
|
}
|
|
9486
10332
|
};
|
|
9487
10333
|
try {
|
|
9488
|
-
fs7.mkdirSync(
|
|
9489
|
-
if (!options.forceLock && !onNetworkFs(
|
|
10334
|
+
fs7.mkdirSync(path10.dirname(file), { recursive: true });
|
|
10335
|
+
if (!options.forceLock && !onNetworkFs(path10.dirname(file)))
|
|
9490
10336
|
return write();
|
|
9491
10337
|
const locked = withLedgerLock(file, write);
|
|
9492
10338
|
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
@@ -9720,7 +10566,7 @@ function verdictBasis(verdict, current) {
|
|
|
9720
10566
|
}
|
|
9721
10567
|
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
9722
10568
|
function trailerSessions(cwd, base, head) {
|
|
9723
|
-
if (!base || !head || !
|
|
10569
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
9724
10570
|
return [];
|
|
9725
10571
|
const out = git3(cwd, [
|
|
9726
10572
|
"log",
|
|
@@ -9733,7 +10579,7 @@ var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
|
9733
10579
|
function branchScope(cwd, branch) {
|
|
9734
10580
|
const renamedAt = new Map;
|
|
9735
10581
|
let since = Number.NEGATIVE_INFINITY;
|
|
9736
|
-
if (
|
|
10582
|
+
if (safeRef2(branch)) {
|
|
9737
10583
|
const log = git3(cwd, [
|
|
9738
10584
|
"log",
|
|
9739
10585
|
"-g",
|
|
@@ -9878,12 +10724,29 @@ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
|
9878
10724
|
}
|
|
9879
10725
|
return effective;
|
|
9880
10726
|
}
|
|
9881
|
-
|
|
10727
|
+
var restackExact = new WeakMap;
|
|
10728
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
9882
10729
|
if (!from || !to)
|
|
9883
10730
|
return false;
|
|
10731
|
+
const exact = (row, fromHead, head) => {
|
|
10732
|
+
if (exactIn === null)
|
|
10733
|
+
return true;
|
|
10734
|
+
const cwd = exactIn;
|
|
10735
|
+
let known = restackExact.get(row);
|
|
10736
|
+
if (known === undefined) {
|
|
10737
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
10738
|
+
from: fromHead,
|
|
10739
|
+
to: head,
|
|
10740
|
+
fromBase: row.fromBase,
|
|
10741
|
+
toBase: row.toBase
|
|
10742
|
+
});
|
|
10743
|
+
restackExact.set(row, known);
|
|
10744
|
+
}
|
|
10745
|
+
return known;
|
|
10746
|
+
};
|
|
9884
10747
|
const edges = new Map;
|
|
9885
10748
|
for (const row of rows)
|
|
9886
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
10749
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head && exact(row, row.fromHead, row.head))
|
|
9887
10750
|
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
9888
10751
|
const seen = new Set([from]);
|
|
9889
10752
|
const queue = [from];
|
|
@@ -9917,9 +10780,57 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
9917
10780
|
return key;
|
|
9918
10781
|
};
|
|
9919
10782
|
const keyFor = (base) => keysAt(base, head);
|
|
9920
|
-
const
|
|
10783
|
+
const driftCache = new Map;
|
|
10784
|
+
const driftOf = (row) => {
|
|
10785
|
+
if (driftCache.has(row))
|
|
10786
|
+
return driftCache.get(row) ?? null;
|
|
10787
|
+
let drift = null;
|
|
10788
|
+
const toBase = row.base ?? fallbackBase;
|
|
10789
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
10790
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
10791
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
10792
|
+
drift = verdictDrift(cwd, {
|
|
10793
|
+
from: row.head,
|
|
10794
|
+
to: head,
|
|
10795
|
+
fromBase,
|
|
10796
|
+
toBase: toBaseSha,
|
|
10797
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
10798
|
+
});
|
|
10799
|
+
driftCache.set(row, drift);
|
|
10800
|
+
return drift;
|
|
10801
|
+
};
|
|
10802
|
+
const exactCache = new Map;
|
|
10803
|
+
const exactCarry = (row) => {
|
|
10804
|
+
if (FAILING.has(String(row.result)))
|
|
10805
|
+
return true;
|
|
10806
|
+
let exact = exactCache.get(row);
|
|
10807
|
+
if (exact === undefined) {
|
|
10808
|
+
const toBase = row.base ?? fallbackBase;
|
|
10809
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
10810
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
10811
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
10812
|
+
exactCache.set(row, exact);
|
|
10813
|
+
}
|
|
10814
|
+
return exact;
|
|
10815
|
+
};
|
|
10816
|
+
const keyBasis = (row) => {
|
|
9921
10817
|
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
9922
|
-
return basis === "
|
|
10818
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
10819
|
+
};
|
|
10820
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
10821
|
+
const carryOf = (row) => {
|
|
10822
|
+
if (keyBasis(row) !== "stale")
|
|
10823
|
+
return null;
|
|
10824
|
+
if (restacked(row))
|
|
10825
|
+
return null;
|
|
10826
|
+
const drift = driftOf(row);
|
|
10827
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
10828
|
+
};
|
|
10829
|
+
const basisOf = (row) => {
|
|
10830
|
+
const basis = keyBasis(row);
|
|
10831
|
+
if (basis !== "stale")
|
|
10832
|
+
return basis;
|
|
10833
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
9923
10834
|
};
|
|
9924
10835
|
const sameCode = (failed, row) => {
|
|
9925
10836
|
if (failed.head && failed.head === row.head)
|
|
@@ -9927,7 +10838,7 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
9927
10838
|
const base = failed.base ?? fallbackBase;
|
|
9928
10839
|
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
9929
10840
|
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
9930
|
-
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
10841
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
9931
10842
|
};
|
|
9932
10843
|
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
9933
10844
|
const failureAuthorCache = new Map;
|
|
@@ -9983,6 +10894,7 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
9983
10894
|
independent: independence.length === 0,
|
|
9984
10895
|
accepted: reasons.length === 0,
|
|
9985
10896
|
reasons,
|
|
10897
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
9986
10898
|
verdict: row
|
|
9987
10899
|
});
|
|
9988
10900
|
if (reasons.length === 0) {
|
|
@@ -10011,7 +10923,12 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
10011
10923
|
verdict: failing?.verdict ?? null,
|
|
10012
10924
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
10013
10925
|
};
|
|
10014
|
-
const
|
|
10926
|
+
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))));
|
|
10927
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
10928
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
10929
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
10930
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
10931
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
10015
10932
|
return {
|
|
10016
10933
|
branch,
|
|
10017
10934
|
head,
|
|
@@ -10020,6 +10937,8 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
10020
10937
|
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
10021
10938
|
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
10022
10939
|
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
10940
|
+
carry: carryEntry?.carry ?? null,
|
|
10941
|
+
reverify,
|
|
10023
10942
|
accepted,
|
|
10024
10943
|
verdicts,
|
|
10025
10944
|
authors: [...authors].toSorted(),
|
|
@@ -10061,6 +10980,7 @@ var RULES = {
|
|
|
10061
10980
|
self: "verdict:self",
|
|
10062
10981
|
nonAuthor: "verdict:non-author",
|
|
10063
10982
|
verified: "verdict:verified",
|
|
10983
|
+
lane: "verdict:lane",
|
|
10064
10984
|
product: "decision:product",
|
|
10065
10985
|
plan: "plan"
|
|
10066
10986
|
};
|
|
@@ -10083,6 +11003,7 @@ var normalizedScope = (scope) => ({
|
|
|
10083
11003
|
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
10084
11004
|
const out = [];
|
|
10085
11005
|
const base = { scope, dependentAction: null };
|
|
11006
|
+
const proportional = verification === "proportional";
|
|
10086
11007
|
if (judgment.behaviorChange)
|
|
10087
11008
|
out.push(derived2({
|
|
10088
11009
|
...base,
|
|
@@ -10093,7 +11014,17 @@ function deriveRequirements(judgment, scope, constraints, verification = "self")
|
|
|
10093
11014
|
before: "close",
|
|
10094
11015
|
acceptanceAllowed: false
|
|
10095
11016
|
}));
|
|
10096
|
-
if (
|
|
11017
|
+
if (proportional && judgment.riskTier === "normal")
|
|
11018
|
+
out.push(derived2({
|
|
11019
|
+
...base,
|
|
11020
|
+
ruleId: RULES.lane,
|
|
11021
|
+
dimension: "review",
|
|
11022
|
+
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.",
|
|
11023
|
+
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.",
|
|
11024
|
+
before: "close",
|
|
11025
|
+
acceptanceAllowed: false
|
|
11026
|
+
}));
|
|
11027
|
+
if (!proportional && judgment.behaviorChange && judgment.riskTier === "normal")
|
|
10097
11028
|
out.push(verification === "independent" ? derived2({
|
|
10098
11029
|
...base,
|
|
10099
11030
|
ruleId: RULES.nonAuthor,
|
|
@@ -10196,7 +11127,7 @@ var productDecision = (task, ledger) => {
|
|
|
10196
11127
|
};
|
|
10197
11128
|
};
|
|
10198
11129
|
var planRecorded = (task, root) => {
|
|
10199
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" &&
|
|
11130
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path11.resolve(root, ref.path)));
|
|
10200
11131
|
if (cited)
|
|
10201
11132
|
return {
|
|
10202
11133
|
met: true,
|
|
@@ -10210,14 +11141,30 @@ var planRecorded = (task, root) => {
|
|
|
10210
11141
|
decisionIds: []
|
|
10211
11142
|
};
|
|
10212
11143
|
};
|
|
10213
|
-
var
|
|
11144
|
+
var workingLane = (task, root) => branchLane(root, { head: null, workingTree: true, judgment: latestJudgment(task) });
|
|
11145
|
+
var laneVerdict = (task, root, ledger) => {
|
|
11146
|
+
if (!ledger.branch() && latestJudgment(task)?.behaviorChange === false)
|
|
11147
|
+
return {
|
|
11148
|
+
met: true,
|
|
11149
|
+
reason: "no branch to read a lane from, and the judgment records no behavior change",
|
|
11150
|
+
decisionIds: []
|
|
11151
|
+
};
|
|
11152
|
+
const lane = workingLane(task, root);
|
|
11153
|
+
const label = `lane ${lane.lane} (${lane.reasons.join("; ")})`;
|
|
11154
|
+
const need = laneNeed(lane.lane);
|
|
11155
|
+
if (need === "none")
|
|
11156
|
+
return { met: true, reason: `${label}: no verdict needed`, decisionIds: [] };
|
|
11157
|
+
const status = verdict(need === "self" ? RULES.self : RULES.nonAuthor, root, ledger);
|
|
11158
|
+
return { ...status, reason: `${label}: ${status.reason}` };
|
|
11159
|
+
};
|
|
11160
|
+
var verdict = (ruleId, root, ledger) => {
|
|
10214
11161
|
const branch = ledger.branch();
|
|
10215
11162
|
if (!branch)
|
|
10216
11163
|
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
10217
11164
|
const check = checkVerdicts(root, branch, ledger.rows());
|
|
10218
11165
|
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
10219
11166
|
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
10220
|
-
const met = !failing && (
|
|
11167
|
+
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
11168
|
if (met)
|
|
10222
11169
|
return {
|
|
10223
11170
|
met: true,
|
|
@@ -10225,7 +11172,7 @@ var verdict = (requirement, root, ledger) => {
|
|
|
10225
11172
|
decisionIds: []
|
|
10226
11173
|
};
|
|
10227
11174
|
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 =
|
|
11175
|
+
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
11176
|
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
10230
11177
|
};
|
|
10231
11178
|
function derivedRuleStatus(task, requirement, root, ledger) {
|
|
@@ -10237,7 +11184,9 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
10237
11184
|
case RULES.self:
|
|
10238
11185
|
case RULES.nonAuthor:
|
|
10239
11186
|
case RULES.verified:
|
|
10240
|
-
return verdict(requirement, root, ledger);
|
|
11187
|
+
return verdict(requirement.ruleId, root, ledger);
|
|
11188
|
+
case RULES.lane:
|
|
11189
|
+
return laneVerdict(task, root, ledger);
|
|
10241
11190
|
default:
|
|
10242
11191
|
return null;
|
|
10243
11192
|
}
|
|
@@ -10812,7 +11761,7 @@ var judgeInputSummary = (raw) => {
|
|
|
10812
11761
|
|
|
10813
11762
|
// packages/workit-core/src/autonomy.ts
|
|
10814
11763
|
import os4 from "node:os";
|
|
10815
|
-
import
|
|
11764
|
+
import path12 from "node:path";
|
|
10816
11765
|
var accountHome = () => {
|
|
10817
11766
|
try {
|
|
10818
11767
|
return os4.userInfo().homedir;
|
|
@@ -10821,10 +11770,10 @@ var accountHome = () => {
|
|
|
10821
11770
|
}
|
|
10822
11771
|
};
|
|
10823
11772
|
var grantsHome = { resolve: accountHome };
|
|
10824
|
-
var grantsDir = () =>
|
|
11773
|
+
var grantsDir = () => path12.join(grantsHome.resolve(), ".config", "workit");
|
|
10825
11774
|
var grantsOverride = () => {
|
|
10826
|
-
const configured =
|
|
10827
|
-
return configured ===
|
|
11775
|
+
const configured = path12.resolve(resolveConfigDir());
|
|
11776
|
+
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
11777
|
};
|
|
10829
11778
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
10830
11779
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -10877,8 +11826,16 @@ function endpointView(entry, context) {
|
|
|
10877
11826
|
...issue ? { endpointIssue: issue } : {}
|
|
10878
11827
|
};
|
|
10879
11828
|
}
|
|
10880
|
-
var
|
|
10881
|
-
|
|
11829
|
+
var VERIFICATION_MODES = [
|
|
11830
|
+
"proportional",
|
|
11831
|
+
"self",
|
|
11832
|
+
"independent"
|
|
11833
|
+
];
|
|
11834
|
+
var DEFAULT_VERIFICATION = "proportional";
|
|
11835
|
+
var verificationOf = (entry) => {
|
|
11836
|
+
const value = entry?.verification;
|
|
11837
|
+
return VERIFICATION_MODES.includes(value) ? value : DEFAULT_VERIFICATION;
|
|
11838
|
+
};
|
|
10882
11839
|
var isGrantValue = (value) => value === true || value === false || value === "verified";
|
|
10883
11840
|
var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
|
|
10884
11841
|
var fromAutoApprove = (value) => {
|
|
@@ -10950,7 +11907,7 @@ var grantHint = (workspace, kind) => `ask the user to run, in their own terminal
|
|
|
10950
11907
|
// packages/workit-core/src/core/task-store.ts
|
|
10951
11908
|
import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
|
|
10952
11909
|
import * as fs12 from "node:fs";
|
|
10953
|
-
import
|
|
11910
|
+
import path17 from "node:path";
|
|
10954
11911
|
|
|
10955
11912
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
10956
11913
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -11001,7 +11958,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
11001
11958
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
11002
11959
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
11003
11960
|
import fsSync from "node:fs";
|
|
11004
|
-
import
|
|
11961
|
+
import path13 from "node:path";
|
|
11005
11962
|
|
|
11006
11963
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
11007
11964
|
import fs8 from "node:fs";
|
|
@@ -11091,13 +12048,13 @@ ${ownershipToken}
|
|
|
11091
12048
|
return { raw, ownershipToken };
|
|
11092
12049
|
}
|
|
11093
12050
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
11094
|
-
const resolved =
|
|
11095
|
-
const lexicalRelative =
|
|
11096
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
11097
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
12051
|
+
const resolved = path13.resolve(lockPath);
|
|
12052
|
+
const lexicalRelative = path13.relative(lockRoot.rootDir, resolved);
|
|
12053
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path13.sep}`) && !path13.isAbsolute(lexicalRelative) ? lexicalRelative : path13.relative(lockRoot.rootReal, resolved);
|
|
12054
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path13.sep}`) || path13.isAbsolute(relative)) {
|
|
11098
12055
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
11099
12056
|
}
|
|
11100
|
-
return relative.split(
|
|
12057
|
+
return relative.split(path13.sep).join(path13.posix.sep);
|
|
11101
12058
|
}
|
|
11102
12059
|
function parseSidecarLockPayload(raw, parser) {
|
|
11103
12060
|
if (parser) {
|
|
@@ -11244,7 +12201,7 @@ function getFsSafeLockConfig() {
|
|
|
11244
12201
|
|
|
11245
12202
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
11246
12203
|
import fs10 from "node:fs";
|
|
11247
|
-
import
|
|
12204
|
+
import path14 from "node:path";
|
|
11248
12205
|
|
|
11249
12206
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
11250
12207
|
function sleepSync(ms) {
|
|
@@ -11335,26 +12292,26 @@ function canonicalLockParentSync(parent) {
|
|
|
11335
12292
|
return process.platform === "win32" ? fs10.realpathSync.native(parent) : fs10.realpathSync(parent);
|
|
11336
12293
|
}
|
|
11337
12294
|
function normalizeTargetPath(targetPath) {
|
|
11338
|
-
const resolved =
|
|
11339
|
-
fs10.mkdirSync(
|
|
12295
|
+
const resolved = path14.resolve(targetPath);
|
|
12296
|
+
fs10.mkdirSync(path14.dirname(resolved), { recursive: true });
|
|
11340
12297
|
try {
|
|
11341
|
-
return
|
|
12298
|
+
return path14.join(canonicalLockParentSync(path14.dirname(resolved)), path14.basename(resolved));
|
|
11342
12299
|
} catch {
|
|
11343
12300
|
return resolved;
|
|
11344
12301
|
}
|
|
11345
12302
|
}
|
|
11346
12303
|
function boundedLockPath(lockPath, lockRoot) {
|
|
11347
|
-
const resolved =
|
|
12304
|
+
const resolved = path14.resolve(lockPath);
|
|
11348
12305
|
if (!lockRoot)
|
|
11349
12306
|
return resolved;
|
|
11350
12307
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
11351
|
-
const parent =
|
|
12308
|
+
const parent = path14.dirname(resolved);
|
|
11352
12309
|
const parentReal = canonicalLockParentSync(parent);
|
|
11353
|
-
const parentRelative =
|
|
11354
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
12310
|
+
const parentRelative = path14.relative(lockRoot.rootReal, parentReal);
|
|
12311
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path14.sep}`) || path14.isAbsolute(parentRelative)) {
|
|
11355
12312
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
11356
12313
|
}
|
|
11357
|
-
return
|
|
12314
|
+
return path14.join(parentReal, path14.basename(resolved));
|
|
11358
12315
|
}
|
|
11359
12316
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
11360
12317
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -11554,14 +12511,14 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
11554
12511
|
}
|
|
11555
12512
|
}
|
|
11556
12513
|
// packages/workit-core/src/core/package-root.ts
|
|
11557
|
-
import { existsSync as
|
|
11558
|
-
import
|
|
12514
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
12515
|
+
import path15 from "node:path";
|
|
11559
12516
|
import { fileURLToPath } from "node:url";
|
|
11560
12517
|
var packageRoot = () => {
|
|
11561
|
-
let dir =
|
|
12518
|
+
let dir = path15.dirname(fileURLToPath(import.meta.url));
|
|
11562
12519
|
while (true) {
|
|
11563
|
-
const parent =
|
|
11564
|
-
if (
|
|
12520
|
+
const parent = path15.dirname(dir);
|
|
12521
|
+
if (existsSync4(path15.join(dir, "package.json")) || parent === dir)
|
|
11565
12522
|
return dir;
|
|
11566
12523
|
dir = parent;
|
|
11567
12524
|
}
|
|
@@ -11570,7 +12527,7 @@ var packageRoot = () => {
|
|
|
11570
12527
|
// packages/workit-core/src/store/event-log.ts
|
|
11571
12528
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11572
12529
|
import fs11 from "node:fs";
|
|
11573
|
-
import
|
|
12530
|
+
import path16 from "node:path";
|
|
11574
12531
|
var EVENT_VERSION = 1;
|
|
11575
12532
|
|
|
11576
12533
|
class LogDamage extends Error {
|
|
@@ -11681,7 +12638,7 @@ function appendEvent(file, event, validEnd) {
|
|
|
11681
12638
|
fs11.closeSync(fd);
|
|
11682
12639
|
}
|
|
11683
12640
|
if (created)
|
|
11684
|
-
fsyncDirectory(
|
|
12641
|
+
fsyncDirectory(path16.dirname(file));
|
|
11685
12642
|
return validEnd;
|
|
11686
12643
|
}
|
|
11687
12644
|
function replaceFile(file, bytes, durable) {
|
|
@@ -11697,7 +12654,7 @@ function replaceFile(file, bytes, durable) {
|
|
|
11697
12654
|
}
|
|
11698
12655
|
renameWithRetry(temporary, file);
|
|
11699
12656
|
if (durable)
|
|
11700
|
-
fsyncDirectory(
|
|
12657
|
+
fsyncDirectory(path16.dirname(file));
|
|
11701
12658
|
} catch (error) {
|
|
11702
12659
|
try {
|
|
11703
12660
|
fs11.unlinkSync(temporary);
|
|
@@ -12040,7 +12997,7 @@ var cachedRuntimeVersion = null;
|
|
|
12040
12997
|
var runtimeVersion = () => {
|
|
12041
12998
|
if (cachedRuntimeVersion === null) {
|
|
12042
12999
|
try {
|
|
12043
|
-
const pkg = JSON.parse(fs12.readFileSync(
|
|
13000
|
+
const pkg = JSON.parse(fs12.readFileSync(path17.join(packageRoot(), "package.json"), "utf8"));
|
|
12044
13001
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
12045
13002
|
} catch {
|
|
12046
13003
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -12073,7 +13030,7 @@ var readText = (file) => {
|
|
|
12073
13030
|
};
|
|
12074
13031
|
var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
|
|
12075
13032
|
var branchStart = (location, branch, aliases) => {
|
|
12076
|
-
const first = readText(
|
|
13033
|
+
const first = readText(path17.join(path17.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
12077
13034
|
`, 1)[0];
|
|
12078
13035
|
const created = REFLOG_LINE.exec(first);
|
|
12079
13036
|
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
@@ -12083,7 +13040,7 @@ var branchStart = (location, branch, aliases) => {
|
|
|
12083
13040
|
return start;
|
|
12084
13041
|
if (!location.git)
|
|
12085
13042
|
return null;
|
|
12086
|
-
for (const line of readText(
|
|
13043
|
+
for (const line of readText(path17.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
12087
13044
|
`)) {
|
|
12088
13045
|
const entry = REFLOG_LINE.exec(line);
|
|
12089
13046
|
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
@@ -12093,14 +13050,14 @@ var branchStart = (location, branch, aliases) => {
|
|
|
12093
13050
|
}
|
|
12094
13051
|
return null;
|
|
12095
13052
|
};
|
|
12096
|
-
var refExists = (common, ref) => fs12.existsSync(
|
|
13053
|
+
var refExists = (common, ref) => fs12.existsSync(path17.join(common, ref)) || readText(path17.join(common, "packed-refs")).includes(` ${ref}
|
|
12097
13054
|
`);
|
|
12098
13055
|
var origins = new Map;
|
|
12099
13056
|
var sameDirectoryIdentity = (left, right) => {
|
|
12100
|
-
if (!
|
|
13057
|
+
if (!path17.isAbsolute(left) || !path17.isAbsolute(right))
|
|
12101
13058
|
return false;
|
|
12102
|
-
const normalizedLeft =
|
|
12103
|
-
const normalizedRight =
|
|
13059
|
+
const normalizedLeft = path17.resolve(left);
|
|
13060
|
+
const normalizedRight = path17.resolve(right);
|
|
12104
13061
|
try {
|
|
12105
13062
|
const a = fs12.statSync(normalizedLeft, { bigint: true });
|
|
12106
13063
|
const b = fs12.statSync(normalizedRight, { bigint: true });
|
|
@@ -12146,7 +13103,7 @@ class TaskStore {
|
|
|
12146
13103
|
migrationChecked = false;
|
|
12147
13104
|
compactDue = new Set;
|
|
12148
13105
|
constructor(root, options = {}) {
|
|
12149
|
-
const resolved =
|
|
13106
|
+
const resolved = path17.resolve(root);
|
|
12150
13107
|
this.origin = fs12.existsSync(resolved) ? fs12.realpathSync(resolved) : resolved;
|
|
12151
13108
|
this.located = resolveStore(this.origin);
|
|
12152
13109
|
this.root = checkoutRootOf(this.origin, this.located);
|
|
@@ -12155,7 +13112,7 @@ class TaskStore {
|
|
|
12155
13112
|
this.migrateOnRead = options.migrateOnRead;
|
|
12156
13113
|
}
|
|
12157
13114
|
location() {
|
|
12158
|
-
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs12.existsSync(
|
|
13115
|
+
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs12.existsSync(path17.join(this.root, ".git"))) {
|
|
12159
13116
|
this.located = undefined;
|
|
12160
13117
|
this.migrationChecked = false;
|
|
12161
13118
|
}
|
|
@@ -12311,7 +13268,7 @@ class TaskStore {
|
|
|
12311
13268
|
const moved = this.bindLocked(again.data.id, key, input.now);
|
|
12312
13269
|
return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
|
|
12313
13270
|
}
|
|
12314
|
-
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" :
|
|
13271
|
+
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path17.basename(this.root) || this.root;
|
|
12315
13272
|
const intent = {
|
|
12316
13273
|
objective: input.objective?.trim() || `Work on ${label}`,
|
|
12317
13274
|
scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
|
|
@@ -12377,14 +13334,14 @@ class TaskStore {
|
|
|
12377
13334
|
const branch = key.ok ? key.data.branch : null;
|
|
12378
13335
|
if (!branch || !location.ok)
|
|
12379
13336
|
return success(null, null, null);
|
|
12380
|
-
const common =
|
|
12381
|
-
const heads =
|
|
12382
|
-
const headLog = location.data.git ?
|
|
13337
|
+
const common = path17.dirname(location.data.dir);
|
|
13338
|
+
const heads = path17.join(common, "logs", "refs", "heads");
|
|
13339
|
+
const headLog = location.data.git ? path17.join(location.data.git.gitDir, "logs", "HEAD") : "";
|
|
12383
13340
|
const signatures = [
|
|
12384
|
-
|
|
13341
|
+
path17.join(heads, branch),
|
|
12385
13342
|
headLog,
|
|
12386
13343
|
heads,
|
|
12387
|
-
|
|
13344
|
+
path17.join(common, "packed-refs")
|
|
12388
13345
|
].map((file) => fileSignature(file) ?? "-");
|
|
12389
13346
|
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
12390
13347
|
let resolved = origins.get(slot);
|
|
@@ -12396,7 +13353,7 @@ class TaskStore {
|
|
|
12396
13353
|
aliases.push(rename.from);
|
|
12397
13354
|
const start = branchStart(location.data, branch, aliases);
|
|
12398
13355
|
const slash = start?.indexOf("/") ?? -1;
|
|
12399
|
-
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(
|
|
13356
|
+
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
13357
|
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
12401
13358
|
resolved = {
|
|
12402
13359
|
signature: signatures.join("|"),
|
|
@@ -12634,13 +13591,13 @@ class TaskStore {
|
|
|
12634
13591
|
if (!ready.ok)
|
|
12635
13592
|
return ready;
|
|
12636
13593
|
const dirs = this.paths();
|
|
12637
|
-
const recovery =
|
|
13594
|
+
const recovery = path17.join(this.root, ".workit", "recovery");
|
|
12638
13595
|
if (fs12.existsSync(recovery)) {
|
|
12639
13596
|
let files = 0;
|
|
12640
13597
|
let bytes = 0;
|
|
12641
13598
|
try {
|
|
12642
13599
|
for (const name of fs12.readdirSync(recovery)) {
|
|
12643
|
-
const stat = fs12.lstatSync(
|
|
13600
|
+
const stat = fs12.lstatSync(path17.join(recovery, name));
|
|
12644
13601
|
if (!stat.isFile())
|
|
12645
13602
|
continue;
|
|
12646
13603
|
files += 1;
|
|
@@ -12693,7 +13650,7 @@ class TaskStore {
|
|
|
12693
13650
|
if (fs12.existsSync(dirs.blobs))
|
|
12694
13651
|
for (const name of fs12.readdirSync(dirs.blobs)) {
|
|
12695
13652
|
const digest = name.replace(/\.json$/, "");
|
|
12696
|
-
const file =
|
|
13653
|
+
const file = path17.join(dirs.blobs, name);
|
|
12697
13654
|
let stat;
|
|
12698
13655
|
try {
|
|
12699
13656
|
stat = fs12.lstatSync(file);
|
|
@@ -12711,7 +13668,7 @@ class TaskStore {
|
|
|
12711
13668
|
}
|
|
12712
13669
|
return success(null, null, null);
|
|
12713
13670
|
};
|
|
12714
|
-
const swept = dryRun ? sweep() : this.withLock(
|
|
13671
|
+
const swept = dryRun ? sweep() : this.withLock(path17.join(dirs.dir, "gc.lock"), sweep);
|
|
12715
13672
|
if (!swept.ok)
|
|
12716
13673
|
return swept;
|
|
12717
13674
|
const temporaryDirs = [
|
|
@@ -12719,7 +13676,7 @@ class TaskStore {
|
|
|
12719
13676
|
dirs.tasks,
|
|
12720
13677
|
dirs.blobs,
|
|
12721
13678
|
dirs.checkout,
|
|
12722
|
-
...names.map((name) =>
|
|
13679
|
+
...names.map((name) => path17.join(dirs.tasks, name))
|
|
12723
13680
|
];
|
|
12724
13681
|
for (const directory of temporaryDirs) {
|
|
12725
13682
|
if (!fs12.existsSync(directory))
|
|
@@ -12727,7 +13684,7 @@ class TaskStore {
|
|
|
12727
13684
|
for (const name of fs12.readdirSync(directory)) {
|
|
12728
13685
|
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
12729
13686
|
continue;
|
|
12730
|
-
const file =
|
|
13687
|
+
const file = path17.join(directory, name);
|
|
12731
13688
|
try {
|
|
12732
13689
|
const stat = fs12.lstatSync(file);
|
|
12733
13690
|
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
@@ -12744,30 +13701,30 @@ class TaskStore {
|
|
|
12744
13701
|
}
|
|
12745
13702
|
paths() {
|
|
12746
13703
|
const location = this.location();
|
|
12747
|
-
const dir = location.ok ? location.data.dir :
|
|
12748
|
-
const checkout =
|
|
13704
|
+
const dir = location.ok ? location.data.dir : path17.join(this.root, ".workit");
|
|
13705
|
+
const checkout = path17.join(dir, "checkouts", checkoutSlug(this.root));
|
|
12749
13706
|
return {
|
|
12750
13707
|
dir,
|
|
12751
13708
|
shared: location.ok ? location.data.shared : false,
|
|
12752
|
-
marker:
|
|
13709
|
+
marker: path17.join(dir, "store.json"),
|
|
12753
13710
|
checkout,
|
|
12754
|
-
workspace:
|
|
12755
|
-
checkoutLock:
|
|
12756
|
-
tasks:
|
|
12757
|
-
index:
|
|
12758
|
-
blobs:
|
|
12759
|
-
legacy:
|
|
12760
|
-
v2:
|
|
13711
|
+
workspace: path17.join(checkout, "workspace.json"),
|
|
13712
|
+
checkoutLock: path17.join(checkout, "metadata.lock"),
|
|
13713
|
+
tasks: path17.join(dir, "tasks"),
|
|
13714
|
+
index: path17.join(dir, "task-index.json"),
|
|
13715
|
+
blobs: path17.join(dir, "blobs", "candidates"),
|
|
13716
|
+
legacy: path17.join(dir, "legacy", checkoutSlug(this.root), "v2"),
|
|
13717
|
+
v2: path17.join(this.root, ".workit")
|
|
12761
13718
|
};
|
|
12762
13719
|
}
|
|
12763
13720
|
eventsPath(taskId) {
|
|
12764
|
-
return
|
|
13721
|
+
return path17.join(this.paths().tasks, taskId, "events.jsonl");
|
|
12765
13722
|
}
|
|
12766
13723
|
snapshotPath(taskId) {
|
|
12767
|
-
return
|
|
13724
|
+
return path17.join(this.paths().tasks, taskId, "snapshot.json");
|
|
12768
13725
|
}
|
|
12769
13726
|
taskLockPath(taskId) {
|
|
12770
|
-
return
|
|
13727
|
+
return path17.join(this.paths().tasks, taskId, "lock");
|
|
12771
13728
|
}
|
|
12772
13729
|
ready(write = false) {
|
|
12773
13730
|
const location = this.location();
|
|
@@ -12806,13 +13763,13 @@ class TaskStore {
|
|
|
12806
13763
|
return {
|
|
12807
13764
|
read: (digest) => {
|
|
12808
13765
|
try {
|
|
12809
|
-
return JSON.parse(fs12.readFileSync(
|
|
13766
|
+
return JSON.parse(fs12.readFileSync(path17.join(dir, `${digest}.json`), "utf8"));
|
|
12810
13767
|
} catch (error) {
|
|
12811
13768
|
throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
|
|
12812
13769
|
}
|
|
12813
13770
|
},
|
|
12814
13771
|
write: (digest, value) => {
|
|
12815
|
-
const file =
|
|
13772
|
+
const file = path17.join(dir, `${digest}.json`);
|
|
12816
13773
|
if (fs12.existsSync(file)) {
|
|
12817
13774
|
const stamp = new Date;
|
|
12818
13775
|
try {
|
|
@@ -13004,7 +13961,7 @@ class TaskStore {
|
|
|
13004
13961
|
}
|
|
13005
13962
|
}
|
|
13006
13963
|
openTask(record, type, key, legacy = null) {
|
|
13007
|
-
const dir =
|
|
13964
|
+
const dir = path17.join(this.paths().tasks, record.id);
|
|
13008
13965
|
fs12.mkdirSync(dir, { recursive: true });
|
|
13009
13966
|
const blobs = this.blobStore();
|
|
13010
13967
|
const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
|
|
@@ -13097,7 +14054,7 @@ class TaskStore {
|
|
|
13097
14054
|
}
|
|
13098
14055
|
withKeyLock(key, operation) {
|
|
13099
14056
|
const digest = createHash11("sha256").update(key).digest("hex").slice(0, 32);
|
|
13100
|
-
return this.withLock(
|
|
14057
|
+
return this.withLock(path17.join(this.paths().dir, "keys", `${digest}.lock`), operation);
|
|
13101
14058
|
}
|
|
13102
14059
|
bindLocked(taskId, key, timestamp) {
|
|
13103
14060
|
const current = this.readWorkspace();
|
|
@@ -13238,7 +14195,7 @@ class TaskStore {
|
|
|
13238
14195
|
writeWorkspace(record) {
|
|
13239
14196
|
const file = this.paths().workspace;
|
|
13240
14197
|
try {
|
|
13241
|
-
fs12.mkdirSync(
|
|
14198
|
+
fs12.mkdirSync(path17.dirname(file), { recursive: true });
|
|
13242
14199
|
replaceFile(file, `${canonicalJson(record)}
|
|
13243
14200
|
`, true);
|
|
13244
14201
|
return success(null, null, null);
|
|
@@ -13316,23 +14273,23 @@ class TaskStore {
|
|
|
13316
14273
|
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
13317
14274
|
}
|
|
13318
14275
|
v2Dirs() {
|
|
13319
|
-
return [...new Set([this.root, this.origin])].map((dir) =>
|
|
14276
|
+
return [...new Set([this.root, this.origin])].map((dir) => path17.join(dir, ".workit"));
|
|
13320
14277
|
}
|
|
13321
14278
|
pendingMigrations() {
|
|
13322
14279
|
return this.v2Dirs().filter((dir) => {
|
|
13323
|
-
const raw = this.readV2Workspace(
|
|
14280
|
+
const raw = this.readV2Workspace(path17.join(dir, "workspace.json"));
|
|
13324
14281
|
return raw !== null && raw !== TOMBSTONE;
|
|
13325
14282
|
});
|
|
13326
14283
|
}
|
|
13327
14284
|
migrateV2(v2) {
|
|
13328
14285
|
const dirs = this.paths();
|
|
13329
|
-
const v2Workspace =
|
|
13330
|
-
const v2Tasks =
|
|
13331
|
-
const legacy =
|
|
14286
|
+
const v2Workspace = path17.join(v2, "workspace.json");
|
|
14287
|
+
const v2Tasks = path17.join(v2, "tasks");
|
|
14288
|
+
const legacy = path17.join(dirs.dir, "legacy", checkoutSlug(path17.dirname(v2)), "v2");
|
|
13332
14289
|
const raw = this.readV2Workspace(v2Workspace);
|
|
13333
14290
|
if (raw === null || raw === TOMBSTONE)
|
|
13334
14291
|
return success(null, null, null);
|
|
13335
|
-
return this.withLock(dirs.checkoutLock, () => this.withLock(
|
|
14292
|
+
return this.withLock(dirs.checkoutLock, () => this.withLock(path17.join(v2, "metadata.lock"), () => {
|
|
13336
14293
|
const workspace = this.readV2Workspace(v2Workspace);
|
|
13337
14294
|
if (workspace === null || workspace === TOMBSTONE)
|
|
13338
14295
|
return success(null, null, null);
|
|
@@ -13341,7 +14298,7 @@ class TaskStore {
|
|
|
13341
14298
|
return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
|
|
13342
14299
|
let files = [];
|
|
13343
14300
|
try {
|
|
13344
|
-
files = fs12.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(
|
|
14301
|
+
files = fs12.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path17.join(v2Tasks, name)));
|
|
13345
14302
|
} catch {}
|
|
13346
14303
|
const current = this.readWorkspace();
|
|
13347
14304
|
if (!current.ok)
|
|
@@ -13359,21 +14316,21 @@ class TaskStore {
|
|
|
13359
14316
|
let skipped = 0;
|
|
13360
14317
|
const done = new Map;
|
|
13361
14318
|
try {
|
|
13362
|
-
fs12.mkdirSync(
|
|
14319
|
+
fs12.mkdirSync(path17.join(legacy, "tasks"), { recursive: true });
|
|
13363
14320
|
for (const name of ["workspace.json", "index.json"])
|
|
13364
|
-
if (fs12.existsSync(
|
|
13365
|
-
fs12.copyFileSync(
|
|
14321
|
+
if (fs12.existsSync(path17.join(v2, name)))
|
|
14322
|
+
fs12.copyFileSync(path17.join(v2, name), path17.join(legacy, name));
|
|
13366
14323
|
for (const name of files) {
|
|
13367
14324
|
const id = name.slice(0, -5);
|
|
13368
|
-
const file =
|
|
14325
|
+
const file = path17.join(v2Tasks, name);
|
|
13369
14326
|
const bytes = fs12.readFileSync(file, "utf8");
|
|
13370
14327
|
const digest = sha256(bytes);
|
|
13371
|
-
fs12.writeFileSync(
|
|
14328
|
+
fs12.writeFileSync(path17.join(legacy, "tasks", name), bytes);
|
|
13372
14329
|
const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
|
|
13373
14330
|
if (!parsed.ok || parsed.data.id !== id)
|
|
13374
14331
|
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
14332
|
const record = { ...parsed.data, workspaceId: target.id };
|
|
13376
|
-
const origin = { path:
|
|
14333
|
+
const origin = { path: path17.join(legacy, "tasks", name), digest };
|
|
13377
14334
|
if (!fs12.existsSync(this.eventsPath(id))) {
|
|
13378
14335
|
this.openTask(record, "migrated.from_v2", null, origin);
|
|
13379
14336
|
migrated += 1;
|
|
@@ -13409,7 +14366,7 @@ class TaskStore {
|
|
|
13409
14366
|
try {
|
|
13410
14367
|
let complete = true;
|
|
13411
14368
|
for (const [name, digest] of done) {
|
|
13412
|
-
const file =
|
|
14369
|
+
const file = path17.join(v2Tasks, name);
|
|
13413
14370
|
if (sha256(fs12.readFileSync(file, "utf8")) !== digest) {
|
|
13414
14371
|
complete = false;
|
|
13415
14372
|
continue;
|
|
@@ -13420,7 +14377,7 @@ class TaskStore {
|
|
|
13420
14377
|
return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
|
|
13421
14378
|
path: v2
|
|
13422
14379
|
});
|
|
13423
|
-
fs12.rmSync(
|
|
14380
|
+
fs12.rmSync(path17.join(v2, "index.json"), { force: true });
|
|
13424
14381
|
this.writeTombstone(v2, target.id);
|
|
13425
14382
|
} catch (error) {
|
|
13426
14383
|
return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
|
|
@@ -13440,7 +14397,7 @@ class TaskStore {
|
|
|
13440
14397
|
}
|
|
13441
14398
|
adoptLocalStore() {
|
|
13442
14399
|
const dirs = this.paths();
|
|
13443
|
-
const marker =
|
|
14400
|
+
const marker = path17.join(dirs.v2, "store.json");
|
|
13444
14401
|
if (!dirs.shared || !fs12.existsSync(marker))
|
|
13445
14402
|
return success(null, null, null);
|
|
13446
14403
|
return this.withLock(dirs.checkoutLock, () => {
|
|
@@ -13450,7 +14407,7 @@ class TaskStore {
|
|
|
13450
14407
|
try {
|
|
13451
14408
|
const merge = (from, to) => {
|
|
13452
14409
|
if (!fs12.existsSync(to)) {
|
|
13453
|
-
fs12.mkdirSync(
|
|
14410
|
+
fs12.mkdirSync(path17.dirname(to), { recursive: true });
|
|
13454
14411
|
try {
|
|
13455
14412
|
fs12.renameSync(from, to);
|
|
13456
14413
|
} catch {
|
|
@@ -13461,24 +14418,24 @@ class TaskStore {
|
|
|
13461
14418
|
}
|
|
13462
14419
|
if (fs12.lstatSync(from).isDirectory() && fs12.lstatSync(to).isDirectory())
|
|
13463
14420
|
for (const name of fs12.readdirSync(from))
|
|
13464
|
-
merge(
|
|
14421
|
+
merge(path17.join(from, name), path17.join(to, name));
|
|
13465
14422
|
};
|
|
13466
14423
|
for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
|
|
13467
|
-
const from =
|
|
14424
|
+
const from = path17.join(dirs.v2, sub);
|
|
13468
14425
|
if (!fs12.existsSync(from))
|
|
13469
14426
|
continue;
|
|
13470
14427
|
if (sub === "tasks") {
|
|
13471
14428
|
for (const name of fs12.readdirSync(from))
|
|
13472
|
-
if (validId(name) && !fs12.existsSync(
|
|
14429
|
+
if (validId(name) && !fs12.existsSync(path17.join(dirs.dir, sub, name)))
|
|
13473
14430
|
tasks += 1;
|
|
13474
14431
|
}
|
|
13475
|
-
merge(from,
|
|
14432
|
+
merge(from, path17.join(dirs.dir, sub));
|
|
13476
14433
|
}
|
|
13477
|
-
const ledger =
|
|
13478
|
-
if (fs12.existsSync(ledger) && !fs12.existsSync(
|
|
13479
|
-
merge(ledger,
|
|
13480
|
-
fs12.rmSync(
|
|
13481
|
-
fs12.renameSync(marker,
|
|
14434
|
+
const ledger = path17.join(dirs.v2, "ledger");
|
|
14435
|
+
if (fs12.existsSync(ledger) && !fs12.existsSync(path17.join(dirs.dir, "ledger")))
|
|
14436
|
+
merge(ledger, path17.join(dirs.dir, "ledger"));
|
|
14437
|
+
fs12.rmSync(path17.join(dirs.v2, "task-index.json"), { force: true });
|
|
14438
|
+
fs12.renameSync(marker, path17.join(dirs.v2, "store.moved.json"));
|
|
13482
14439
|
} catch (error) {
|
|
13483
14440
|
return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
|
|
13484
14441
|
path: dirs.v2
|
|
@@ -13516,7 +14473,7 @@ class TaskStore {
|
|
|
13516
14473
|
schemaVersion: SCHEMA_VERSION,
|
|
13517
14474
|
id: workspaceId ?? newId(),
|
|
13518
14475
|
revision: newRevision(),
|
|
13519
|
-
root:
|
|
14476
|
+
root: path17.dirname(v2),
|
|
13520
14477
|
runtime: { createdWith: version, updatedWith: version },
|
|
13521
14478
|
writer: null,
|
|
13522
14479
|
store: {
|
|
@@ -13528,11 +14485,11 @@ class TaskStore {
|
|
|
13528
14485
|
critical: ["store"]
|
|
13529
14486
|
};
|
|
13530
14487
|
fs12.mkdirSync(v2, { recursive: true });
|
|
13531
|
-
const ignore =
|
|
14488
|
+
const ignore = path17.join(v2, ".gitignore");
|
|
13532
14489
|
if (!fs12.existsSync(ignore))
|
|
13533
14490
|
fs12.writeFileSync(ignore, `*
|
|
13534
14491
|
`);
|
|
13535
|
-
replaceFile(
|
|
14492
|
+
replaceFile(path17.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
|
|
13536
14493
|
`, true);
|
|
13537
14494
|
}
|
|
13538
14495
|
stubFor(taskId) {
|
|
@@ -13556,8 +14513,8 @@ class TaskStore {
|
|
|
13556
14513
|
}
|
|
13557
14514
|
writeTombstoneIfMissing() {
|
|
13558
14515
|
try {
|
|
13559
|
-
const v2 =
|
|
13560
|
-
if (fs12.existsSync(
|
|
14516
|
+
const v2 = path17.join(this.root, ".workit");
|
|
14517
|
+
if (fs12.existsSync(path17.join(v2, "workspace.json")))
|
|
13561
14518
|
return;
|
|
13562
14519
|
let id = null;
|
|
13563
14520
|
try {
|
|
@@ -13574,7 +14531,7 @@ class TaskStore {
|
|
|
13574
14531
|
replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
|
|
13575
14532
|
`, false);
|
|
13576
14533
|
if (!dirs.shared) {
|
|
13577
|
-
const ignore =
|
|
14534
|
+
const ignore = path17.join(dirs.dir, ".gitignore");
|
|
13578
14535
|
let current = null;
|
|
13579
14536
|
try {
|
|
13580
14537
|
current = fs12.readFileSync(ignore, "utf8");
|
|
@@ -13634,7 +14591,7 @@ class TaskStore {
|
|
|
13634
14591
|
withLock(lockPath, operation) {
|
|
13635
14592
|
try {
|
|
13636
14593
|
this.initializeStorage();
|
|
13637
|
-
fs12.mkdirSync(
|
|
14594
|
+
fs12.mkdirSync(path17.dirname(lockPath), { recursive: true });
|
|
13638
14595
|
} catch (error) {
|
|
13639
14596
|
return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
13640
14597
|
path: this.paths().dir
|
|
@@ -13697,7 +14654,7 @@ class TaskStore {
|
|
|
13697
14654
|
if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
|
|
13698
14655
|
return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
|
|
13699
14656
|
path: lockPath,
|
|
13700
|
-
guidance: `Check permissions and the read-only attribute on ${
|
|
14657
|
+
guidance: `Check permissions and the read-only attribute on ${path17.dirname(lockPath)}.`
|
|
13701
14658
|
});
|
|
13702
14659
|
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
13703
14660
|
let lock = null;
|
|
@@ -13724,18 +14681,18 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
13724
14681
|
import { createHash as createHash12 } from "node:crypto";
|
|
13725
14682
|
import * as fs14 from "node:fs";
|
|
13726
14683
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
13727
|
-
import
|
|
14684
|
+
import path19 from "node:path";
|
|
13728
14685
|
|
|
13729
14686
|
// packages/workit-core/src/check-config.ts
|
|
13730
14687
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
13731
14688
|
import fs13 from "node:fs";
|
|
13732
|
-
import
|
|
14689
|
+
import path18 from "node:path";
|
|
13733
14690
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
13734
14691
|
var CHECKS_FILE = ".workit/checks.json";
|
|
13735
14692
|
var LEGACY_CHECKS_FILE = "workit.checks.json";
|
|
13736
14693
|
function checksConfigFile(root) {
|
|
13737
|
-
const current =
|
|
13738
|
-
const legacy =
|
|
14694
|
+
const current = path18.join(root, CHECKS_FILE);
|
|
14695
|
+
const legacy = path18.join(root, LEGACY_CHECKS_FILE);
|
|
13739
14696
|
return !fs13.existsSync(current) && fs13.existsSync(legacy) ? legacy : current;
|
|
13740
14697
|
}
|
|
13741
14698
|
function repoHeavyCommands(root) {
|
|
@@ -13798,10 +14755,10 @@ var git4 = (cwd, args) => {
|
|
|
13798
14755
|
return value || null;
|
|
13799
14756
|
};
|
|
13800
14757
|
function checkRoot(cwd) {
|
|
13801
|
-
return
|
|
14758
|
+
return path18.resolve(git4(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
13802
14759
|
}
|
|
13803
14760
|
var packageManager = (root) => {
|
|
13804
|
-
const has = (file) => fs13.existsSync(
|
|
14761
|
+
const has = (file) => fs13.existsSync(path18.join(root, file));
|
|
13805
14762
|
if (has("bun.lock") || has("bun.lockb"))
|
|
13806
14763
|
return "bun";
|
|
13807
14764
|
if (has("pnpm-lock.yaml"))
|
|
@@ -13821,7 +14778,7 @@ var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
|
13821
14778
|
function detectedChecks(root) {
|
|
13822
14779
|
const read = (file) => {
|
|
13823
14780
|
try {
|
|
13824
|
-
return fs13.readFileSync(
|
|
14781
|
+
return fs13.readFileSync(path18.join(root, file), "utf8");
|
|
13825
14782
|
} catch {
|
|
13826
14783
|
return null;
|
|
13827
14784
|
}
|
|
@@ -13861,7 +14818,7 @@ var GATES = ["testing", "verification"];
|
|
|
13861
14818
|
function loadCheckConfig(cwd) {
|
|
13862
14819
|
const root = checkRoot(cwd);
|
|
13863
14820
|
const file = checksConfigFile(root);
|
|
13864
|
-
const legacy =
|
|
14821
|
+
const legacy = path18.basename(file) === LEGACY_CHECKS_FILE;
|
|
13865
14822
|
const label = legacy ? LEGACY_CHECKS_FILE : CHECKS_FILE;
|
|
13866
14823
|
const note = legacy ? {
|
|
13867
14824
|
note: `${LEGACY_CHECKS_FILE} is deprecated; move it to ${CHECKS_FILE} (workit doctor --fix)`
|
|
@@ -13939,10 +14896,10 @@ function workitAvailable(env = process.env) {
|
|
|
13939
14896
|
return workitOnPath;
|
|
13940
14897
|
const windows = process.platform === "win32";
|
|
13941
14898
|
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
13942
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(
|
|
14899
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path18.delimiter).filter(Boolean);
|
|
13943
14900
|
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
13944
14901
|
try {
|
|
13945
|
-
return fs13.statSync(
|
|
14902
|
+
return fs13.statSync(path18.join(dir, `workit${ext}`)).isFile();
|
|
13946
14903
|
} catch {
|
|
13947
14904
|
return false;
|
|
13948
14905
|
}
|
|
@@ -13955,14 +14912,14 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
13955
14912
|
|
|
13956
14913
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
13957
14914
|
var digestBytes = (value) => createHash12("sha256").update(value).digest("hex");
|
|
13958
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
13959
|
-
var relative = (root, candidate) =>
|
|
14915
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path19.sep}`);
|
|
14916
|
+
var relative = (root, candidate) => path19.relative(root, candidate).split(path19.sep).join("/") || ".";
|
|
13960
14917
|
var canonicalPath = (value) => {
|
|
13961
14918
|
if (typeof value !== "string" || !value)
|
|
13962
14919
|
return null;
|
|
13963
14920
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
13964
14921
|
return null;
|
|
13965
|
-
const normalized =
|
|
14922
|
+
const normalized = path19.posix.normalize(value);
|
|
13966
14923
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
13967
14924
|
return null;
|
|
13968
14925
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14010,14 +14967,14 @@ var gitPaths = (root) => {
|
|
|
14010
14967
|
git: !/not a git repository/i.test(stderr)
|
|
14011
14968
|
};
|
|
14012
14969
|
}
|
|
14013
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14970
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")) : [];
|
|
14014
14971
|
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14015
14972
|
if (stagedDeleted.status !== 0) {
|
|
14016
14973
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14017
14974
|
if (!/not a git repository/i.test(stderr))
|
|
14018
14975
|
return { paths, uncertain: true, git: true };
|
|
14019
14976
|
} else if (stagedDeleted.stdout) {
|
|
14020
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14977
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")));
|
|
14021
14978
|
}
|
|
14022
14979
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14023
14980
|
};
|
|
@@ -14032,7 +14989,7 @@ var walk = (root, directory, output) => {
|
|
|
14032
14989
|
for (const name of names) {
|
|
14033
14990
|
if (name === ".git" || name === ".workit")
|
|
14034
14991
|
continue;
|
|
14035
|
-
const target =
|
|
14992
|
+
const target = path19.join(directory, name);
|
|
14036
14993
|
let stat;
|
|
14037
14994
|
try {
|
|
14038
14995
|
stat = fs14.lstatSync(target);
|
|
@@ -14050,12 +15007,12 @@ var walk = (root, directory, output) => {
|
|
|
14050
15007
|
var scopeRoots = (root, scope) => {
|
|
14051
15008
|
const roots = [];
|
|
14052
15009
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14053
|
-
const target =
|
|
15010
|
+
const target = path19.resolve(root, item);
|
|
14054
15011
|
if (!inside(root, target))
|
|
14055
15012
|
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
15013
|
let ancestor = target;
|
|
14057
15014
|
while (!fs14.existsSync(ancestor) && ancestor !== root)
|
|
14058
|
-
ancestor =
|
|
15015
|
+
ancestor = path19.dirname(ancestor);
|
|
14059
15016
|
try {
|
|
14060
15017
|
if (!inside(root, fs14.realpathSync(ancestor)))
|
|
14061
15018
|
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 +15064,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14107
15064
|
for (const item of [...names].toSorted()) {
|
|
14108
15065
|
if (!scopeMatches(item, normalizedScope))
|
|
14109
15066
|
continue;
|
|
14110
|
-
const target =
|
|
15067
|
+
const target = path19.join(checkout, item);
|
|
14111
15068
|
try {
|
|
14112
15069
|
const stat = fs14.lstatSync(target);
|
|
14113
15070
|
if (stat.isSymbolicLink()) {
|
|
@@ -14291,23 +15248,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14291
15248
|
continue;
|
|
14292
15249
|
if (!reference.digest)
|
|
14293
15250
|
return failure2("invalid_input", "document references require a byte digest");
|
|
14294
|
-
const target =
|
|
14295
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15251
|
+
const target = path19.resolve(root, reference.path);
|
|
15252
|
+
if (target !== root && !target.startsWith(`${root}${path19.sep}`))
|
|
14296
15253
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14297
15254
|
fields: [
|
|
14298
15255
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
14299
15256
|
]
|
|
14300
15257
|
});
|
|
14301
15258
|
try {
|
|
14302
|
-
const below =
|
|
15259
|
+
const below = path19.relative(root, target);
|
|
14303
15260
|
let current = root;
|
|
14304
|
-
for (const segment of below.split(
|
|
15261
|
+
for (const segment of below.split(path19.sep)) {
|
|
14305
15262
|
if (!segment)
|
|
14306
15263
|
continue;
|
|
14307
|
-
current =
|
|
15264
|
+
current = path19.join(current, segment);
|
|
14308
15265
|
const stat = fs14.lstatSync(current);
|
|
14309
15266
|
const real = fs14.realpathSync(current);
|
|
14310
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15267
|
+
if (real !== root && !real.startsWith(`${root}${path19.sep}`))
|
|
14311
15268
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14312
15269
|
fields: [
|
|
14313
15270
|
{
|
|
@@ -14740,9 +15697,9 @@ Autonomy contract:
|
|
|
14740
15697
|
Prose is a note: only a configured check the CLI ran (\`workit check <name>\`)
|
|
14741
15698
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
14742
15699
|
- 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\`).
|
|
15700
|
+
self-reviewed, never verified. High risk, a focused or full lane and
|
|
15701
|
+
merge under \`verification: self|independent\` need a verifier with its
|
|
15702
|
+
own host-derived session (a subagent's \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
14746
15703
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
14747
15704
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
14748
15705
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -14750,7 +15707,7 @@ Autonomy contract:
|
|
|
14750
15707
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
14751
15708
|
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
14752
15709
|
else \`npx -y @brainervirus/workit-cli\`).
|
|
14753
|
-
Other checkout: \`workit --cwd <dir>
|
|
15710
|
+
Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
|
|
14754
15711
|
verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
|
|
14755
15712
|
(\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
|
|
14756
15713
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
@@ -14762,7 +15719,7 @@ authorizes an effect. Judge work once with
|
|
|
14762
15719
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
14763
15720
|
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
14764
15721
|
competing targets before a mutation, and reconcile an uncertain
|
|
14765
|
-
|
|
15722
|
+
effect before a retry.
|
|
14766
15723
|
|
|
14767
15724
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
14768
15725
|
|
|
@@ -16608,7 +17565,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
16608
17565
|
// packages/workit-core/src/hooks/context.ts
|
|
16609
17566
|
import { createHash as createHash13 } from "node:crypto";
|
|
16610
17567
|
import fs15 from "node:fs";
|
|
16611
|
-
import
|
|
17568
|
+
import path20 from "node:path";
|
|
16612
17569
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
16613
17570
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
16614
17571
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
@@ -16693,7 +17650,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
16693
17650
|
return null;
|
|
16694
17651
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
16695
17652
|
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" ? [
|
|
17653
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path20.resolve(store.root, ref.path)] : []));
|
|
16697
17654
|
const observedFiles = filesKey(files);
|
|
16698
17655
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
16699
17656
|
if (!compact.ok || !compact.data)
|
|
@@ -16771,14 +17728,14 @@ var turnMarker = (input) => {
|
|
|
16771
17728
|
if (location instanceof Error)
|
|
16772
17729
|
return null;
|
|
16773
17730
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
16774
|
-
return
|
|
17731
|
+
return path20.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
16775
17732
|
} catch {
|
|
16776
17733
|
return null;
|
|
16777
17734
|
}
|
|
16778
17735
|
};
|
|
16779
17736
|
var writeTurnDigest = (file, digest) => {
|
|
16780
17737
|
try {
|
|
16781
|
-
fs15.mkdirSync(
|
|
17738
|
+
fs15.mkdirSync(path20.dirname(file), { recursive: true });
|
|
16782
17739
|
fs15.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
16783
17740
|
`);
|
|
16784
17741
|
} catch {}
|
|
@@ -16793,7 +17750,7 @@ var pruneTurnDigests = (dir, now) => {
|
|
|
16793
17750
|
for (const name of fs15.readdirSync(dir)) {
|
|
16794
17751
|
if (!name.startsWith("turn-"))
|
|
16795
17752
|
continue;
|
|
16796
|
-
const file =
|
|
17753
|
+
const file = path20.join(dir, name);
|
|
16797
17754
|
try {
|
|
16798
17755
|
if (now - fs15.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
16799
17756
|
fs15.rmSync(file, { force: true });
|
|
@@ -16823,7 +17780,7 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
16823
17780
|
const file = turnMarker(input);
|
|
16824
17781
|
if (!file)
|
|
16825
17782
|
return;
|
|
16826
|
-
pruneTurnDigests(
|
|
17783
|
+
pruneTurnDigests(path20.dirname(file), Date.now());
|
|
16827
17784
|
if (text)
|
|
16828
17785
|
writeTurnDigest(file, digestOf(text));
|
|
16829
17786
|
else
|
|
@@ -16831,20 +17788,20 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
16831
17788
|
};
|
|
16832
17789
|
// packages/workit-core/src/core/route-intent.ts
|
|
16833
17790
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
16834
|
-
import { existsSync as
|
|
16835
|
-
import
|
|
17791
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
17792
|
+
import path23 from "node:path";
|
|
16836
17793
|
|
|
16837
17794
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
16838
17795
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
16839
17796
|
import { createHash as createHash15 } from "node:crypto";
|
|
16840
17797
|
import fs17 from "node:fs";
|
|
16841
17798
|
import os5 from "node:os";
|
|
16842
|
-
import
|
|
17799
|
+
import path22 from "node:path";
|
|
16843
17800
|
|
|
16844
17801
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
16845
17802
|
import { createHash as createHash14 } from "node:crypto";
|
|
16846
17803
|
import fs16 from "node:fs";
|
|
16847
|
-
import
|
|
17804
|
+
import path21 from "node:path";
|
|
16848
17805
|
var sessionMarkerFile = (input, session, store) => {
|
|
16849
17806
|
let dir = store;
|
|
16850
17807
|
if (dir === undefined) {
|
|
@@ -16854,7 +17811,7 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
16854
17811
|
dir = location.dir;
|
|
16855
17812
|
}
|
|
16856
17813
|
const name = createHash14("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
16857
|
-
return
|
|
17814
|
+
return path21.join(dir, "hooks", `skills-${name}.json`);
|
|
16858
17815
|
};
|
|
16859
17816
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
16860
17817
|
var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
|
|
@@ -16881,7 +17838,7 @@ var parseMarker = (text) => {
|
|
|
16881
17838
|
};
|
|
16882
17839
|
var readSessionMarker = (file) => parseMarker(readRaw(file));
|
|
16883
17840
|
var writeAtomic = (file, marker) => {
|
|
16884
|
-
fs16.mkdirSync(
|
|
17841
|
+
fs16.mkdirSync(path21.dirname(file), { recursive: true });
|
|
16885
17842
|
const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
16886
17843
|
try {
|
|
16887
17844
|
fs16.writeFileSync(temp, JSON.stringify(marker));
|
|
@@ -17168,6 +18125,18 @@ var substitutions = (body) => {
|
|
|
17168
18125
|
};
|
|
17169
18126
|
var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
|
|
17170
18127
|
var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
|
|
18128
|
+
var expandsInQuotes = (body, dialect) => {
|
|
18129
|
+
const escape = dialect === "posix" ? "\\" : "`";
|
|
18130
|
+
for (let index = 0;index < body.length; index++) {
|
|
18131
|
+
const char = body[index];
|
|
18132
|
+
if (char === escape)
|
|
18133
|
+
index++;
|
|
18134
|
+
else if (char === "$" || dialect === "posix" && char === "`")
|
|
18135
|
+
return true;
|
|
18136
|
+
}
|
|
18137
|
+
return false;
|
|
18138
|
+
};
|
|
18139
|
+
var EXPANDING_RUN = /[*?[]/u;
|
|
17171
18140
|
var arithmeticEnd = (command, start) => {
|
|
17172
18141
|
const end = substitutionEnd(command, start);
|
|
17173
18142
|
return command[end - 1] === ")" && end > start + 2 ? end : 0;
|
|
@@ -17186,6 +18155,7 @@ var tokenize = (command, dialect) => {
|
|
|
17186
18155
|
let current = { words: [], redirects: [] };
|
|
17187
18156
|
let word = "";
|
|
17188
18157
|
let inWord = false;
|
|
18158
|
+
let expanding = false;
|
|
17189
18159
|
let pending = null;
|
|
17190
18160
|
const heredocs = [];
|
|
17191
18161
|
const escape = posix ? "\\" : "`";
|
|
@@ -17200,11 +18170,16 @@ var tokenize = (command, dialect) => {
|
|
|
17200
18170
|
heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
|
|
17201
18171
|
else if (pending === "herestring")
|
|
17202
18172
|
(current.stdin ??= []).push(text);
|
|
17203
|
-
else if (pending !== "input")
|
|
18173
|
+
else if (pending !== "input") {
|
|
18174
|
+
const at = current.words.length;
|
|
17204
18175
|
current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
|
|
18176
|
+
if (expanding || !posix && word.startsWith("@"))
|
|
18177
|
+
(current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
|
|
18178
|
+
}
|
|
17205
18179
|
pending = null;
|
|
17206
18180
|
word = "";
|
|
17207
18181
|
inWord = false;
|
|
18182
|
+
expanding = false;
|
|
17208
18183
|
};
|
|
17209
18184
|
const flushSegment = () => {
|
|
17210
18185
|
flushWord();
|
|
@@ -17219,6 +18194,7 @@ var tokenize = (command, dialect) => {
|
|
|
17219
18194
|
};
|
|
17220
18195
|
const substitution = (inner, text) => {
|
|
17221
18196
|
(current.subs ??= []).push(inner);
|
|
18197
|
+
expanding = true;
|
|
17222
18198
|
word += text;
|
|
17223
18199
|
inWord = true;
|
|
17224
18200
|
};
|
|
@@ -17229,6 +18205,8 @@ var tokenize = (command, dialect) => {
|
|
|
17229
18205
|
if (run) {
|
|
17230
18206
|
word += run[0];
|
|
17231
18207
|
inWord = true;
|
|
18208
|
+
if (EXPANDING_RUN.test(run[0]))
|
|
18209
|
+
expanding = true;
|
|
17232
18210
|
index += run[0].length - 1;
|
|
17233
18211
|
continue;
|
|
17234
18212
|
}
|
|
@@ -17257,6 +18235,8 @@ var tokenize = (command, dialect) => {
|
|
|
17257
18235
|
const body = command.slice(index + 1, end < 0 ? undefined : end);
|
|
17258
18236
|
if (posix && char === '"' && /\$\(|`/.test(body))
|
|
17259
18237
|
(current.subs ??= []).push(...substitutions(body));
|
|
18238
|
+
if (char === '"' && expandsInQuotes(body, dialect))
|
|
18239
|
+
expanding = true;
|
|
17260
18240
|
word += unquote(body, char, dialect);
|
|
17261
18241
|
inWord = true;
|
|
17262
18242
|
index = end < 0 ? command.length : end;
|
|
@@ -17302,6 +18282,8 @@ var tokenize = (command, dialect) => {
|
|
|
17302
18282
|
if (char === "(" && inWord && word.endsWith("$")) {
|
|
17303
18283
|
word = word.slice(0, -1);
|
|
17304
18284
|
inWord = word !== "";
|
|
18285
|
+
if (!inWord)
|
|
18286
|
+
expanding = false;
|
|
17305
18287
|
}
|
|
17306
18288
|
group(char === "(" ? "open" : "close");
|
|
17307
18289
|
} else if (!posix && (char === "{" || char === "}")) {
|
|
@@ -17355,6 +18337,8 @@ var tokenize = (command, dialect) => {
|
|
|
17355
18337
|
else if (posix && char === "}" && word.includes(OPEN))
|
|
17356
18338
|
word += CLOSE;
|
|
17357
18339
|
else {
|
|
18340
|
+
if (char === "$")
|
|
18341
|
+
expanding = true;
|
|
17358
18342
|
word += char;
|
|
17359
18343
|
inWord = true;
|
|
17360
18344
|
}
|
|
@@ -17412,14 +18396,14 @@ var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
|
17412
18396
|
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
17413
18397
|
var MAX_DEPTH = 4;
|
|
17414
18398
|
var MAX_EXPANSIONS = 16;
|
|
17415
|
-
var isAbsolute = (value) =>
|
|
18399
|
+
var isAbsolute = (value) => path22.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
17416
18400
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
17417
18401
|
var joinDir = (base, raw) => {
|
|
17418
18402
|
const next = raw.replace(HERE, ".");
|
|
17419
18403
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
17420
18404
|
return;
|
|
17421
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
17422
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
18405
|
+
const expanded = next === "~" || next.startsWith("~/") ? path22.join(os5.homedir(), next.slice(1)) : next;
|
|
18406
|
+
return base === null || isAbsolute(expanded) ? expanded : path22.join(base, expanded);
|
|
17423
18407
|
};
|
|
17424
18408
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
17425
18409
|
var cdTarget = (words) => {
|
|
@@ -17799,8 +18783,17 @@ function walk2(command, dialect, depth, seen) {
|
|
|
17799
18783
|
visit(next, undefined, hops + 1);
|
|
17800
18784
|
};
|
|
17801
18785
|
const wrapped = seen ? null : workitCheckTail(words);
|
|
17802
|
-
if (wrapped)
|
|
17803
|
-
|
|
18786
|
+
if (wrapped) {
|
|
18787
|
+
const outer = dir;
|
|
18788
|
+
if (wrapped.cwd !== null)
|
|
18789
|
+
dir = joinDir(dir, wrapped.cwd);
|
|
18790
|
+
if ("script" in wrapped)
|
|
18791
|
+
nested(wrapped.script, "posix");
|
|
18792
|
+
else
|
|
18793
|
+
runs([wrapped.argv]);
|
|
18794
|
+
dir = outer;
|
|
18795
|
+
return;
|
|
18796
|
+
}
|
|
17804
18797
|
if (CD.has(name)) {
|
|
17805
18798
|
const target = cdTarget(words);
|
|
17806
18799
|
dir = target === null ? os5.homedir() : joinDir(dir, target);
|
|
@@ -17901,14 +18894,14 @@ function workitCheckTail(words) {
|
|
|
17901
18894
|
return null;
|
|
17902
18895
|
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
17903
18896
|
return null;
|
|
17904
|
-
const args = words.slice(at + 1);
|
|
18897
|
+
const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
|
|
17905
18898
|
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
17906
18899
|
return null;
|
|
17907
18900
|
const split = args.indexOf("--");
|
|
17908
18901
|
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
17909
18902
|
if (!tail.length)
|
|
17910
18903
|
return null;
|
|
17911
|
-
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
18904
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
|
|
17912
18905
|
}
|
|
17913
18906
|
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
17914
18907
|
let segments;
|
|
@@ -17969,7 +18962,21 @@ function skipWorkitGlobals(args) {
|
|
|
17969
18962
|
} else if (args[at] !== "--json")
|
|
17970
18963
|
break;
|
|
17971
18964
|
}
|
|
17972
|
-
|
|
18965
|
+
const rest = [];
|
|
18966
|
+
for (;at < args.length; at++) {
|
|
18967
|
+
const arg = args[at];
|
|
18968
|
+
if (arg === "--") {
|
|
18969
|
+
rest.push(...args.slice(at));
|
|
18970
|
+
break;
|
|
18971
|
+
}
|
|
18972
|
+
if (arg.startsWith("--cwd="))
|
|
18973
|
+
cwd = arg.slice("--cwd=".length);
|
|
18974
|
+
else if (arg === "--cwd" && args[at + 1] !== undefined)
|
|
18975
|
+
cwd = args[++at];
|
|
18976
|
+
else
|
|
18977
|
+
rest.push(arg);
|
|
18978
|
+
}
|
|
18979
|
+
return { rest, cwd };
|
|
17973
18980
|
}
|
|
17974
18981
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17975
18982
|
var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
@@ -18270,17 +19277,17 @@ var headSha2 = (repo) => {
|
|
|
18270
19277
|
if (!git || git.reftable)
|
|
18271
19278
|
return null;
|
|
18272
19279
|
try {
|
|
18273
|
-
const head = fs17.readFileSync(
|
|
19280
|
+
const head = fs17.readFileSync(path22.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18274
19281
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18275
19282
|
return head;
|
|
18276
19283
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18277
19284
|
if (!ref)
|
|
18278
19285
|
return null;
|
|
18279
|
-
const common =
|
|
19286
|
+
const common = path22.dirname(repo.location.dir);
|
|
18280
19287
|
try {
|
|
18281
|
-
return fs17.readFileSync(
|
|
19288
|
+
return fs17.readFileSync(path22.join(common, ref), "utf8").trim() || null;
|
|
18282
19289
|
} catch {}
|
|
18283
|
-
const packed = fs17.readFileSync(
|
|
19290
|
+
const packed = fs17.readFileSync(path22.join(common, "packed-refs"), "utf8");
|
|
18284
19291
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18285
19292
|
} catch {
|
|
18286
19293
|
return null;
|
|
@@ -18368,7 +19375,7 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
18368
19375
|
const sub = invocation.args[0];
|
|
18369
19376
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
18370
19377
|
return [invocation];
|
|
18371
|
-
const dir =
|
|
19378
|
+
const dir = path22.resolve(root, invocation.dir ?? ".");
|
|
18372
19379
|
if (!fs17.existsSync(dir))
|
|
18373
19380
|
return [invocation];
|
|
18374
19381
|
const config = gitConfig(dir, invocation, cache);
|
|
@@ -18391,11 +19398,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
18391
19398
|
var normalizedUrl = (url, dir) => {
|
|
18392
19399
|
const trimmed = url.trim();
|
|
18393
19400
|
if (/^file:\/\//i.test(trimmed))
|
|
18394
|
-
return
|
|
19401
|
+
return path22.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18395
19402
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
18396
19403
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
18397
19404
|
if (!scheme && !scp)
|
|
18398
|
-
return
|
|
19405
|
+
return path22.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18399
19406
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
18400
19407
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18401
19408
|
};
|
|
@@ -18619,7 +19626,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
18619
19626
|
const out = [];
|
|
18620
19627
|
const repos = new Map;
|
|
18621
19628
|
const repoFor = (relative) => {
|
|
18622
|
-
const dir =
|
|
19629
|
+
const dir = path22.resolve(cwd, relative ?? ".");
|
|
18623
19630
|
if (!repos.has(dir)) {
|
|
18624
19631
|
const repo = repoAt(dir);
|
|
18625
19632
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18705,7 +19712,7 @@ var analysisDenial = (error) => ({
|
|
|
18705
19712
|
});
|
|
18706
19713
|
var NEXT_COMMAND = "next";
|
|
18707
19714
|
var MARKER_TTL_SECONDS = 3600;
|
|
18708
|
-
var markerFile = (repo, session, key) =>
|
|
19715
|
+
var markerFile = (repo, session, key) => path22.join(repo.location.dir, "hooks", `commit-${createHash15("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18709
19716
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash15("sha256").update(command).digest("hex")}`;
|
|
18710
19717
|
function noteRawCommit(input, command, key) {
|
|
18711
19718
|
try {
|
|
@@ -18716,7 +19723,7 @@ function noteRawCommit(input, command, key) {
|
|
|
18716
19723
|
if (action.kind !== "commit")
|
|
18717
19724
|
continue;
|
|
18718
19725
|
const file = markerFile(repo, input.session.id, key);
|
|
18719
|
-
fs17.mkdirSync(
|
|
19726
|
+
fs17.mkdirSync(path22.dirname(file), { recursive: true });
|
|
18720
19727
|
fs17.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18721
19728
|
`);
|
|
18722
19729
|
}
|
|
@@ -18947,8 +19954,8 @@ var judgedDir = (root, dir) => {
|
|
|
18947
19954
|
return null;
|
|
18948
19955
|
if (dir === null)
|
|
18949
19956
|
return root;
|
|
18950
|
-
const resolved =
|
|
18951
|
-
return
|
|
19957
|
+
const resolved = path23.resolve(root, dir);
|
|
19958
|
+
return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
18952
19959
|
};
|
|
18953
19960
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
18954
19961
|
let judged = false;
|
|
@@ -18981,10 +19988,57 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
18981
19988
|
}
|
|
18982
19989
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
18983
19990
|
};
|
|
19991
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
19992
|
+
var WORKIT_ALLOWED_VERBS = [
|
|
19993
|
+
"git",
|
|
19994
|
+
"pr",
|
|
19995
|
+
"ci",
|
|
19996
|
+
"stack",
|
|
19997
|
+
"release",
|
|
19998
|
+
"task",
|
|
19999
|
+
"ledger",
|
|
20000
|
+
"evidence",
|
|
20001
|
+
"finding",
|
|
20002
|
+
"decision",
|
|
20003
|
+
"worker",
|
|
20004
|
+
"verify-delivery",
|
|
20005
|
+
"policy",
|
|
20006
|
+
"doctor",
|
|
20007
|
+
"help"
|
|
20008
|
+
];
|
|
20009
|
+
var WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
|
|
20010
|
+
var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
20011
|
+
var isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`));
|
|
20012
|
+
var isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word);
|
|
20013
|
+
var bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`;
|
|
20014
|
+
var EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).";
|
|
20015
|
+
var workitBypassFlag = (command, dialect = "posix") => {
|
|
20016
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
20017
|
+
if (segment.unanalyzable) {
|
|
20018
|
+
const text = command.replaceAll(/['"\\`]/gu, "");
|
|
20019
|
+
if (!/\bworkit\b/u.test(text))
|
|
20020
|
+
return null;
|
|
20021
|
+
const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
|
|
20022
|
+
return found ? bypassReason(found) : null;
|
|
20023
|
+
}
|
|
20024
|
+
const start = segment.words.findIndex(isWorkitWord);
|
|
20025
|
+
if (start < 0)
|
|
20026
|
+
continue;
|
|
20027
|
+
const words = segment.words.slice(start + 1);
|
|
20028
|
+
const end = words.indexOf("--");
|
|
20029
|
+
const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
|
|
20030
|
+
if (flag)
|
|
20031
|
+
return bypassReason(flag.split("=")[0] ?? flag);
|
|
20032
|
+
const limit = start + 1 + (end < 0 ? words.length : end);
|
|
20033
|
+
if (segment.expands?.some((index) => index > start && index < limit))
|
|
20034
|
+
return EXPANSION_REASON;
|
|
20035
|
+
}
|
|
20036
|
+
return null;
|
|
20037
|
+
};
|
|
18984
20038
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
18985
20039
|
import fs18 from "node:fs";
|
|
18986
20040
|
import os6 from "node:os";
|
|
18987
|
-
import
|
|
20041
|
+
import path24 from "node:path";
|
|
18988
20042
|
var EMPTY2 = () => ({ deny: [], allow: [] });
|
|
18989
20043
|
var readText2 = (file) => {
|
|
18990
20044
|
try {
|
|
@@ -19025,19 +20079,19 @@ function parseJsonc(text) {
|
|
|
19025
20079
|
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
19026
20080
|
}
|
|
19027
20081
|
function gitTop(cwd) {
|
|
19028
|
-
for (let dir =
|
|
19029
|
-
if (fs18.existsSync(
|
|
20082
|
+
for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
|
|
20083
|
+
if (fs18.existsSync(path24.join(dir, ".git")))
|
|
19030
20084
|
return dir;
|
|
19031
|
-
if (
|
|
20085
|
+
if (path24.dirname(dir) === dir)
|
|
19032
20086
|
return null;
|
|
19033
20087
|
}
|
|
19034
20088
|
}
|
|
19035
20089
|
var projectDirs = (cwd) => {
|
|
19036
20090
|
const top = gitTop(cwd);
|
|
19037
20091
|
const dirs = [];
|
|
19038
|
-
for (let dir =
|
|
20092
|
+
for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
|
|
19039
20093
|
dirs.push(dir);
|
|
19040
|
-
if (!top || dir === top ||
|
|
20094
|
+
if (!top || dir === top || path24.dirname(dir) === dir)
|
|
19041
20095
|
return dirs;
|
|
19042
20096
|
}
|
|
19043
20097
|
};
|
|
@@ -19049,12 +20103,12 @@ var CLAUDE_MANAGED = {
|
|
|
19049
20103
|
};
|
|
19050
20104
|
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
19051
20105
|
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
19052
|
-
const user = env.CLAUDE_CONFIG_DIR ||
|
|
20106
|
+
const user = env.CLAUDE_CONFIG_DIR || path24.join(homeOf(env), ".claude");
|
|
19053
20107
|
return [
|
|
19054
20108
|
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
19055
|
-
|
|
19056
|
-
|
|
19057
|
-
|
|
20109
|
+
path24.join(user, "settings.json"),
|
|
20110
|
+
path24.join(project, ".claude", "settings.json"),
|
|
20111
|
+
path24.join(project, ".claude", "settings.local.json")
|
|
19058
20112
|
];
|
|
19059
20113
|
}
|
|
19060
20114
|
var bashPattern = (entry) => {
|
|
@@ -19144,10 +20198,10 @@ var opencodeConfig = (value, rules) => {
|
|
|
19144
20198
|
function opencodeRules(cwd, env = process.env) {
|
|
19145
20199
|
const rules = EMPTY2();
|
|
19146
20200
|
try {
|
|
19147
|
-
const config = env.XDG_CONFIG_HOME ||
|
|
20201
|
+
const config = env.XDG_CONFIG_HOME || path24.join(homeOf(env), ".config");
|
|
19148
20202
|
const files = [
|
|
19149
|
-
...OPENCODE_NAMES.map((name) =>
|
|
19150
|
-
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [
|
|
20203
|
+
...OPENCODE_NAMES.map((name) => path24.join(config, "opencode", name)),
|
|
20204
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path24.join(dir, name), path24.join(dir, ".opencode", name)])),
|
|
19151
20205
|
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
19152
20206
|
];
|
|
19153
20207
|
for (const file of files) {
|
|
@@ -19191,9 +20245,9 @@ var codexRule = (body) => {
|
|
|
19191
20245
|
function codexRules(cwd, env = process.env) {
|
|
19192
20246
|
const rules = EMPTY2();
|
|
19193
20247
|
try {
|
|
19194
|
-
const home = env.CODEX_HOME ||
|
|
20248
|
+
const home = env.CODEX_HOME || path24.join(homeOf(env), ".codex");
|
|
19195
20249
|
const top = gitTop(cwd) ?? cwd;
|
|
19196
|
-
for (const dir of [
|
|
20250
|
+
for (const dir of [path24.join(home, "rules"), path24.join(top, ".codex", "rules")]) {
|
|
19197
20251
|
let files;
|
|
19198
20252
|
try {
|
|
19199
20253
|
files = fs18.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
@@ -19203,7 +20257,7 @@ function codexRules(cwd, env = process.env) {
|
|
|
19203
20257
|
return "unreadable";
|
|
19204
20258
|
}
|
|
19205
20259
|
for (const name of files) {
|
|
19206
|
-
const text = readText2(
|
|
20260
|
+
const text = readText2(path24.join(dir, name)) ?? "";
|
|
19207
20261
|
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
19208
20262
|
const rule = codexRule(part);
|
|
19209
20263
|
if (!rule)
|
|
@@ -19222,8 +20276,8 @@ function cursorRules(cwd, env = process.env) {
|
|
|
19222
20276
|
try {
|
|
19223
20277
|
const top = gitTop(cwd) ?? cwd;
|
|
19224
20278
|
for (const file of [
|
|
19225
|
-
|
|
19226
|
-
|
|
20279
|
+
path24.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
20280
|
+
path24.join(top, ".cursor", "cli.json")
|
|
19227
20281
|
]) {
|
|
19228
20282
|
const text = readText2(file);
|
|
19229
20283
|
if (text === null)
|
|
@@ -19299,21 +20353,21 @@ function wrapAllowed(read, words, command, options = {}) {
|
|
|
19299
20353
|
// packages/workit-core/src/core/heavy.ts
|
|
19300
20354
|
import fs19 from "node:fs";
|
|
19301
20355
|
import os7 from "node:os";
|
|
19302
|
-
import
|
|
20356
|
+
import path25 from "node:path";
|
|
19303
20357
|
function stateDir(env, platform = process.platform) {
|
|
19304
20358
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
19305
20359
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
19306
20360
|
if (env.XDG_STATE_HOME)
|
|
19307
|
-
return
|
|
20361
|
+
return path25.join(env.XDG_STATE_HOME, "workit");
|
|
19308
20362
|
const home = env.HOME || os7.homedir();
|
|
19309
20363
|
if (platform === "darwin")
|
|
19310
|
-
return
|
|
20364
|
+
return path25.join(home, "Library", "Application Support", "workit");
|
|
19311
20365
|
if (platform === "win32")
|
|
19312
|
-
return
|
|
19313
|
-
return
|
|
20366
|
+
return path25.join(env.LOCALAPPDATA ?? path25.join(home, "AppData", "Local"), "workit");
|
|
20367
|
+
return path25.join(home, ".local", "state", "workit");
|
|
19314
20368
|
}
|
|
19315
20369
|
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
19316
|
-
var heavyCommandsFile = (env) =>
|
|
20370
|
+
var heavyCommandsFile = (env) => path25.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
19317
20371
|
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;
|
|
19318
20372
|
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
19319
20373
|
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
@@ -19588,7 +20642,7 @@ function machinePatterns(env) {
|
|
|
19588
20642
|
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
19589
20643
|
}
|
|
19590
20644
|
function repoPatterns(cwd) {
|
|
19591
|
-
return repoHeavyCommands(gitTop(cwd) ??
|
|
20645
|
+
return repoHeavyCommands(gitTop(cwd) ?? path25.resolve(cwd));
|
|
19592
20646
|
}
|
|
19593
20647
|
function heavyLines(lines, options) {
|
|
19594
20648
|
const found = [];
|
|
@@ -19666,12 +20720,12 @@ function heavyPlan(command, options) {
|
|
|
19666
20720
|
}
|
|
19667
20721
|
function workitOnPath2(env = process.env) {
|
|
19668
20722
|
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
19669
|
-
for (const dir of (env.PATH ?? "").split(
|
|
20723
|
+
for (const dir of (env.PATH ?? "").split(path25.delimiter))
|
|
19670
20724
|
for (const name of names) {
|
|
19671
20725
|
if (!dir)
|
|
19672
20726
|
continue;
|
|
19673
20727
|
try {
|
|
19674
|
-
fs19.accessSync(
|
|
20728
|
+
fs19.accessSync(path25.join(dir, name), fs19.constants.X_OK);
|
|
19675
20729
|
return true;
|
|
19676
20730
|
} catch {}
|
|
19677
20731
|
}
|
|
@@ -19690,20 +20744,20 @@ function leaseRewrite(command) {
|
|
|
19690
20744
|
// packages/workit-core/src/core/heavy-words.ts
|
|
19691
20745
|
import fs20 from "node:fs";
|
|
19692
20746
|
import os8 from "node:os";
|
|
19693
|
-
import
|
|
20747
|
+
import path26 from "node:path";
|
|
19694
20748
|
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(" "));
|
|
19695
20749
|
var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
|
|
19696
20750
|
var stateDir2 = (env) => {
|
|
19697
20751
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
19698
20752
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
19699
20753
|
if (env.XDG_STATE_HOME)
|
|
19700
|
-
return
|
|
20754
|
+
return path26.join(env.XDG_STATE_HOME, "workit");
|
|
19701
20755
|
const home = env.HOME || os8.homedir();
|
|
19702
20756
|
if (process.platform === "darwin")
|
|
19703
|
-
return
|
|
20757
|
+
return path26.join(home, "Library", "Application Support", "workit");
|
|
19704
20758
|
if (process.platform === "win32")
|
|
19705
|
-
return
|
|
19706
|
-
return
|
|
20759
|
+
return path26.join(env.LOCALAPPDATA ?? path26.join(home, "AppData", "Local"), "workit");
|
|
20760
|
+
return path26.join(home, ".local", "state", "workit");
|
|
19707
20761
|
};
|
|
19708
20762
|
var readJson2 = (file) => {
|
|
19709
20763
|
try {
|
|
@@ -19714,15 +20768,15 @@ var readJson2 = (file) => {
|
|
|
19714
20768
|
};
|
|
19715
20769
|
var patternWords = (cwd, env) => {
|
|
19716
20770
|
const patterns = [];
|
|
19717
|
-
const machine = readJson2(
|
|
20771
|
+
const machine = readJson2(path26.join(stateDir2(env), "heavy-commands.json"));
|
|
19718
20772
|
if (Array.isArray(machine?.patterns))
|
|
19719
20773
|
patterns.push(...machine.patterns);
|
|
19720
|
-
for (let dir =
|
|
19721
|
-
if (fs20.existsSync(
|
|
20774
|
+
for (let dir = path26.resolve(cwd);; dir = path26.dirname(dir)) {
|
|
20775
|
+
if (fs20.existsSync(path26.join(dir, ".git"))) {
|
|
19722
20776
|
patterns.push(...repoHeavyCommands(dir));
|
|
19723
20777
|
break;
|
|
19724
20778
|
}
|
|
19725
|
-
if (
|
|
20779
|
+
if (path26.dirname(dir) === dir)
|
|
19726
20780
|
break;
|
|
19727
20781
|
}
|
|
19728
20782
|
const words = new Set;
|
|
@@ -19792,16 +20846,16 @@ var splitDenial = (plan) => ({
|
|
|
19792
20846
|
unblock: plan.unblock
|
|
19793
20847
|
});
|
|
19794
20848
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
19795
|
-
import { existsSync as
|
|
20849
|
+
import { existsSync as existsSync8, realpathSync as realpathSync5 } from "node:fs";
|
|
19796
20850
|
import os10 from "node:os";
|
|
19797
|
-
import
|
|
20851
|
+
import path28 from "node:path";
|
|
19798
20852
|
|
|
19799
20853
|
// packages/workit-core/src/core/live-session.ts
|
|
19800
20854
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
19801
20855
|
import { createHash as createHash16 } from "node:crypto";
|
|
19802
20856
|
import fs21 from "node:fs";
|
|
19803
20857
|
import os9 from "node:os";
|
|
19804
|
-
import
|
|
20858
|
+
import path27 from "node:path";
|
|
19805
20859
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
19806
20860
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
19807
20861
|
var liveProbe = {
|
|
@@ -19875,15 +20929,15 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
19875
20929
|
};
|
|
19876
20930
|
};
|
|
19877
20931
|
var checkoutOf = (target) => {
|
|
19878
|
-
let dir =
|
|
19879
|
-
while (!fs21.existsSync(dir) &&
|
|
19880
|
-
dir =
|
|
20932
|
+
let dir = path27.resolve(target);
|
|
20933
|
+
while (!fs21.existsSync(dir) && path27.dirname(dir) !== dir)
|
|
20934
|
+
dir = path27.dirname(dir);
|
|
19881
20935
|
if (fs21.existsSync(dir) && !fs21.statSync(dir).isDirectory())
|
|
19882
|
-
dir =
|
|
19883
|
-
const parts = dir.split(
|
|
20936
|
+
dir = path27.dirname(dir);
|
|
20937
|
+
const parts = dir.split(path27.sep);
|
|
19884
20938
|
const dotGit = parts.lastIndexOf(".git");
|
|
19885
20939
|
if (dotGit > 0)
|
|
19886
|
-
dir = parts.slice(0, dotGit).join(
|
|
20940
|
+
dir = parts.slice(0, dotGit).join(path27.sep) || path27.sep;
|
|
19887
20941
|
const location = resolveStore(dir);
|
|
19888
20942
|
if (location instanceof Error || !location.shared || !location.top)
|
|
19889
20943
|
return null;
|
|
@@ -19893,9 +20947,9 @@ var checkoutOf = (target) => {
|
|
|
19893
20947
|
} catch {}
|
|
19894
20948
|
return top;
|
|
19895
20949
|
};
|
|
19896
|
-
var markerFile2 = (top) =>
|
|
20950
|
+
var markerFile2 = (top) => path27.join(resolveStateDir(), "live-sessions", `${createHash16("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
19897
20951
|
var checkoutKey = (top, platform = process.platform) => {
|
|
19898
|
-
let key =
|
|
20952
|
+
let key = path27.resolve(top);
|
|
19899
20953
|
try {
|
|
19900
20954
|
key = fs21.realpathSync.native(key);
|
|
19901
20955
|
} catch {}
|
|
@@ -19990,7 +21044,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
19990
21044
|
checkout,
|
|
19991
21045
|
heartbeatAt: now.toISOString()
|
|
19992
21046
|
};
|
|
19993
|
-
fs21.mkdirSync(
|
|
21047
|
+
fs21.mkdirSync(path27.dirname(file), { recursive: true });
|
|
19994
21048
|
const temp = `${file}.${process.pid}.tmp`;
|
|
19995
21049
|
fs21.writeFileSync(temp, JSON.stringify(marker));
|
|
19996
21050
|
fs21.renameSync(temp, file);
|
|
@@ -20002,22 +21056,22 @@ var NONE3 = { kind: "none" };
|
|
|
20002
21056
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
20003
21057
|
var canonical2 = (file) => {
|
|
20004
21058
|
let rest = "";
|
|
20005
|
-
for (let dir =
|
|
21059
|
+
for (let dir = path28.resolve(file);; dir = path28.dirname(dir)) {
|
|
20006
21060
|
try {
|
|
20007
|
-
return
|
|
21061
|
+
return path28.join(realpathSync5.native(dir), rest);
|
|
20008
21062
|
} catch {
|
|
20009
|
-
if (
|
|
20010
|
-
return
|
|
20011
|
-
rest = rest ?
|
|
21063
|
+
if (path28.dirname(dir) === dir)
|
|
21064
|
+
return path28.resolve(file);
|
|
21065
|
+
rest = rest ? path28.join(path28.basename(dir), rest) : path28.basename(dir);
|
|
20012
21066
|
}
|
|
20013
21067
|
}
|
|
20014
21068
|
};
|
|
20015
21069
|
var exempt = (cwd, root, file, plans) => {
|
|
20016
21070
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
20017
|
-
const relative =
|
|
20018
|
-
if (relative.startsWith("..") ||
|
|
21071
|
+
const relative = path28.relative(fold(canonical2(root)), fold(canonical2(path28.resolve(cwd, file))));
|
|
21072
|
+
if (relative.startsWith("..") || path28.isAbsolute(relative))
|
|
20019
21073
|
return true;
|
|
20020
|
-
const posix = relative.split(
|
|
21074
|
+
const posix = relative.split(path28.sep).join("/");
|
|
20021
21075
|
const segments = posix.split("/");
|
|
20022
21076
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
20023
21077
|
};
|
|
@@ -20194,10 +21248,10 @@ var linkSources = (args, at) => {
|
|
|
20194
21248
|
const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
|
|
20195
21249
|
const target = dest === undefined ? null : at(dest);
|
|
20196
21250
|
const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
|
|
20197
|
-
const base = target === null ? null : into >= 0 || sources.length > 1 ||
|
|
21251
|
+
const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync8(target) || /[\\/]$/.test(dest ?? "") ? target : path28.dirname(target);
|
|
20198
21252
|
return sources.flatMap((source) => {
|
|
20199
|
-
if (symbolic && base !== null && !
|
|
20200
|
-
return [
|
|
21253
|
+
if (symbolic && base !== null && !path28.isAbsolute(homeExpanded(source)))
|
|
21254
|
+
return [path28.resolve(base, source)];
|
|
20201
21255
|
const resolved = at(source);
|
|
20202
21256
|
return resolved ? [resolved] : [];
|
|
20203
21257
|
});
|
|
@@ -20226,7 +21280,7 @@ var shellWrites = (command, depth = 0) => {
|
|
|
20226
21280
|
return { writes: true, targets: unknown ? null : targets };
|
|
20227
21281
|
};
|
|
20228
21282
|
var cache2 = new Map;
|
|
20229
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
21283
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path28.resolve(root, file)) ?? "-").join("\x00");
|
|
20230
21284
|
var gateFor = (cwd) => {
|
|
20231
21285
|
const store = new TaskStore(cwd);
|
|
20232
21286
|
const listed = store.listTaskIndex();
|
|
@@ -20305,13 +21359,13 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
20305
21359
|
return [];
|
|
20306
21360
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
20307
21361
|
return [];
|
|
20308
|
-
return [
|
|
21362
|
+
return [path28.resolve(cwd, invocation.dir ?? ".")];
|
|
20309
21363
|
});
|
|
20310
21364
|
};
|
|
20311
21365
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
20312
21366
|
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
20313
21367
|
var POPD2 = new Set(["popd", "pop-location"]);
|
|
20314
|
-
var UNKNOWN_DIR =
|
|
21368
|
+
var UNKNOWN_DIR = path28.resolve(path28.parse(process.cwd()).root, ".workit-unknown-dir");
|
|
20315
21369
|
var cdInto = (dir, words, strict = false) => {
|
|
20316
21370
|
const lost = strict ? UNKNOWN_DIR : undefined;
|
|
20317
21371
|
const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
@@ -20320,7 +21374,7 @@ var cdInto = (dir, words, strict = false) => {
|
|
|
20320
21374
|
return lost;
|
|
20321
21375
|
if (dir === undefined)
|
|
20322
21376
|
return lost;
|
|
20323
|
-
return target === undefined ? os10.homedir() :
|
|
21377
|
+
return target === undefined ? os10.homedir() : path28.resolve(dir, target);
|
|
20324
21378
|
};
|
|
20325
21379
|
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
20326
21380
|
var homeExpanded = (file) => {
|
|
@@ -20334,7 +21388,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20334
21388
|
const groups = [];
|
|
20335
21389
|
const at = (file) => {
|
|
20336
21390
|
const home = homeExpanded(file);
|
|
20337
|
-
return
|
|
21391
|
+
return path28.isAbsolute(home) ? home : dir === undefined ? null : path28.resolve(dir, home);
|
|
20338
21392
|
};
|
|
20339
21393
|
for (const segment of segmentsOf(command, dialect)) {
|
|
20340
21394
|
if (segment.group === "open") {
|
|
@@ -20365,7 +21419,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20365
21419
|
}
|
|
20366
21420
|
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
20367
21421
|
if (inner !== null && dir !== undefined)
|
|
20368
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
21422
|
+
targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
|
|
20369
21423
|
if (strict && dir !== undefined) {
|
|
20370
21424
|
const runIn = dir;
|
|
20371
21425
|
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
@@ -20374,7 +21428,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20374
21428
|
for (const file of opaque ?? []) {
|
|
20375
21429
|
const resolved = at(file);
|
|
20376
21430
|
if (resolved)
|
|
20377
|
-
targets.push(file === "." ?
|
|
21431
|
+
targets.push(file === "." ? path28.join(resolved, DIR_TARGET) : resolved);
|
|
20378
21432
|
}
|
|
20379
21433
|
const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
|
|
20380
21434
|
if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
|
|
@@ -20389,7 +21443,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20389
21443
|
const edits = editsOf(segment.words);
|
|
20390
21444
|
if (edits === null) {
|
|
20391
21445
|
if (dir !== undefined)
|
|
20392
|
-
targets.push(
|
|
21446
|
+
targets.push(path28.join(dir, DIR_TARGET));
|
|
20393
21447
|
} else
|
|
20394
21448
|
for (const file of edits) {
|
|
20395
21449
|
const resolved = at(file);
|
|
@@ -20400,20 +21454,28 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20400
21454
|
for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
|
|
20401
21455
|
const { at: runsIn, args } = workitCwd(dir, invocation);
|
|
20402
21456
|
if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
|
|
20403
|
-
targets.push(
|
|
21457
|
+
targets.push(path28.join(runsIn, DIR_TARGET));
|
|
20404
21458
|
}
|
|
20405
21459
|
}
|
|
20406
21460
|
return targets;
|
|
20407
21461
|
};
|
|
21462
|
+
var workitRunDir = (dir, words) => {
|
|
21463
|
+
for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
|
|
21464
|
+
const { at, args } = workitCwd(dir, invocation);
|
|
21465
|
+
if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
|
|
21466
|
+
return at;
|
|
21467
|
+
}
|
|
21468
|
+
return dir;
|
|
21469
|
+
};
|
|
20408
21470
|
var workitCwd = (dir, args) => {
|
|
20409
21471
|
const { rest, cwd } = skipWorkitGlobals(args);
|
|
20410
|
-
return { at: cwd === null ? dir :
|
|
21472
|
+
return { at: cwd === null ? dir : path28.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
20411
21473
|
};
|
|
20412
21474
|
var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
|
|
20413
21475
|
try {
|
|
20414
21476
|
return [
|
|
20415
21477
|
...segmentTargets(cwd, command, dialect, 0, strict),
|
|
20416
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
21478
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path28.join(dir, DIR_TARGET))
|
|
20417
21479
|
];
|
|
20418
21480
|
} catch {
|
|
20419
21481
|
return [];
|
|
@@ -20450,7 +21512,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
20450
21512
|
var guarded = (targets) => {
|
|
20451
21513
|
const seen = new Set;
|
|
20452
21514
|
return targets.filter((target) => {
|
|
20453
|
-
const dir =
|
|
21515
|
+
const dir = path28.dirname(target);
|
|
20454
21516
|
if (seen.has(dir))
|
|
20455
21517
|
return false;
|
|
20456
21518
|
seen.add(dir);
|
|
@@ -20471,7 +21533,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
20471
21533
|
for (const target of guarded(targets))
|
|
20472
21534
|
claimLiveSession(target, owner, deps);
|
|
20473
21535
|
};
|
|
20474
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
21536
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path28.resolve(cwd, file)) : [path28.join(cwd, DIR_TARGET)];
|
|
20475
21537
|
var issuedTarget = (cwd, targets) => {
|
|
20476
21538
|
const state = resolveStateDir();
|
|
20477
21539
|
const found = targets.find((target) => underIssued(state, cwd, homeExpanded(target), process.platform, canonical2));
|
|
@@ -20479,7 +21541,7 @@ var issuedTarget = (cwd, targets) => {
|
|
|
20479
21541
|
};
|
|
20480
21542
|
var underIssued = (state, cwd, target, platform = process.platform, real = (file) => file) => {
|
|
20481
21543
|
const win = platform === "win32";
|
|
20482
|
-
const p = win ?
|
|
21544
|
+
const p = win ? path28.win32 : path28.posix;
|
|
20483
21545
|
const fold = (value) => win || platform === "darwin" ? value.toLowerCase() : value;
|
|
20484
21546
|
const named = win ? target.replace(/^\/([a-zA-Z])(?=\/|$)/, "$1:") : target;
|
|
20485
21547
|
const relative = p.relative(fold(real(p.resolve(state))), fold(real(p.resolve(cwd, named))));
|
|
@@ -20570,6 +21632,24 @@ function beforeWriteGate(cwd, paths) {
|
|
|
20570
21632
|
}
|
|
20571
21633
|
// packages/workit-core/src/hooks/stop.ts
|
|
20572
21634
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
21635
|
+
|
|
21636
|
+
// packages/workit-core/src/core/policy/current-judgment.ts
|
|
21637
|
+
function currentJudgment(cwd) {
|
|
21638
|
+
try {
|
|
21639
|
+
const store = new TaskStore(cwd);
|
|
21640
|
+
const listed = store.listTaskIndex();
|
|
21641
|
+
const key = store.currentKey();
|
|
21642
|
+
if (!listed.ok || !key.ok)
|
|
21643
|
+
return null;
|
|
21644
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
21645
|
+
const task = entry ? store.readTask(entry.id) : null;
|
|
21646
|
+
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
21647
|
+
} catch {
|
|
21648
|
+
return null;
|
|
21649
|
+
}
|
|
21650
|
+
}
|
|
21651
|
+
|
|
21652
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
20573
21653
|
var NONE4 = { kind: "none" };
|
|
20574
21654
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
20575
21655
|
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;
|
|
@@ -20662,29 +21742,32 @@ var redChecks = (rows, pr, head, endpoint) => {
|
|
|
20662
21742
|
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)" : ""}.`
|
|
20663
21743
|
};
|
|
20664
21744
|
};
|
|
20665
|
-
var
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
const listed = store.listTaskIndex();
|
|
20669
|
-
const key = store.currentKey();
|
|
20670
|
-
if (!listed.ok || !key.ok)
|
|
20671
|
-
return null;
|
|
20672
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
20673
|
-
const task = entry ? store.readTask(entry.id) : null;
|
|
20674
|
-
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
20675
|
-
} catch {
|
|
20676
|
-
return null;
|
|
20677
|
-
}
|
|
21745
|
+
var prBase = (rows) => {
|
|
21746
|
+
const seen = rows.findLast((row) => (row.type === "pr.status" || row.type === "pr.created") && row.observer === "workit_cli" && typeof row.base === "string");
|
|
21747
|
+
return typeof seen?.base === "string" ? seen.base : null;
|
|
20678
21748
|
};
|
|
20679
|
-
var unverified = (cwd, branch, all, descriptor, verification) => {
|
|
21749
|
+
var unverified = (cwd, branch, all, descriptor, verification, head) => {
|
|
20680
21750
|
const check = checkVerdicts(cwd, branch, all);
|
|
20681
21751
|
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
20682
21752
|
return null;
|
|
20683
|
-
const judgment =
|
|
21753
|
+
const judgment = currentJudgment(cwd);
|
|
20684
21754
|
const high = judgment?.riskTier === "high";
|
|
20685
|
-
|
|
20686
|
-
|
|
20687
|
-
|
|
21755
|
+
let independent;
|
|
21756
|
+
if (verification === "proportional") {
|
|
21757
|
+
const lane = branchLane(cwd, {
|
|
21758
|
+
base: prBase(all.filter((row) => row.branch === branch)),
|
|
21759
|
+
head,
|
|
21760
|
+
judgment
|
|
21761
|
+
});
|
|
21762
|
+
const need = laneNeed(lane.lane);
|
|
21763
|
+
if (need === "none")
|
|
21764
|
+
return null;
|
|
21765
|
+
independent = need === "non-author";
|
|
21766
|
+
} else {
|
|
21767
|
+
if (judgment && !judgment.behaviorChange && !high)
|
|
21768
|
+
return null;
|
|
21769
|
+
independent = verification === "independent" || high;
|
|
21770
|
+
}
|
|
20688
21771
|
if (!independent && check.review === "self-reviewed")
|
|
20689
21772
|
return null;
|
|
20690
21773
|
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
@@ -20731,7 +21814,7 @@ function stopObligation(input, descriptor) {
|
|
|
20731
21814
|
}
|
|
20732
21815
|
if (!delivered || endpoint !== "green" && endpoint !== "merged")
|
|
20733
21816
|
return null;
|
|
20734
|
-
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification);
|
|
21817
|
+
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification, head);
|
|
20735
21818
|
return left ? { ...left, head } : null;
|
|
20736
21819
|
} catch {
|
|
20737
21820
|
return null;
|
|
@@ -20992,13 +22075,13 @@ ${line}` };
|
|
|
20992
22075
|
return decision;
|
|
20993
22076
|
};
|
|
20994
22077
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20995
|
-
import { existsSync as
|
|
20996
|
-
import
|
|
22078
|
+
import { existsSync as existsSync9, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
22079
|
+
import path29 from "node:path";
|
|
20997
22080
|
var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20998
22081
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
20999
22082
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
21000
22083
|
var existingDirectory = (value) => {
|
|
21001
|
-
if (!nonEmpty2(value) || !
|
|
22084
|
+
if (!nonEmpty2(value) || !path29.isAbsolute(value) || !existsSync9(value))
|
|
21002
22085
|
return null;
|
|
21003
22086
|
try {
|
|
21004
22087
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -21066,9 +22149,9 @@ var writeTargets = (input) => {
|
|
|
21066
22149
|
};
|
|
21067
22150
|
// packages/workit-core/src/hooks/standing.ts
|
|
21068
22151
|
import fs22 from "node:fs";
|
|
21069
|
-
import
|
|
22152
|
+
import path30 from "node:path";
|
|
21070
22153
|
var readPlans = (store) => {
|
|
21071
|
-
const dir =
|
|
22154
|
+
const dir = path30.join(store, "fanouts");
|
|
21072
22155
|
let names;
|
|
21073
22156
|
try {
|
|
21074
22157
|
names = fs22.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
@@ -21078,7 +22161,7 @@ var readPlans = (store) => {
|
|
|
21078
22161
|
const plans = [];
|
|
21079
22162
|
for (const name of names) {
|
|
21080
22163
|
try {
|
|
21081
|
-
const raw = JSON.parse(fs22.readFileSync(
|
|
22164
|
+
const raw = JSON.parse(fs22.readFileSync(path30.join(dir, name), "utf8"));
|
|
21082
22165
|
if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
|
|
21083
22166
|
continue;
|
|
21084
22167
|
const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
@@ -21168,8 +22251,13 @@ var standingDelta = (input) => {
|
|
|
21168
22251
|
};
|
|
21169
22252
|
var sinceStart = new Map;
|
|
21170
22253
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
21171
|
-
var
|
|
22254
|
+
var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
22255
|
+
var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
22256
|
+
var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
22257
|
+
var VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
|
|
22258
|
+
var VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
21172
22259
|
var FILTERS = new Set(["tail", "head", "grep"]);
|
|
22260
|
+
var clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
21173
22261
|
var stdinOnly = (words) => {
|
|
21174
22262
|
const [name, ...args] = words;
|
|
21175
22263
|
const operands = args.filter((w) => !w.startsWith("-"));
|
|
@@ -21191,53 +22279,67 @@ var cwdRewrite = (command) => {
|
|
|
21191
22279
|
return null;
|
|
21192
22280
|
if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
|
|
21193
22281
|
return null;
|
|
21194
|
-
const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
21195
22282
|
if (!segments.every(clean))
|
|
21196
22283
|
return null;
|
|
21197
22284
|
if (!segments.slice(0, -1).every((s) => s.pipe))
|
|
21198
22285
|
return null;
|
|
21199
22286
|
if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
|
|
21200
22287
|
return null;
|
|
21201
|
-
|
|
22288
|
+
const args = rest.slice("workit".length).trimStart();
|
|
22289
|
+
const verb = VERB_FIRST.exec(args);
|
|
22290
|
+
if (!verb?.[2] || !VERBS.has(verb[2]))
|
|
22291
|
+
return `workit --cwd ${dir} ${args}`.trimEnd();
|
|
22292
|
+
const json = verb[1] ? " --json" : "";
|
|
22293
|
+
return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
|
|
22294
|
+
};
|
|
22295
|
+
var verbFirst = (command) => {
|
|
22296
|
+
const match = GLOBALS_FIRST.exec(command);
|
|
22297
|
+
const [whole, lead, globals, verb] = match ?? [];
|
|
22298
|
+
if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
|
|
22299
|
+
return null;
|
|
22300
|
+
const segments = segmentsOf(command);
|
|
22301
|
+
if (segments.length !== 1 || !segments.every(clean))
|
|
22302
|
+
return null;
|
|
22303
|
+
return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
|
|
21202
22304
|
};
|
|
21203
22305
|
|
|
21204
22306
|
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
21205
22307
|
import { createHash as createHash17 } from "node:crypto";
|
|
21206
22308
|
import fs23 from "node:fs";
|
|
21207
|
-
import
|
|
22309
|
+
import path31 from "node:path";
|
|
21208
22310
|
var FANOUT_TOKEN_FILE = "workit-fanout-token";
|
|
21209
22311
|
var NONE5 = { kind: "none" };
|
|
21210
22312
|
var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
|
|
21211
22313
|
var canonical3 = (file) => {
|
|
21212
22314
|
let rest = "";
|
|
21213
|
-
for (let dir =
|
|
22315
|
+
for (let dir = path31.resolve(file);; dir = path31.dirname(dir)) {
|
|
21214
22316
|
try {
|
|
21215
|
-
return
|
|
22317
|
+
return path31.join(fs23.realpathSync.native(dir), rest);
|
|
21216
22318
|
} catch {
|
|
21217
|
-
if (
|
|
21218
|
-
return
|
|
21219
|
-
rest = rest ?
|
|
22319
|
+
if (path31.dirname(dir) === dir)
|
|
22320
|
+
return path31.resolve(file);
|
|
22321
|
+
rest = rest ? path31.join(path31.basename(dir), rest) : path31.basename(dir);
|
|
21220
22322
|
}
|
|
21221
22323
|
}
|
|
21222
22324
|
};
|
|
21223
22325
|
var within = (child, parent) => {
|
|
21224
22326
|
const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
|
|
21225
|
-
const relative =
|
|
21226
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
22327
|
+
const relative = path31.relative(fold(parent), fold(child));
|
|
22328
|
+
return relative === "" || !relative.startsWith("..") && !path31.isAbsolute(relative);
|
|
21227
22329
|
};
|
|
21228
22330
|
var checkoutOf2 = (file) => {
|
|
21229
|
-
for (let dir = canonical3(file);; dir =
|
|
21230
|
-
const dotGit =
|
|
22331
|
+
for (let dir = canonical3(file);; dir = path31.dirname(dir)) {
|
|
22332
|
+
const dotGit = path31.join(dir, ".git");
|
|
21231
22333
|
if (fs23.existsSync(dotGit)) {
|
|
21232
22334
|
if (!fs23.statSync(dotGit).isFile())
|
|
21233
22335
|
return { root: dir, workit: false };
|
|
21234
22336
|
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
21235
22337
|
return {
|
|
21236
22338
|
root: dir,
|
|
21237
|
-
workit: Boolean(admin) && fs23.existsSync(
|
|
22339
|
+
workit: Boolean(admin) && fs23.existsSync(path31.join(path31.resolve(dir, admin), FANOUT_TOKEN_FILE))
|
|
21238
22340
|
};
|
|
21239
22341
|
}
|
|
21240
|
-
if (
|
|
22342
|
+
if (path31.dirname(dir) === dir)
|
|
21241
22343
|
return null;
|
|
21242
22344
|
}
|
|
21243
22345
|
};
|
|
@@ -21249,8 +22351,8 @@ var workitWorktreeOf = (file) => {
|
|
|
21249
22351
|
return null;
|
|
21250
22352
|
}
|
|
21251
22353
|
};
|
|
21252
|
-
var bindingDir = () =>
|
|
21253
|
-
var bindingFile = (session, agentId) =>
|
|
22354
|
+
var bindingDir = () => path31.join(resolveStateDir(), "worktree-bind");
|
|
22355
|
+
var bindingFile = (session, agentId) => path31.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
|
|
21254
22356
|
var readBinding = (file) => {
|
|
21255
22357
|
try {
|
|
21256
22358
|
const value = JSON.parse(fs23.readFileSync(file, "utf8"));
|
|
@@ -21275,7 +22377,7 @@ var bind = (input, worktree) => {
|
|
|
21275
22377
|
agentId,
|
|
21276
22378
|
at: new Date().toISOString()
|
|
21277
22379
|
};
|
|
21278
|
-
fs23.mkdirSync(
|
|
22380
|
+
fs23.mkdirSync(path31.dirname(file), { recursive: true });
|
|
21279
22381
|
fs23.writeFileSync(file, JSON.stringify(binding));
|
|
21280
22382
|
try {
|
|
21281
22383
|
if (inWorkitWorkspace(input.cwd))
|
|
@@ -21294,27 +22396,27 @@ var hostIsolated = (cwd, agentId) => {
|
|
|
21294
22396
|
const checkout = checkoutOf2(cwd);
|
|
21295
22397
|
if (!checkout)
|
|
21296
22398
|
return false;
|
|
21297
|
-
const dotGit =
|
|
22399
|
+
const dotGit = path31.join(checkout.root, ".git");
|
|
21298
22400
|
if (!fs23.statSync(dotGit).isFile())
|
|
21299
22401
|
return false;
|
|
21300
22402
|
const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
21301
22403
|
if (!admin)
|
|
21302
22404
|
return false;
|
|
21303
|
-
const adminDir =
|
|
21304
|
-
const back = fs23.readFileSync(
|
|
21305
|
-
if (
|
|
22405
|
+
const adminDir = path31.resolve(checkout.root, admin);
|
|
22406
|
+
const back = fs23.readFileSync(path31.join(adminDir, "gitdir"), "utf8").trim();
|
|
22407
|
+
if (path31.relative(canonical3(path31.resolve(adminDir, back)), canonical3(dotGit)) !== "")
|
|
21306
22408
|
return false;
|
|
21307
|
-
const common =
|
|
21308
|
-
const gitDir = fs23.existsSync(common) ?
|
|
21309
|
-
const expected =
|
|
21310
|
-
return
|
|
22409
|
+
const common = path31.join(adminDir, "commondir");
|
|
22410
|
+
const gitDir = fs23.existsSync(common) ? path31.resolve(adminDir, fs23.readFileSync(common, "utf8").trim()) : adminDir;
|
|
22411
|
+
const expected = path31.join(path31.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
|
|
22412
|
+
return path31.relative(canonical3(expected), checkout.root) === "";
|
|
21311
22413
|
} catch {
|
|
21312
22414
|
return false;
|
|
21313
22415
|
}
|
|
21314
22416
|
};
|
|
21315
|
-
var shown = (target) =>
|
|
22417
|
+
var shown = (target) => path31.basename(target) === ".workit-live-target" ? path31.dirname(target) : target;
|
|
21316
22418
|
var unboundDeny = (input, target) => {
|
|
21317
|
-
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>
|
|
22419
|
+
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 git --cwd <worktree> commit -m … -- <paths>`, `workit check --cwd <worktree> <name>`). If your brief names no worktree, stop and report that to the lead.";
|
|
21318
22420
|
return {
|
|
21319
22421
|
kind: "deny",
|
|
21320
22422
|
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}`,
|
|
@@ -21322,7 +22424,7 @@ var unboundDeny = (input, target) => {
|
|
|
21322
22424
|
};
|
|
21323
22425
|
};
|
|
21324
22426
|
var outsideDeny = (input, worktree, target) => {
|
|
21325
|
-
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree}
|
|
22427
|
+
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit git --cwd ${worktree} commit -m … -- <paths>\`, \`workit check --cwd ${worktree} <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.`;
|
|
21326
22428
|
return {
|
|
21327
22429
|
kind: "deny",
|
|
21328
22430
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
@@ -21331,7 +22433,7 @@ var outsideDeny = (input, worktree, target) => {
|
|
|
21331
22433
|
};
|
|
21332
22434
|
var unknownDirDeny = (input, worktree) => {
|
|
21333
22435
|
const wt = worktree ?? "<worktree>";
|
|
21334
|
-
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
22436
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
|
|
21335
22437
|
return {
|
|
21336
22438
|
kind: "deny",
|
|
21337
22439
|
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
@@ -21342,7 +22444,7 @@ function confineImplementer(input, targets) {
|
|
|
21342
22444
|
try {
|
|
21343
22445
|
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
21344
22446
|
return NONE5;
|
|
21345
|
-
if (targets.some((target) => within(
|
|
22447
|
+
if (targets.some((target) => within(path31.resolve(target), UNKNOWN_DIR)))
|
|
21346
22448
|
return unknownDirDeny(input, boundWorktree(input));
|
|
21347
22449
|
const bindings = canonical3(bindingDir());
|
|
21348
22450
|
const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
|
|
@@ -21489,7 +22591,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
21489
22591
|
return `${isolatedGit(input.cwd)}
|
|
21490
22592
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
21491
22593
|
if (role === "implementer")
|
|
21492
|
-
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>
|
|
22594
|
+
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 git --cwd <worktree> commit -m "<message>" -- <paths>\`, \`workit check --cwd <worktree> <name>\` (verb first, so host allow rules match). The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit git --cwd <worktree> 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.`;
|
|
21493
22595
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
21494
22596
|
if (role !== "judge" || !event.agentId)
|
|
21495
22597
|
return readOnly;
|
|
@@ -21506,7 +22608,7 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
21506
22608
|
var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
|
|
21507
22609
|
function handleHook(input, deps) {
|
|
21508
22610
|
const decision = decide(input, deps);
|
|
21509
|
-
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
|
|
22611
|
+
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
|
|
21510
22612
|
return decision;
|
|
21511
22613
|
const delta = standingDelta(input);
|
|
21512
22614
|
if (!delta)
|
|
@@ -21573,9 +22675,12 @@ function decide(input, deps) {
|
|
|
21573
22675
|
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
21574
22676
|
return heavyDenial(event.command, plan);
|
|
21575
22677
|
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
21576
|
-
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
|
|
22678
|
+
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
|
|
21577
22679
|
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
21578
22680
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
22681
|
+
const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
|
|
22682
|
+
if (bypass)
|
|
22683
|
+
return { kind: "ask", reason: bypass };
|
|
21579
22684
|
const rewrittenTo = lease?.command ?? cwdCommand;
|
|
21580
22685
|
const rewritten = (text) => rewrittenTo ? {
|
|
21581
22686
|
kind: "rewrite",
|
|
@@ -21662,7 +22767,7 @@ var dispatchHook = (adapter, raw, env = process.env) => {
|
|
|
21662
22767
|
};
|
|
21663
22768
|
// packages/workit-core/src/hooks/deadline.ts
|
|
21664
22769
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
21665
|
-
import
|
|
22770
|
+
import path32 from "node:path";
|
|
21666
22771
|
var HOOK_SKIP_TEXT = "workit context skipped: host under load";
|
|
21667
22772
|
var HOOK_DEADLINE_MS = 8000;
|
|
21668
22773
|
var HOST_DEADLINES = {
|
|
@@ -21680,7 +22785,7 @@ var hookDeadlineMs = (env, host = "", native) => {
|
|
|
21680
22785
|
const row = HOST_DEADLINES[host];
|
|
21681
22786
|
return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
|
|
21682
22787
|
};
|
|
21683
|
-
var skipsFile = (stateDir) =>
|
|
22788
|
+
var skipsFile = (stateDir) => path32.join(stateDir, HOOK_SKIPS_FILE);
|
|
21684
22789
|
var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
|
|
21685
22790
|
try {
|
|
21686
22791
|
mkdirSync3(stateDir, { recursive: true });
|
|
@@ -22002,6 +23107,17 @@ var render = (decision, native) => {
|
|
|
22002
23107
|
},
|
|
22003
23108
|
exitCode: 0
|
|
22004
23109
|
};
|
|
23110
|
+
if (decision.kind === "ask" && native === "PreToolUse")
|
|
23111
|
+
return {
|
|
23112
|
+
json: {
|
|
23113
|
+
hookSpecificOutput: {
|
|
23114
|
+
hookEventName: "PreToolUse",
|
|
23115
|
+
permissionDecision: "ask",
|
|
23116
|
+
permissionDecisionReason: decision.reason
|
|
23117
|
+
}
|
|
23118
|
+
},
|
|
23119
|
+
exitCode: 0
|
|
23120
|
+
};
|
|
22005
23121
|
if (decision.kind === "context" && native !== null && CONTEXT_EVENTS.has(native))
|
|
22006
23122
|
return {
|
|
22007
23123
|
json: { hookSpecificOutput: { hookEventName: native, additionalContext: decision.text } },
|
|
@@ -22325,7 +23441,7 @@ var codexAdapter = {
|
|
|
22325
23441
|
|
|
22326
23442
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
22327
23443
|
import fs24 from "node:fs";
|
|
22328
|
-
import
|
|
23444
|
+
import path33 from "node:path";
|
|
22329
23445
|
var undocumented = { support: "undocumented", native: null };
|
|
22330
23446
|
var none = { support: "none", native: null };
|
|
22331
23447
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -22522,7 +23638,7 @@ var protocolEvent2 = (input) => {
|
|
|
22522
23638
|
};
|
|
22523
23639
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
22524
23640
|
var lastAssistantText = (file) => {
|
|
22525
|
-
if (!file || !
|
|
23641
|
+
if (!file || !path33.isAbsolute(file))
|
|
22526
23642
|
return null;
|
|
22527
23643
|
try {
|
|
22528
23644
|
const size = fs24.statSync(file).size;
|
|
@@ -22559,7 +23675,7 @@ var lastAssistantText = (file) => {
|
|
|
22559
23675
|
return null;
|
|
22560
23676
|
}
|
|
22561
23677
|
};
|
|
22562
|
-
var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
23678
|
+
var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path33.sep) ? root : `${root}${path33.sep}`);
|
|
22563
23679
|
var hookCwd = (input) => {
|
|
22564
23680
|
const cwd = existingDirectory(input.cwd);
|
|
22565
23681
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|