@brainervirus/workit-mcp 8.24.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/dist/index.js +1625 -1491
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -17775,6 +17775,26 @@ function resolveRef(cwd, ref) {
|
|
|
17775
17775
|
return null;
|
|
17776
17776
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
17777
17777
|
}
|
|
17778
|
+
function originHeadRef(cwd) {
|
|
17779
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
17780
|
+
}
|
|
17781
|
+
function ownChangeDrift(cwd, range) {
|
|
17782
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
17783
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
17784
|
+
if (!before || !after)
|
|
17785
|
+
return null;
|
|
17786
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
17787
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
17788
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
17789
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
17790
|
+
}
|
|
17791
|
+
function sameOwnChange(cwd, range) {
|
|
17792
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
17793
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
17794
|
+
if (!fromFork || !toFork)
|
|
17795
|
+
return false;
|
|
17796
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
17797
|
+
}
|
|
17778
17798
|
var LANE_DIFF = [
|
|
17779
17799
|
"-c",
|
|
17780
17800
|
"core.quotePath=false",
|
|
@@ -21401,9 +21421,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
21401
21421
|
|
|
21402
21422
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
21403
21423
|
import { createHash as createHash10 } from "node:crypto";
|
|
21404
|
-
import * as
|
|
21405
|
-
import { spawnSync as
|
|
21406
|
-
import
|
|
21424
|
+
import * as fs13 from "node:fs";
|
|
21425
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
21426
|
+
import path17 from "node:path";
|
|
21407
21427
|
|
|
21408
21428
|
// packages/workit-core/src/check-config.ts
|
|
21409
21429
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
@@ -21624,68 +21644,20 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
21624
21644
|
|
|
21625
21645
|
// packages/workit-core/src/core/policy/derive.ts
|
|
21626
21646
|
import { existsSync as existsSync5 } from "node:fs";
|
|
21627
|
-
import
|
|
21647
|
+
import path16 from "node:path";
|
|
21628
21648
|
|
|
21629
21649
|
// packages/workit-core/src/ledger.ts
|
|
21630
21650
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
21631
21651
|
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
21632
21652
|
import fs12 from "node:fs";
|
|
21653
|
+
import path15 from "node:path";
|
|
21654
|
+
|
|
21655
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
21656
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
21633
21657
|
import path14 from "node:path";
|
|
21634
|
-
|
|
21635
|
-
|
|
21636
|
-
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
21637
|
-
var FAILING = new Set(["failed", "blocked"]);
|
|
21638
|
-
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
21639
|
-
var PR_ROW_TYPES = new Set([
|
|
21640
|
-
"pr.created",
|
|
21641
|
-
"pr.status",
|
|
21642
|
-
"pr.merged",
|
|
21643
|
-
"pr.linked"
|
|
21644
|
-
]);
|
|
21645
|
-
var err = (code, error, unblock) => ({
|
|
21646
|
-
ok: false,
|
|
21647
|
-
code,
|
|
21648
|
-
error,
|
|
21649
|
-
...unblock ? { unblock } : {}
|
|
21650
|
-
});
|
|
21651
|
-
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
21652
|
-
const run = spawnSync9("git", args, {
|
|
21653
|
-
cwd,
|
|
21654
|
-
encoding: "utf8",
|
|
21655
|
-
timeout: timeoutMs,
|
|
21656
|
-
killSignal: "SIGKILL",
|
|
21657
|
-
windowsHide: true,
|
|
21658
|
-
maxBuffer: 256 * 1024 * 1024,
|
|
21659
|
-
env: {
|
|
21660
|
-
...process.env,
|
|
21661
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
21662
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
21663
|
-
LC_ALL: "C",
|
|
21664
|
-
LANGUAGE: "C"
|
|
21665
|
-
}
|
|
21666
|
-
});
|
|
21667
|
-
return {
|
|
21668
|
-
ok: run.status === 0,
|
|
21669
|
-
stdout: run.stdout ?? "",
|
|
21670
|
-
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
21671
|
-
};
|
|
21672
|
-
};
|
|
21673
|
-
var git4 = (cwd, args) => {
|
|
21674
|
-
const run = gitRun2(cwd, args);
|
|
21675
|
-
const value = run.ok ? run.stdout.trim() : "";
|
|
21676
|
-
return value ? value : null;
|
|
21677
|
-
};
|
|
21658
|
+
|
|
21659
|
+
// packages/workit-core/src/git/default-base.ts
|
|
21678
21660
|
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
21679
|
-
function resolveCommit(cwd, ref) {
|
|
21680
|
-
if (!safeRef(ref))
|
|
21681
|
-
return null;
|
|
21682
|
-
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
21683
|
-
}
|
|
21684
|
-
function branchHead(cwd, branch) {
|
|
21685
|
-
if (!safeRef(branch))
|
|
21686
|
-
return null;
|
|
21687
|
-
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
21688
|
-
}
|
|
21689
21661
|
var trackTrunk = (cwd) => {
|
|
21690
21662
|
try {
|
|
21691
21663
|
const resolved = vcsConfig("resolve", cwd);
|
|
@@ -21701,11 +21673,11 @@ function defaultBase(cwd) {
|
|
|
21701
21673
|
const trunk = trackTrunk(cwd);
|
|
21702
21674
|
if (trunk) {
|
|
21703
21675
|
for (const candidate of [`origin/${trunk}`, trunk])
|
|
21704
|
-
if (
|
|
21676
|
+
if (resolveRef(cwd, candidate))
|
|
21705
21677
|
return candidate;
|
|
21706
21678
|
}
|
|
21707
|
-
const originHead =
|
|
21708
|
-
if (originHead &&
|
|
21679
|
+
const originHead = originHeadRef(cwd);
|
|
21680
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
21709
21681
|
return originHead;
|
|
21710
21682
|
for (const candidate of [
|
|
21711
21683
|
"origin/main",
|
|
@@ -21715,1489 +21687,1611 @@ function defaultBase(cwd) {
|
|
|
21715
21687
|
"origin/develop",
|
|
21716
21688
|
"develop"
|
|
21717
21689
|
])
|
|
21718
|
-
if (
|
|
21690
|
+
if (resolveRef(cwd, candidate))
|
|
21719
21691
|
return candidate;
|
|
21720
21692
|
return null;
|
|
21721
21693
|
}
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
|
|
21725
|
-
|
|
21726
|
-
"diff",
|
|
21727
|
-
"-U3",
|
|
21728
|
-
"--diff-algorithm=myers",
|
|
21729
|
-
"--indent-heuristic",
|
|
21730
|
-
"--no-color",
|
|
21731
|
-
"--no-ext-diff",
|
|
21732
|
-
"--no-textconv",
|
|
21733
|
-
"--no-renames",
|
|
21734
|
-
"--full-index",
|
|
21735
|
-
"--binary",
|
|
21736
|
-
"--src-prefix=a/",
|
|
21737
|
-
"--dst-prefix=b/",
|
|
21738
|
-
`${base}...${head}`,
|
|
21739
|
-
"--"
|
|
21740
|
-
], GIT_TIMEOUTS.worktree);
|
|
21741
|
-
if (!diff.ok || !diff.stdout)
|
|
21694
|
+
|
|
21695
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
21696
|
+
function patternRegex(dir, pattern) {
|
|
21697
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
21742
21698
|
return null;
|
|
21743
|
-
const
|
|
21744
|
-
|
|
21745
|
-
|
|
21746
|
-
|
|
21747
|
-
|
|
21748
|
-
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
|
|
21755
|
-
|
|
21756
|
-
|
|
21757
|
-
|
|
21758
|
-
|
|
21759
|
-
|
|
21760
|
-
|
|
21761
|
-
|
|
21762
|
-
|
|
21763
|
-
}
|
|
21764
|
-
|
|
21765
|
-
|
|
21766
|
-
|
|
21767
|
-
|
|
21768
|
-
4266872130,
|
|
21769
|
-
4283649346,
|
|
21770
|
-
1702057286,
|
|
21771
|
-
1397113167,
|
|
21772
|
-
12805120,
|
|
21773
|
-
16914839,
|
|
21774
|
-
1196443219,
|
|
21775
|
-
198183888
|
|
21776
|
-
]);
|
|
21777
|
-
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
21778
|
-
var onNetworkFs = (dir) => {
|
|
21779
|
-
if (process.platform !== "linux")
|
|
21780
|
-
return false;
|
|
21781
|
-
try {
|
|
21782
|
-
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
21783
|
-
} catch {
|
|
21784
|
-
return false;
|
|
21699
|
+
const anchored = pattern.includes("/");
|
|
21700
|
+
const body = pattern.replace(/^\//u, "");
|
|
21701
|
+
let source = "";
|
|
21702
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
21703
|
+
const char = body[index];
|
|
21704
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
21705
|
+
const slash = body[index + 2] === "/";
|
|
21706
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
21707
|
+
index += slash ? 2 : 1;
|
|
21708
|
+
} else if (char === "*")
|
|
21709
|
+
source += "[^/]*";
|
|
21710
|
+
else if (char === "?")
|
|
21711
|
+
source += "[^/]";
|
|
21712
|
+
else if (char === "[") {
|
|
21713
|
+
const end = body.indexOf("]", index + 2);
|
|
21714
|
+
if (end < 0)
|
|
21715
|
+
return null;
|
|
21716
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
21717
|
+
source += `[${set}]`;
|
|
21718
|
+
index = end;
|
|
21719
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
21720
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
21721
|
+
index += 1;
|
|
21722
|
+
} else
|
|
21723
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
21785
21724
|
}
|
|
21786
|
-
};
|
|
21787
|
-
var LOCK_WAIT_MS = 5000;
|
|
21788
|
-
var LOCK_STALE_MS = 30000;
|
|
21789
|
-
var LOCK_OWNER = "owner";
|
|
21790
|
-
var lockToken = (lock) => {
|
|
21725
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
21791
21726
|
try {
|
|
21792
|
-
return
|
|
21727
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
21793
21728
|
} catch {
|
|
21794
21729
|
return null;
|
|
21795
21730
|
}
|
|
21796
|
-
}
|
|
21797
|
-
|
|
21798
|
-
|
|
21799
|
-
|
|
21800
|
-
|
|
21801
|
-
|
|
21802
|
-
|
|
21803
|
-
|
|
21804
|
-
|
|
21805
|
-
|
|
21806
|
-
|
|
21807
|
-
|
|
21808
|
-
|
|
21809
|
-
|
|
21810
|
-
|
|
21811
|
-
return null;
|
|
21812
|
-
throw error;
|
|
21813
|
-
}
|
|
21814
|
-
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
21815
|
-
fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
|
|
21816
|
-
return token;
|
|
21817
|
-
},
|
|
21818
|
-
release(lock, token) {
|
|
21819
|
-
if (lockToken(lock) !== token)
|
|
21820
|
-
return false;
|
|
21821
|
-
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
21822
|
-
try {
|
|
21823
|
-
fs12.renameSync(lock, tomb);
|
|
21824
|
-
} catch {
|
|
21825
|
-
return false;
|
|
21826
|
-
}
|
|
21827
|
-
if (lockToken(tomb) !== token) {
|
|
21828
|
-
try {
|
|
21829
|
-
fs12.renameSync(tomb, lock);
|
|
21830
|
-
} catch {}
|
|
21831
|
-
return false;
|
|
21832
|
-
}
|
|
21833
|
-
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
21834
|
-
return true;
|
|
21835
|
-
},
|
|
21836
|
-
token: lockToken,
|
|
21837
|
-
reap(lock, observed) {
|
|
21838
|
-
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
21839
|
-
try {
|
|
21840
|
-
fs12.renameSync(lock, tomb);
|
|
21841
|
-
} catch {
|
|
21842
|
-
return false;
|
|
21843
|
-
}
|
|
21844
|
-
if (lockToken(tomb) === observed) {
|
|
21845
|
-
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
21846
|
-
return true;
|
|
21847
|
-
}
|
|
21848
|
-
try {
|
|
21849
|
-
fs12.renameSync(tomb, lock);
|
|
21850
|
-
} catch {
|
|
21851
|
-
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
21731
|
+
}
|
|
21732
|
+
function rulesOf(dir, text) {
|
|
21733
|
+
const out = [];
|
|
21734
|
+
for (const raw of text.split(`
|
|
21735
|
+
`)) {
|
|
21736
|
+
const line = raw.trim();
|
|
21737
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
21738
|
+
continue;
|
|
21739
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
21740
|
+
let generated = null;
|
|
21741
|
+
for (const attribute of attributes) {
|
|
21742
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
21743
|
+
generated = true;
|
|
21744
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
21745
|
+
generated = false;
|
|
21852
21746
|
}
|
|
21853
|
-
|
|
21854
|
-
}
|
|
21855
|
-
};
|
|
21856
|
-
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
21857
|
-
const lock = `${file}.lock`;
|
|
21858
|
-
const deadline = Date.now() + waitMs;
|
|
21859
|
-
let token = null;
|
|
21860
|
-
for (;; ) {
|
|
21861
|
-
token = ledgerLock.acquire(lock);
|
|
21862
|
-
if (token)
|
|
21863
|
-
break;
|
|
21864
|
-
const observed = lockToken(lock);
|
|
21865
|
-
const age = lockAgeMs(lock);
|
|
21866
|
-
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
21747
|
+
if (generated === null)
|
|
21867
21748
|
continue;
|
|
21868
|
-
|
|
21869
|
-
|
|
21870
|
-
|
|
21749
|
+
const regex = patternRegex(dir, pattern);
|
|
21750
|
+
if (regex)
|
|
21751
|
+
out.push({ regex, generated });
|
|
21871
21752
|
}
|
|
21872
|
-
|
|
21873
|
-
|
|
21874
|
-
|
|
21875
|
-
|
|
21753
|
+
return out;
|
|
21754
|
+
}
|
|
21755
|
+
function generatedByAttributes(files, paths) {
|
|
21756
|
+
const rules = [...files].map(([file, text]) => {
|
|
21757
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
21758
|
+
return {
|
|
21759
|
+
depth: dir ? dir.split("/").length : 0,
|
|
21760
|
+
rules: rulesOf(dir, text)
|
|
21761
|
+
};
|
|
21762
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
21763
|
+
const out = new Set;
|
|
21764
|
+
for (const file of paths) {
|
|
21765
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
21766
|
+
if (last?.generated)
|
|
21767
|
+
out.add(file);
|
|
21876
21768
|
}
|
|
21769
|
+
return out;
|
|
21877
21770
|
}
|
|
21878
|
-
|
|
21879
|
-
|
|
21880
|
-
|
|
21881
|
-
var
|
|
21882
|
-
var
|
|
21883
|
-
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
|
|
21889
|
-
|
|
21890
|
-
|
|
21771
|
+
|
|
21772
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
21773
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
21774
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
21775
|
+
var SYNTAXES = Object.fromEntries([
|
|
21776
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
21777
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
21778
|
+
ext,
|
|
21779
|
+
{ ...C_LIKE, triple: true }
|
|
21780
|
+
]),
|
|
21781
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
21782
|
+
...["css", "scss", "less"].map((ext) => [
|
|
21783
|
+
ext,
|
|
21784
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
21785
|
+
]),
|
|
21786
|
+
...["py", "pyi"].map((ext) => [
|
|
21787
|
+
ext,
|
|
21788
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
21789
|
+
]),
|
|
21790
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
21791
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
21792
|
+
ext,
|
|
21793
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
21794
|
+
])
|
|
21795
|
+
]);
|
|
21796
|
+
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;
|
|
21797
|
+
var extensionOf = (file) => {
|
|
21798
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
21799
|
+
const dot = name.lastIndexOf(".");
|
|
21800
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
21801
|
+
};
|
|
21802
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
21803
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
21804
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
21805
|
+
var WORD = /[\w$]/u;
|
|
21806
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
21807
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
21808
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
21809
|
+
var REGEX_KEYWORDS = new Set([
|
|
21810
|
+
"return",
|
|
21811
|
+
"typeof",
|
|
21812
|
+
"instanceof",
|
|
21813
|
+
"case",
|
|
21814
|
+
"do",
|
|
21815
|
+
"else",
|
|
21816
|
+
"in",
|
|
21817
|
+
"of",
|
|
21818
|
+
"new",
|
|
21819
|
+
"delete",
|
|
21820
|
+
"void",
|
|
21821
|
+
"throw",
|
|
21822
|
+
"yield",
|
|
21823
|
+
"await"
|
|
21824
|
+
]);
|
|
21825
|
+
function substanceOf(file, text) {
|
|
21826
|
+
const syntax = syntaxOf(file);
|
|
21827
|
+
if (!syntax)
|
|
21828
|
+
return plainSubstance(text);
|
|
21829
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
21830
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
21831
|
+
return plainSubstance(text);
|
|
21832
|
+
return lex(syntax, text);
|
|
21833
|
+
}
|
|
21834
|
+
function plainSubstance(text) {
|
|
21835
|
+
return text.split(`
|
|
21836
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
21837
|
+
`);
|
|
21838
|
+
}
|
|
21839
|
+
function lex(syntax, text) {
|
|
21840
|
+
const collapse = !syntax.indented;
|
|
21841
|
+
let out = "";
|
|
21842
|
+
let lineStart = 0;
|
|
21843
|
+
let kept = 0;
|
|
21844
|
+
let pendingSpace = false;
|
|
21845
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
21846
|
+
let index = 0;
|
|
21847
|
+
if (text.startsWith("#!")) {
|
|
21848
|
+
const end = text.indexOf(`
|
|
21849
|
+
`);
|
|
21850
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
21851
|
+
kept = out.length;
|
|
21852
|
+
index = end < 0 ? text.length : end;
|
|
21853
|
+
}
|
|
21854
|
+
const emitCode = (chunk) => {
|
|
21855
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
21856
|
+
const before = out[out.length - 1];
|
|
21857
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
21858
|
+
out += " ";
|
|
21891
21859
|
}
|
|
21892
|
-
|
|
21893
|
-
|
|
21894
|
-
|
|
21895
|
-
|
|
21896
|
-
|
|
21897
|
-
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
|
|
21902
|
-
|
|
21903
|
-
|
|
21904
|
-
|
|
21905
|
-
break;
|
|
21906
|
-
}
|
|
21907
|
-
stop = start;
|
|
21860
|
+
pendingSpace = false;
|
|
21861
|
+
out += chunk;
|
|
21862
|
+
};
|
|
21863
|
+
const emitVerbatim = (chunk) => {
|
|
21864
|
+
emitCode(chunk);
|
|
21865
|
+
kept = out.length;
|
|
21866
|
+
};
|
|
21867
|
+
const newline = () => {
|
|
21868
|
+
if (!collapse) {
|
|
21869
|
+
let end = out.length;
|
|
21870
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
21871
|
+
end -= 1;
|
|
21872
|
+
out = out.slice(0, end);
|
|
21908
21873
|
}
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
return null;
|
|
21913
|
-
};
|
|
21914
|
-
var tighten = (fd) => {
|
|
21915
|
-
try {
|
|
21916
|
-
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
21917
|
-
fs12.fchmodSync(fd, 384);
|
|
21918
|
-
} catch {}
|
|
21919
|
-
};
|
|
21920
|
-
function appendLine(file, build, options) {
|
|
21921
|
-
const write = () => {
|
|
21922
|
-
const fd = fs12.openSync(file, "a+", 384);
|
|
21923
|
-
try {
|
|
21924
|
-
if (process.platform !== "win32")
|
|
21925
|
-
tighten(fd);
|
|
21926
|
-
const size = fs12.fstatSync(fd).size;
|
|
21927
|
-
let fence = "";
|
|
21928
|
-
if (size > 0) {
|
|
21929
|
-
const last = Buffer.alloc(1);
|
|
21930
|
-
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
21931
|
-
if (last[0] !== 10)
|
|
21932
|
-
fence = `
|
|
21874
|
+
pendingSpace = false;
|
|
21875
|
+
if (out.length > lineStart) {
|
|
21876
|
+
out += `
|
|
21933
21877
|
`;
|
|
21934
|
-
|
|
21935
|
-
const line = build(lastLine(fd, size));
|
|
21936
|
-
if (!line.ok)
|
|
21937
|
-
return line;
|
|
21938
|
-
const bytes = Buffer.from(fence + line.value);
|
|
21939
|
-
const written = fs12.writeSync(fd, bytes);
|
|
21940
|
-
if (written !== bytes.length)
|
|
21941
|
-
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
21942
|
-
return { ok: true, value: undefined };
|
|
21943
|
-
} finally {
|
|
21944
|
-
fs12.closeSync(fd);
|
|
21878
|
+
lineStart = out.length;
|
|
21945
21879
|
}
|
|
21946
21880
|
};
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
var sealed = (row, id, at, previous) => {
|
|
21958
|
-
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
21959
|
-
const prevHash = previous === null ? null : sha2562(previous);
|
|
21960
|
-
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
21961
|
-
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
21962
|
-
return { full, line: `${JSON.stringify(full)}
|
|
21963
|
-
` };
|
|
21964
|
-
};
|
|
21965
|
-
function appendRaw(cwd, row, options) {
|
|
21966
|
-
const file = ledgerPath(cwd);
|
|
21967
|
-
if (!file.ok)
|
|
21968
|
-
return file;
|
|
21969
|
-
const id = newId2();
|
|
21970
|
-
const at = (options.now ?? new Date).toISOString();
|
|
21971
|
-
let full = null;
|
|
21972
|
-
const written = appendLine(file.value, (previous) => {
|
|
21973
|
-
const next = sealed(row, id, at, previous);
|
|
21974
|
-
const bytes = Buffer.byteLength(next.line);
|
|
21975
|
-
if (bytes > MAX_LINE_BYTES)
|
|
21976
|
-
return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
|
|
21977
|
-
full = next.full;
|
|
21978
|
-
return { ok: true, value: next.line };
|
|
21979
|
-
}, options);
|
|
21980
|
-
return written.ok ? { ok: true, value: full } : written;
|
|
21981
|
-
}
|
|
21982
|
-
function observedRowBytes(row) {
|
|
21983
|
-
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
21984
|
-
return Buffer.byteLength(sample.line);
|
|
21985
|
-
}
|
|
21986
|
-
function appendObserved(cwd, row, options = {}) {
|
|
21987
|
-
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
21988
|
-
}
|
|
21989
|
-
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21990
|
-
var str2 = (value) => typeof value === "string" ? value : null;
|
|
21991
|
-
var normalizeActor = (value) => {
|
|
21992
|
-
const actor = isRecord3(value) ? value : {};
|
|
21993
|
-
return {
|
|
21994
|
-
host: str2(actor.host) ?? "unknown",
|
|
21995
|
-
session: str2(actor.session),
|
|
21996
|
-
agentId: str2(actor.agentId)
|
|
21881
|
+
const regexAllowed = () => {
|
|
21882
|
+
let end = out.length;
|
|
21883
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
21884
|
+
end -= 1;
|
|
21885
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
21886
|
+
return true;
|
|
21887
|
+
let start = end;
|
|
21888
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
21889
|
+
start -= 1;
|
|
21890
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
21997
21891
|
};
|
|
21998
|
-
|
|
21999
|
-
|
|
22000
|
-
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22005
|
-
|
|
22006
|
-
|
|
22007
|
-
|
|
22008
|
-
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
|
|
22012
|
-
|
|
22013
|
-
|
|
22014
|
-
|
|
22015
|
-
|
|
22016
|
-
|
|
22017
|
-
|
|
22018
|
-
|
|
22019
|
-
|
|
22020
|
-
|
|
22021
|
-
}
|
|
22022
|
-
|
|
22023
|
-
|
|
22024
|
-
|
|
22025
|
-
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
|
|
22034
|
-
|
|
22035
|
-
|
|
22036
|
-
|
|
22037
|
-
|
|
22038
|
-
|
|
22039
|
-
|
|
22040
|
-
|
|
22041
|
-
|
|
22042
|
-
const trimmed = raw.trim();
|
|
22043
|
-
if (!trimmed)
|
|
21892
|
+
const scanString = (quote, escapes, multiline) => {
|
|
21893
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
21894
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
21895
|
+
let end = index + quote.length;
|
|
21896
|
+
while (end < text.length) {
|
|
21897
|
+
const char = text[end];
|
|
21898
|
+
if (escapes && char === "\\") {
|
|
21899
|
+
end += 2;
|
|
21900
|
+
continue;
|
|
21901
|
+
}
|
|
21902
|
+
if (text.startsWith(quote, end)) {
|
|
21903
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
21904
|
+
return false;
|
|
21905
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
21906
|
+
index = end + quote.length;
|
|
21907
|
+
return true;
|
|
21908
|
+
}
|
|
21909
|
+
if (char === `
|
|
21910
|
+
` && !multiline)
|
|
21911
|
+
return false;
|
|
21912
|
+
end += 1;
|
|
21913
|
+
}
|
|
21914
|
+
return false;
|
|
21915
|
+
};
|
|
21916
|
+
while (index < text.length) {
|
|
21917
|
+
const frame = frames[frames.length - 1];
|
|
21918
|
+
const char = text[index];
|
|
21919
|
+
if (frame.kind === "template") {
|
|
21920
|
+
if (char === "\\") {
|
|
21921
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
21922
|
+
index += 2;
|
|
21923
|
+
} else if (char === "`") {
|
|
21924
|
+
emitVerbatim(char);
|
|
21925
|
+
frames.pop();
|
|
21926
|
+
index += 1;
|
|
21927
|
+
} else if (text.startsWith("${", index)) {
|
|
21928
|
+
emitVerbatim("${");
|
|
21929
|
+
frames.push({ kind: "code", braces: 0 });
|
|
21930
|
+
index += 2;
|
|
21931
|
+
} else {
|
|
21932
|
+
out += char;
|
|
21933
|
+
kept = out.length;
|
|
21934
|
+
index += 1;
|
|
21935
|
+
}
|
|
22044
21936
|
continue;
|
|
22045
|
-
|
|
22046
|
-
if (
|
|
22047
|
-
|
|
22048
|
-
|
|
22049
|
-
|
|
22050
|
-
parsed = JSON.parse(trimmed);
|
|
22051
|
-
} catch {
|
|
22052
|
-
skipped += 1;
|
|
21937
|
+
}
|
|
21938
|
+
if (char === `
|
|
21939
|
+
`) {
|
|
21940
|
+
newline();
|
|
21941
|
+
index += 1;
|
|
22053
21942
|
continue;
|
|
22054
21943
|
}
|
|
22055
|
-
if (
|
|
22056
|
-
|
|
21944
|
+
if (SPACE.test(char)) {
|
|
21945
|
+
if (collapse)
|
|
21946
|
+
pendingSpace = true;
|
|
21947
|
+
else
|
|
21948
|
+
out += char;
|
|
21949
|
+
index += 1;
|
|
22057
21950
|
continue;
|
|
22058
21951
|
}
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
|
|
22062
|
-
|
|
22063
|
-
const
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
} else if (chainStarted)
|
|
22070
|
-
reason = "unsigned";
|
|
22071
|
-
else
|
|
22072
|
-
rowIntegrity = "legacy";
|
|
22073
|
-
if (options.integrity && !reason && ids.has(parsed.id))
|
|
22074
|
-
reason = "duplicate_id";
|
|
22075
|
-
if (options.integrity)
|
|
22076
|
-
ids.add(parsed.id);
|
|
22077
|
-
if (reason)
|
|
22078
|
-
rowIntegrity = "unverified";
|
|
22079
|
-
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
22080
|
-
const row = {
|
|
22081
|
-
...parsed,
|
|
22082
|
-
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
22083
|
-
id: parsed.id,
|
|
22084
|
-
at: parsed.at,
|
|
22085
|
-
type: parsed.type,
|
|
22086
|
-
actor: normalizeActor(parsed.actor),
|
|
22087
|
-
branch: str2(parsed.branch),
|
|
22088
|
-
head: str2(parsed.head),
|
|
22089
|
-
base: str2(parsed.base),
|
|
22090
|
-
baseSha: str2(parsed.baseSha),
|
|
22091
|
-
patchId: str2(parsed.patchId),
|
|
22092
|
-
diffHash: str2(parsed.diffHash),
|
|
22093
|
-
tree: str2(parsed.tree),
|
|
22094
|
-
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
22095
|
-
...pr === undefined ? {} : { pr },
|
|
22096
|
-
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
22097
|
-
seq: rows.length + 1,
|
|
22098
|
-
superseded: false,
|
|
22099
|
-
supersedeIgnored: false,
|
|
22100
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
22101
|
-
...reason ? { integrityReason: reason } : {}
|
|
22102
|
-
};
|
|
22103
|
-
rows.push(row);
|
|
22104
|
-
if (!options.integrity)
|
|
21952
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
21953
|
+
if (lineOpener) {
|
|
21954
|
+
const end = text.indexOf(`
|
|
21955
|
+
`, index);
|
|
21956
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
21957
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
21958
|
+
emitVerbatim(comment);
|
|
21959
|
+
else
|
|
21960
|
+
pendingSpace = true;
|
|
21961
|
+
index = end < 0 ? text.length : end;
|
|
22105
21962
|
continue;
|
|
22106
|
-
|
|
22107
|
-
|
|
22108
|
-
|
|
22109
|
-
|
|
22110
|
-
|
|
22111
|
-
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
22115
|
-
|
|
22116
|
-
|
|
22117
|
-
|
|
22118
|
-
|
|
22119
|
-
|
|
22120
|
-
|
|
22121
|
-
|
|
22122
|
-
|
|
22123
|
-
|
|
22124
|
-
|
|
22125
|
-
|
|
22126
|
-
|
|
22127
|
-
|
|
22128
|
-
|
|
22129
|
-
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
|
|
22133
|
-
if (
|
|
22134
|
-
|
|
21963
|
+
}
|
|
21964
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
21965
|
+
if (block) {
|
|
21966
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
21967
|
+
if (end < 0)
|
|
21968
|
+
return null;
|
|
21969
|
+
const comment = text.slice(index, end + block[1].length);
|
|
21970
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
21971
|
+
emitVerbatim(comment);
|
|
21972
|
+
else
|
|
21973
|
+
pendingSpace = true;
|
|
21974
|
+
if (comment.includes(`
|
|
21975
|
+
`))
|
|
21976
|
+
newline();
|
|
21977
|
+
index = end + block[1].length;
|
|
21978
|
+
continue;
|
|
21979
|
+
}
|
|
21980
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
21981
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
21982
|
+
return null;
|
|
21983
|
+
continue;
|
|
21984
|
+
}
|
|
21985
|
+
if (syntax.quotes.includes(char)) {
|
|
21986
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
21987
|
+
return null;
|
|
21988
|
+
continue;
|
|
21989
|
+
}
|
|
21990
|
+
if (syntax.literal?.includes(char)) {
|
|
21991
|
+
if (!scanString(char, false, false))
|
|
21992
|
+
return null;
|
|
21993
|
+
continue;
|
|
21994
|
+
}
|
|
21995
|
+
if (syntax.rawBacktick && char === "`") {
|
|
21996
|
+
if (!scanString("`", false, true))
|
|
21997
|
+
return null;
|
|
21998
|
+
continue;
|
|
21999
|
+
}
|
|
22000
|
+
if (syntax.js && char === "`") {
|
|
22001
|
+
emitVerbatim(char);
|
|
22002
|
+
frames.push({ kind: "template" });
|
|
22003
|
+
index += 1;
|
|
22004
|
+
continue;
|
|
22005
|
+
}
|
|
22006
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
22007
|
+
return null;
|
|
22008
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
22009
|
+
const lineEnd = text.indexOf(`
|
|
22010
|
+
`, index);
|
|
22011
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
22012
|
+
return null;
|
|
22013
|
+
}
|
|
22014
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
22015
|
+
let end = index + 1;
|
|
22016
|
+
let inClass = false;
|
|
22017
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
22018
|
+
if (text[end] === `
|
|
22019
|
+
`)
|
|
22020
|
+
return null;
|
|
22021
|
+
if (text[end] === "\\")
|
|
22022
|
+
end += 1;
|
|
22023
|
+
else if (text[end] === "[")
|
|
22024
|
+
inClass = true;
|
|
22025
|
+
else if (text[end] === "]")
|
|
22026
|
+
inClass = false;
|
|
22027
|
+
end += 1;
|
|
22028
|
+
}
|
|
22029
|
+
if (end >= text.length)
|
|
22030
|
+
return null;
|
|
22031
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
22032
|
+
index = end + 1;
|
|
22033
|
+
continue;
|
|
22034
|
+
}
|
|
22035
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
22036
|
+
if (char === "{")
|
|
22037
|
+
frame.braces += 1;
|
|
22038
|
+
else if (char === "}") {
|
|
22039
|
+
if (frame.braces === 0) {
|
|
22040
|
+
emitVerbatim(char);
|
|
22041
|
+
frames.pop();
|
|
22042
|
+
index += 1;
|
|
22043
|
+
continue;
|
|
22044
|
+
}
|
|
22045
|
+
frame.braces -= 1;
|
|
22046
|
+
}
|
|
22047
|
+
}
|
|
22048
|
+
emitCode(char);
|
|
22049
|
+
index += 1;
|
|
22135
22050
|
}
|
|
22136
|
-
if (
|
|
22137
|
-
|
|
22138
|
-
|
|
22139
|
-
|
|
22140
|
-
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
22141
|
-
});
|
|
22142
|
-
return {
|
|
22143
|
-
ok: true,
|
|
22144
|
-
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
22145
|
-
};
|
|
22051
|
+
if (frames.length !== 1)
|
|
22052
|
+
return null;
|
|
22053
|
+
newline();
|
|
22054
|
+
return out;
|
|
22146
22055
|
}
|
|
22147
|
-
|
|
22148
|
-
|
|
22149
|
-
|
|
22150
|
-
|
|
22151
|
-
|
|
22152
|
-
|
|
22153
|
-
|
|
22154
|
-
try {
|
|
22155
|
-
const stat = fs12.statSync(file.value);
|
|
22156
|
-
const cached = tamperCache.get(file.value);
|
|
22157
|
-
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
22158
|
-
return cached.breaks;
|
|
22159
|
-
} catch {
|
|
22160
|
-
return [];
|
|
22056
|
+
function unbalanced(body) {
|
|
22057
|
+
let depth = 0;
|
|
22058
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
22059
|
+
if (part === "{")
|
|
22060
|
+
depth += 1;
|
|
22061
|
+
else if (part === "}")
|
|
22062
|
+
depth = Math.max(0, depth - 1);
|
|
22161
22063
|
}
|
|
22162
|
-
|
|
22163
|
-
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
22164
|
-
}
|
|
22165
|
-
function tamperedUnblock(cwd) {
|
|
22166
|
-
const file = ledgerPath(cwd);
|
|
22167
|
-
const where = file.ok ? file.value : "the ledger file";
|
|
22168
|
-
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
22169
|
-
}
|
|
22170
|
-
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
22171
|
-
function verdictBasis(verdict, current) {
|
|
22172
|
-
if (verdict.head && verdict.head === current.head)
|
|
22173
|
-
return "fresh";
|
|
22174
|
-
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
22175
|
-
return "carried";
|
|
22176
|
-
return "stale";
|
|
22064
|
+
return depth > 0;
|
|
22177
22065
|
}
|
|
22178
|
-
|
|
22179
|
-
|
|
22180
|
-
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
22181
|
-
return [];
|
|
22182
|
-
const out = git4(cwd, [
|
|
22183
|
-
"log",
|
|
22184
|
-
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
22185
|
-
`${base}..${head}`
|
|
22186
|
-
]);
|
|
22187
|
-
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
22066
|
+
function hasOpaqueLineBreak(text) {
|
|
22067
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
22188
22068
|
}
|
|
22189
|
-
var
|
|
22190
|
-
function
|
|
22191
|
-
|
|
22192
|
-
|
|
22193
|
-
|
|
22194
|
-
|
|
22195
|
-
|
|
22196
|
-
|
|
22197
|
-
"--date=unix",
|
|
22198
|
-
"--format=%gd%x09%gs",
|
|
22199
|
-
`refs/heads/${branch}`,
|
|
22200
|
-
"--"
|
|
22201
|
-
]);
|
|
22202
|
-
for (const line of (log ?? "").split(`
|
|
22203
|
-
`)) {
|
|
22204
|
-
const [selector = "", subject = ""] = line.split("\t");
|
|
22205
|
-
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
22206
|
-
if (!Number.isFinite(seconds))
|
|
22207
|
-
continue;
|
|
22208
|
-
since = seconds * 1000;
|
|
22209
|
-
const match = RENAMED.exec(subject);
|
|
22210
|
-
if (!match || match[1] === branch)
|
|
22211
|
-
continue;
|
|
22212
|
-
const until = (seconds + 1) * 1000;
|
|
22213
|
-
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
22214
|
-
}
|
|
22069
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
22070
|
+
function textOf(bytes) {
|
|
22071
|
+
if (bytes.includes(0))
|
|
22072
|
+
return null;
|
|
22073
|
+
try {
|
|
22074
|
+
return decoder.decode(bytes);
|
|
22075
|
+
} catch {
|
|
22076
|
+
return null;
|
|
22215
22077
|
}
|
|
22216
|
-
return (row) => {
|
|
22217
|
-
if (row.branch === branch)
|
|
22218
|
-
return true;
|
|
22219
|
-
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
22220
|
-
if (until === undefined)
|
|
22221
|
-
return false;
|
|
22222
|
-
const at = Date.parse(row.at);
|
|
22223
|
-
return at >= since && at <= until;
|
|
22224
|
-
};
|
|
22225
22078
|
}
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
|
|
22235
|
-
};
|
|
22236
|
-
};
|
|
22237
|
-
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
22238
|
-
const sessions = new Set;
|
|
22239
|
-
for (const row of rows) {
|
|
22240
|
-
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
22241
|
-
continue;
|
|
22242
|
-
const session = str2(row.session) ?? row.actor.session;
|
|
22243
|
-
if (session)
|
|
22244
|
-
sessions.add(session);
|
|
22245
|
-
}
|
|
22246
|
-
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
22247
|
-
sessions.add(session);
|
|
22248
|
-
return sessions;
|
|
22079
|
+
function cosmeticChange(file, before, after) {
|
|
22080
|
+
const left = textOf(before);
|
|
22081
|
+
const right = textOf(after);
|
|
22082
|
+
if (left === null || right === null)
|
|
22083
|
+
return false;
|
|
22084
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
22085
|
+
return false;
|
|
22086
|
+
const a = substanceOf(file, left);
|
|
22087
|
+
return a !== null && a === substanceOf(file, right);
|
|
22249
22088
|
}
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
return true;
|
|
22257
|
-
return false;
|
|
22089
|
+
|
|
22090
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
22091
|
+
var LANE_THRESHOLDS = {
|
|
22092
|
+
selfMaxLines: 100,
|
|
22093
|
+
selfMaxFiles: 5,
|
|
22094
|
+
fullMinLines: 500
|
|
22258
22095
|
};
|
|
22259
|
-
var
|
|
22260
|
-
"
|
|
22261
|
-
"
|
|
22262
|
-
"
|
|
22263
|
-
"
|
|
22264
|
-
"
|
|
22265
|
-
"
|
|
22096
|
+
var RISK_TOKENS = new Set([
|
|
22097
|
+
"auth",
|
|
22098
|
+
"authn",
|
|
22099
|
+
"authz",
|
|
22100
|
+
"oauth",
|
|
22101
|
+
"security",
|
|
22102
|
+
"permission",
|
|
22103
|
+
"permissions",
|
|
22104
|
+
"secret",
|
|
22105
|
+
"secrets",
|
|
22106
|
+
"crypto",
|
|
22107
|
+
"gate",
|
|
22108
|
+
"gates",
|
|
22109
|
+
"policy",
|
|
22110
|
+
"policies",
|
|
22111
|
+
"hook",
|
|
22112
|
+
"hooks",
|
|
22113
|
+
"husky",
|
|
22114
|
+
"lefthook",
|
|
22115
|
+
"release",
|
|
22116
|
+
"releases",
|
|
22117
|
+
"migration",
|
|
22118
|
+
"migrations",
|
|
22119
|
+
"migrate",
|
|
22120
|
+
"workflows"
|
|
22266
22121
|
]);
|
|
22267
|
-
var
|
|
22268
|
-
"
|
|
22269
|
-
"
|
|
22270
|
-
"
|
|
22271
|
-
"
|
|
22272
|
-
"
|
|
22122
|
+
var RISK_FILES = new Set([
|
|
22123
|
+
".gitlab-ci.yml",
|
|
22124
|
+
"Jenkinsfile",
|
|
22125
|
+
"azure-pipelines.yml",
|
|
22126
|
+
".pre-commit-config.yaml",
|
|
22127
|
+
".workit/risk-paths"
|
|
22273
22128
|
]);
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22129
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
22130
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
22131
|
+
var LOCKFILES = new Set([
|
|
22132
|
+
"bun.lock",
|
|
22133
|
+
"bun.lockb",
|
|
22134
|
+
"package-lock.json",
|
|
22135
|
+
"npm-shrinkwrap.json",
|
|
22136
|
+
"yarn.lock",
|
|
22137
|
+
"pnpm-lock.yaml",
|
|
22138
|
+
"Cargo.lock",
|
|
22139
|
+
"go.sum",
|
|
22140
|
+
"poetry.lock",
|
|
22141
|
+
"uv.lock",
|
|
22142
|
+
"Pipfile.lock",
|
|
22143
|
+
"Gemfile.lock",
|
|
22144
|
+
"composer.lock"
|
|
22145
|
+
]);
|
|
22146
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
22147
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
22148
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
22149
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
22150
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
22151
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
22152
|
+
function kindOf(file) {
|
|
22153
|
+
if (file.binary)
|
|
22154
|
+
return "behavior";
|
|
22155
|
+
if (file.generated)
|
|
22156
|
+
return "generated";
|
|
22157
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
22158
|
+
return "lockfile";
|
|
22159
|
+
if (isDoc(file.path))
|
|
22160
|
+
return "doc";
|
|
22161
|
+
if (file.cosmetic)
|
|
22162
|
+
return "cosmetic";
|
|
22163
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
22164
|
+
}
|
|
22165
|
+
var globCache = new Map;
|
|
22166
|
+
function globMatch(pattern, file) {
|
|
22167
|
+
let regex = globCache.get(pattern);
|
|
22168
|
+
if (!regex) {
|
|
22169
|
+
let source = "";
|
|
22170
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
22171
|
+
const char = pattern[index];
|
|
22172
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
22173
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
22174
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
22175
|
+
} else if (char === "*")
|
|
22176
|
+
source += "[^/]*";
|
|
22177
|
+
else if (char === "?")
|
|
22178
|
+
source += "[^/]";
|
|
22179
|
+
else
|
|
22180
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
22181
|
+
}
|
|
22182
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
22183
|
+
globCache.set(pattern, regex);
|
|
22184
|
+
}
|
|
22185
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
22287
22186
|
}
|
|
22288
|
-
function
|
|
22289
|
-
if (
|
|
22290
|
-
return
|
|
22291
|
-
if (
|
|
22292
|
-
return
|
|
22293
|
-
|
|
22294
|
-
|
|
22187
|
+
function isRiskPath(file, extra = []) {
|
|
22188
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
22189
|
+
return true;
|
|
22190
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
22191
|
+
return true;
|
|
22192
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
22193
|
+
return false;
|
|
22194
|
+
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)));
|
|
22295
22195
|
}
|
|
22296
|
-
|
|
22297
|
-
|
|
22298
|
-
|
|
22299
|
-
const
|
|
22300
|
-
const
|
|
22301
|
-
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22196
|
+
var parseRiskPaths = (text) => text.split(`
|
|
22197
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
22198
|
+
function computeLane(input) {
|
|
22199
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
22200
|
+
const risky = new Set(riskFiles);
|
|
22201
|
+
const kinds = input.files.map((file) => ({
|
|
22202
|
+
file,
|
|
22203
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
22204
|
+
}));
|
|
22205
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
22206
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
22207
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
22208
|
+
const result = (lane, reasons) => ({
|
|
22209
|
+
lane,
|
|
22210
|
+
reasons,
|
|
22211
|
+
lines,
|
|
22212
|
+
files: behavior.length,
|
|
22213
|
+
riskFiles
|
|
22306
22214
|
});
|
|
22215
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
22216
|
+
if (input.riskTier === "high")
|
|
22217
|
+
return result("full", ["the task judgment records high risk"]);
|
|
22218
|
+
if (riskFiles.length)
|
|
22219
|
+
return result("full", [
|
|
22220
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
22221
|
+
]);
|
|
22222
|
+
if (!behavior.length)
|
|
22223
|
+
return result("none", [
|
|
22224
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
22225
|
+
]);
|
|
22226
|
+
if (lines >= fullMinLines)
|
|
22227
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
22228
|
+
if (lines > selfMaxLines)
|
|
22229
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
22230
|
+
if (behavior.length > selfMaxFiles)
|
|
22231
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
22232
|
+
if (opaque.length)
|
|
22233
|
+
return result("focused", [
|
|
22234
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
22235
|
+
]);
|
|
22236
|
+
return result("self", [
|
|
22237
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
22238
|
+
]);
|
|
22307
22239
|
}
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
if (!effective) {
|
|
22323
|
-
effective = row;
|
|
22324
|
-
continue;
|
|
22325
|
-
}
|
|
22326
|
-
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
22327
|
-
continue;
|
|
22328
|
-
const rowIndependent = reasonsOf(row).length === 0;
|
|
22329
|
-
const effIndependent = reasonsOf(effective).length === 0;
|
|
22330
|
-
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
22331
|
-
continue;
|
|
22332
|
-
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
22333
|
-
continue;
|
|
22334
|
-
effective = row;
|
|
22240
|
+
function untrackedStat(cwd, file) {
|
|
22241
|
+
try {
|
|
22242
|
+
const bytes = readFileSync4(path14.join(cwd, file));
|
|
22243
|
+
if (bytes.includes(0))
|
|
22244
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
22245
|
+
const text = bytes.toString("utf8");
|
|
22246
|
+
if (hasOpaqueLineBreak(text))
|
|
22247
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
22248
|
+
const lines = text.split(`
|
|
22249
|
+
`).length - (text.endsWith(`
|
|
22250
|
+
`) ? 1 : 0);
|
|
22251
|
+
return { path: file, added: lines, deleted: 0 };
|
|
22252
|
+
} catch {
|
|
22253
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
22335
22254
|
}
|
|
22336
|
-
return effective;
|
|
22337
22255
|
}
|
|
22338
|
-
|
|
22339
|
-
|
|
22340
|
-
|
|
22341
|
-
const
|
|
22342
|
-
|
|
22343
|
-
|
|
22344
|
-
|
|
22345
|
-
const
|
|
22346
|
-
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22256
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
22257
|
+
var ABSENT = /^0+$/u;
|
|
22258
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
22259
|
+
const raw = diffRaw(cwd, fork, head);
|
|
22260
|
+
if (!raw)
|
|
22261
|
+
return null;
|
|
22262
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
22263
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
22264
|
+
if (!blobs)
|
|
22265
|
+
return null;
|
|
22266
|
+
const out = new Set;
|
|
22267
|
+
const opaque = new Set;
|
|
22268
|
+
for (const entry of candidates) {
|
|
22269
|
+
const before = blobs.get(entry.oldBlob);
|
|
22270
|
+
let after = blobs.get(entry.newBlob);
|
|
22271
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
22272
|
+
try {
|
|
22273
|
+
after = readFileSync4(path14.join(cwd, entry.path));
|
|
22274
|
+
} catch {
|
|
22275
|
+
after = undefined;
|
|
22355
22276
|
}
|
|
22277
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
22278
|
+
out.add(entry.path);
|
|
22279
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
22280
|
+
opaque.add(entry.path);
|
|
22356
22281
|
}
|
|
22357
|
-
return
|
|
22282
|
+
return { cosmetic: out, opaque };
|
|
22358
22283
|
}
|
|
22359
|
-
function
|
|
22360
|
-
const
|
|
22361
|
-
const
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
const
|
|
22365
|
-
const
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
let key = keyCache.get(cacheKey);
|
|
22370
|
-
if (!key) {
|
|
22371
|
-
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
22372
|
-
keyCache.set(cacheKey, key);
|
|
22373
|
-
}
|
|
22374
|
-
return key;
|
|
22375
|
-
};
|
|
22376
|
-
const keyFor = (base) => keysAt(base, head);
|
|
22377
|
-
const basisOf = (row) => {
|
|
22378
|
-
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
22379
|
-
return basis === "stale" && restackCarries(rows, branch, row.head, head) ? "carried" : basis;
|
|
22380
|
-
};
|
|
22381
|
-
const sameCode = (failed, row) => {
|
|
22382
|
-
if (failed.head && failed.head === row.head)
|
|
22383
|
-
return true;
|
|
22384
|
-
const base = failed.base ?? fallbackBase;
|
|
22385
|
-
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
22386
|
-
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
22387
|
-
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
22388
|
-
};
|
|
22389
|
-
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
22390
|
-
const failureAuthorCache = new Map;
|
|
22391
|
-
const failureAuthors = (row) => {
|
|
22392
|
-
let set = failureAuthorCache.get(row.seq);
|
|
22393
|
-
if (!set) {
|
|
22394
|
-
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
22395
|
-
failureAuthorCache.set(row.seq, set);
|
|
22396
|
-
}
|
|
22397
|
-
return set;
|
|
22398
|
-
};
|
|
22399
|
-
const reasonCache = new Map;
|
|
22400
|
-
const reasonsOf = (row) => {
|
|
22401
|
-
let reasons = reasonCache.get(row);
|
|
22402
|
-
if (!reasons) {
|
|
22403
|
-
const failing = FAILING.has(String(row.result));
|
|
22404
|
-
reasons = [
|
|
22405
|
-
...trustReasons(cwd, row),
|
|
22406
|
-
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
22407
|
-
];
|
|
22408
|
-
reasonCache.set(row, reasons);
|
|
22409
|
-
}
|
|
22410
|
-
return reasons;
|
|
22411
|
-
};
|
|
22412
|
-
const retired = new Set;
|
|
22413
|
-
for (const row of rows)
|
|
22414
|
-
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
22415
|
-
retired.add(row.supersedes);
|
|
22416
|
-
const byKind = new Map;
|
|
22417
|
-
for (const row of rows)
|
|
22418
|
-
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
22419
|
-
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
22420
|
-
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
22421
|
-
}
|
|
22422
|
-
const verdicts = [];
|
|
22423
|
-
const warnings = [];
|
|
22424
|
-
for (const [kind, list] of byKind) {
|
|
22425
|
-
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
22426
|
-
if (!row)
|
|
22427
|
-
continue;
|
|
22428
|
-
const basis = basisOf(row);
|
|
22429
|
-
const current = basis !== "stale";
|
|
22430
|
-
const independence = reasonsOf(row);
|
|
22431
|
-
const reasons = [
|
|
22432
|
-
...current ? [] : ["stale"],
|
|
22433
|
-
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
22434
|
-
...independence
|
|
22435
|
-
];
|
|
22436
|
-
verdicts.push({
|
|
22437
|
-
kind,
|
|
22438
|
-
basis,
|
|
22439
|
-
current,
|
|
22440
|
-
independent: independence.length === 0,
|
|
22441
|
-
accepted: reasons.length === 0,
|
|
22442
|
-
reasons,
|
|
22443
|
-
verdict: row
|
|
22444
|
-
});
|
|
22445
|
-
if (reasons.length === 0) {
|
|
22446
|
-
for (const later of list)
|
|
22447
|
-
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
22448
|
-
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
22449
|
-
}
|
|
22284
|
+
function generatedAt(cwd, fork, paths) {
|
|
22285
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
22286
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
22287
|
+
if (!files || !blobs)
|
|
22288
|
+
return new Set;
|
|
22289
|
+
const texts = new Map;
|
|
22290
|
+
for (const [file, id] of files) {
|
|
22291
|
+
const bytes = blobs.get(id);
|
|
22292
|
+
if (bytes)
|
|
22293
|
+
texts.set(file, bytes.toString("utf8"));
|
|
22450
22294
|
}
|
|
22451
|
-
|
|
22452
|
-
|
|
22453
|
-
|
|
22454
|
-
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
|
|
22459
|
-
|
|
22460
|
-
|
|
22295
|
+
return generatedByAttributes(texts, paths);
|
|
22296
|
+
}
|
|
22297
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
22298
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
22299
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
22300
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
22301
|
+
if (!stats || !cosmetic)
|
|
22302
|
+
return null;
|
|
22303
|
+
const files = [
|
|
22304
|
+
...stats.map((entry) => ({
|
|
22305
|
+
...entry,
|
|
22306
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
22307
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
22308
|
+
})),
|
|
22309
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
22310
|
+
];
|
|
22311
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
22312
|
+
return files.map((entry) => ({
|
|
22313
|
+
...entry,
|
|
22314
|
+
generated: generated.has(entry.path)
|
|
22315
|
+
}));
|
|
22316
|
+
}
|
|
22317
|
+
function driftFiles(cwd, range) {
|
|
22318
|
+
if (range.fromFork === range.toFork)
|
|
22319
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
22320
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
22321
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
22322
|
+
if (!drifted || !stats)
|
|
22323
|
+
return null;
|
|
22324
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
22325
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
22326
|
+
return drifted.map((file) => ({
|
|
22327
|
+
path: file,
|
|
22328
|
+
added: byPath.get(file)?.added ?? 0,
|
|
22329
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
22330
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
22331
|
+
cosmetic: false,
|
|
22332
|
+
generated: generated.has(file)
|
|
22333
|
+
}));
|
|
22334
|
+
}
|
|
22335
|
+
function branchLane(cwd, options) {
|
|
22336
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
22337
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
22338
|
+
const head = options.workingTree ? null : options.head;
|
|
22339
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
22340
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
22341
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
22342
|
+
if (!fork || !files) {
|
|
22343
|
+
const high = riskTier === "high";
|
|
22344
|
+
return {
|
|
22345
|
+
lane: high ? "full" : "focused",
|
|
22346
|
+
reasons: [
|
|
22347
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
22348
|
+
...high ? ["the task judgment records high risk"] : []
|
|
22349
|
+
],
|
|
22350
|
+
lines: 0,
|
|
22351
|
+
files: 0,
|
|
22352
|
+
riskFiles: [],
|
|
22353
|
+
base,
|
|
22354
|
+
head: options.head
|
|
22355
|
+
};
|
|
22461
22356
|
}
|
|
22462
|
-
|
|
22463
|
-
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
22464
|
-
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
22465
|
-
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
22466
|
-
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
22467
|
-
accepted: false,
|
|
22468
|
-
verdict: failing?.verdict ?? null,
|
|
22469
|
-
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
22470
|
-
};
|
|
22471
|
-
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))));
|
|
22472
|
-
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
22357
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
22473
22358
|
return {
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22477
|
-
|
|
22478
|
-
|
|
22479
|
-
|
|
22480
|
-
|
|
22481
|
-
accepted,
|
|
22482
|
-
verdicts,
|
|
22483
|
-
authors: [...authors].toSorted(),
|
|
22484
|
-
warnings
|
|
22359
|
+
...computeLane({
|
|
22360
|
+
files,
|
|
22361
|
+
riskTier,
|
|
22362
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
22363
|
+
}),
|
|
22364
|
+
base,
|
|
22365
|
+
head: options.head
|
|
22485
22366
|
};
|
|
22486
22367
|
}
|
|
22487
|
-
var
|
|
22488
|
-
"check",
|
|
22489
|
-
"merge.unverified",
|
|
22490
|
-
"policy.judged",
|
|
22491
|
-
...PR_ROW_TYPES
|
|
22492
|
-
]);
|
|
22493
|
-
|
|
22494
|
-
// packages/workit-core/src/core/policy/lane.ts
|
|
22495
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
22496
|
-
import path16 from "node:path";
|
|
22368
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
22497
22369
|
|
|
22498
|
-
// packages/workit-core/src/core/
|
|
22499
|
-
|
|
22500
|
-
|
|
22501
|
-
|
|
22502
|
-
|
|
22503
|
-
|
|
22504
|
-
|
|
22505
|
-
|
|
22370
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
22371
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
22372
|
+
function verdictDrift(cwd, input) {
|
|
22373
|
+
const since = input.from;
|
|
22374
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
22375
|
+
carries: false,
|
|
22376
|
+
since,
|
|
22377
|
+
files,
|
|
22378
|
+
focused,
|
|
22379
|
+
reason
|
|
22380
|
+
});
|
|
22381
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
22382
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
22383
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
22384
|
+
if (!fromFork || !toFork || !files)
|
|
22385
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
22386
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
22387
|
+
const lane = computeLane({ files, riskPaths });
|
|
22388
|
+
const paths = files.map((file) => file.path);
|
|
22389
|
+
if (lane.lane === "none")
|
|
22390
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
22391
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
22392
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
22393
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
22506
22394
|
}
|
|
22507
22395
|
|
|
22508
|
-
// packages/workit-core/src/
|
|
22509
|
-
|
|
22510
|
-
|
|
22511
|
-
|
|
22512
|
-
|
|
22513
|
-
|
|
22514
|
-
|
|
22515
|
-
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22519
|
-
|
|
22520
|
-
|
|
22521
|
-
|
|
22522
|
-
|
|
22523
|
-
|
|
22524
|
-
|
|
22525
|
-
|
|
22526
|
-
|
|
22396
|
+
// packages/workit-core/src/ledger.ts
|
|
22397
|
+
var LEDGER_VERSION = 1;
|
|
22398
|
+
var MAX_LINE_BYTES = 4096;
|
|
22399
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
22400
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
22401
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
22402
|
+
var PR_ROW_TYPES = new Set([
|
|
22403
|
+
"pr.created",
|
|
22404
|
+
"pr.status",
|
|
22405
|
+
"pr.merged",
|
|
22406
|
+
"pr.linked"
|
|
22407
|
+
]);
|
|
22408
|
+
var err = (code, error, unblock) => ({
|
|
22409
|
+
ok: false,
|
|
22410
|
+
code,
|
|
22411
|
+
error,
|
|
22412
|
+
...unblock ? { unblock } : {}
|
|
22413
|
+
});
|
|
22414
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
22415
|
+
const run = spawnSync9("git", args, {
|
|
22527
22416
|
cwd,
|
|
22528
|
-
encoding: "utf8"
|
|
22417
|
+
encoding: "utf8",
|
|
22418
|
+
timeout: timeoutMs,
|
|
22419
|
+
killSignal: "SIGKILL",
|
|
22420
|
+
windowsHide: true,
|
|
22421
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
22422
|
+
env: {
|
|
22423
|
+
...process.env,
|
|
22424
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
22425
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
22426
|
+
LC_ALL: "C",
|
|
22427
|
+
LANGUAGE: "C"
|
|
22428
|
+
}
|
|
22529
22429
|
});
|
|
22530
|
-
|
|
22531
|
-
|
|
22532
|
-
|
|
22533
|
-
|
|
22430
|
+
return {
|
|
22431
|
+
ok: run.status === 0,
|
|
22432
|
+
stdout: run.stdout ?? "",
|
|
22433
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
22434
|
+
};
|
|
22534
22435
|
};
|
|
22535
|
-
|
|
22536
|
-
|
|
22537
|
-
|
|
22538
|
-
|
|
22539
|
-
|
|
22540
|
-
var
|
|
22541
|
-
|
|
22542
|
-
|
|
22543
|
-
var COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
22544
|
-
|
|
22545
|
-
// packages/workit-core/src/core/policy/attributes.ts
|
|
22546
|
-
function patternRegex(dir, pattern) {
|
|
22547
|
-
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
22436
|
+
var git4 = (cwd, args) => {
|
|
22437
|
+
const run = gitRun2(cwd, args);
|
|
22438
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
22439
|
+
return value ? value : null;
|
|
22440
|
+
};
|
|
22441
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
22442
|
+
function resolveCommit(cwd, ref) {
|
|
22443
|
+
if (!safeRef2(ref))
|
|
22548
22444
|
return null;
|
|
22549
|
-
|
|
22550
|
-
|
|
22551
|
-
|
|
22552
|
-
|
|
22553
|
-
|
|
22554
|
-
|
|
22555
|
-
|
|
22556
|
-
|
|
22557
|
-
|
|
22558
|
-
|
|
22559
|
-
|
|
22560
|
-
|
|
22561
|
-
|
|
22562
|
-
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
|
|
22566
|
-
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
|
|
22572
|
-
}
|
|
22573
|
-
|
|
22445
|
+
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
22446
|
+
}
|
|
22447
|
+
function branchHead(cwd, branch) {
|
|
22448
|
+
if (!safeRef2(branch))
|
|
22449
|
+
return null;
|
|
22450
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
22451
|
+
}
|
|
22452
|
+
function diffHash(cwd, base, head) {
|
|
22453
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
22454
|
+
return null;
|
|
22455
|
+
const diff = gitRun2(cwd, [
|
|
22456
|
+
"diff",
|
|
22457
|
+
"-U3",
|
|
22458
|
+
"--diff-algorithm=myers",
|
|
22459
|
+
"--indent-heuristic",
|
|
22460
|
+
"--no-color",
|
|
22461
|
+
"--no-ext-diff",
|
|
22462
|
+
"--no-textconv",
|
|
22463
|
+
"--no-renames",
|
|
22464
|
+
"--full-index",
|
|
22465
|
+
"--binary",
|
|
22466
|
+
"--src-prefix=a/",
|
|
22467
|
+
"--dst-prefix=b/",
|
|
22468
|
+
`${base}...${head}`,
|
|
22469
|
+
"--"
|
|
22470
|
+
], GIT_TIMEOUTS.worktree);
|
|
22471
|
+
if (!diff.ok || !diff.stdout)
|
|
22472
|
+
return null;
|
|
22473
|
+
const hash = createHash9("sha256");
|
|
22474
|
+
for (const line of diff.stdout.split(`
|
|
22475
|
+
`)) {
|
|
22476
|
+
if (line.startsWith("index "))
|
|
22477
|
+
continue;
|
|
22478
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
22479
|
+
hash.update(`
|
|
22480
|
+
`);
|
|
22481
|
+
}
|
|
22482
|
+
return `sha256:${hash.digest("hex")}`;
|
|
22483
|
+
}
|
|
22484
|
+
function storeRoot(cwd) {
|
|
22485
|
+
const location = resolveStore(path15.resolve(cwd));
|
|
22486
|
+
if (location instanceof Error)
|
|
22487
|
+
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");
|
|
22488
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
22489
|
+
}
|
|
22490
|
+
function ledgerPath(cwd) {
|
|
22491
|
+
const root = storeRoot(cwd);
|
|
22492
|
+
return root.ok ? { ok: true, value: path15.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
22493
|
+
}
|
|
22494
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
22495
|
+
var NETWORK_FS_TYPES = new Set([
|
|
22496
|
+
26985,
|
|
22497
|
+
20859,
|
|
22498
|
+
4266872130,
|
|
22499
|
+
4283649346,
|
|
22500
|
+
1702057286,
|
|
22501
|
+
1397113167,
|
|
22502
|
+
12805120,
|
|
22503
|
+
16914839,
|
|
22504
|
+
1196443219,
|
|
22505
|
+
198183888
|
|
22506
|
+
]);
|
|
22507
|
+
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
22508
|
+
var onNetworkFs = (dir) => {
|
|
22509
|
+
if (process.platform !== "linux")
|
|
22510
|
+
return false;
|
|
22511
|
+
try {
|
|
22512
|
+
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
22513
|
+
} catch {
|
|
22514
|
+
return false;
|
|
22574
22515
|
}
|
|
22575
|
-
|
|
22516
|
+
};
|
|
22517
|
+
var LOCK_WAIT_MS = 5000;
|
|
22518
|
+
var LOCK_STALE_MS = 30000;
|
|
22519
|
+
var LOCK_OWNER = "owner";
|
|
22520
|
+
var lockToken = (lock) => {
|
|
22576
22521
|
try {
|
|
22577
|
-
return
|
|
22522
|
+
return fs12.readFileSync(path15.join(lock, LOCK_OWNER), "utf8");
|
|
22578
22523
|
} catch {
|
|
22579
22524
|
return null;
|
|
22580
22525
|
}
|
|
22581
|
-
}
|
|
22582
|
-
function rulesOf(dir, text) {
|
|
22583
|
-
const out = [];
|
|
22584
|
-
for (const raw of text.split(`
|
|
22585
|
-
`)) {
|
|
22586
|
-
const line = raw.trim();
|
|
22587
|
-
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
22588
|
-
continue;
|
|
22589
|
-
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
22590
|
-
let generated = null;
|
|
22591
|
-
for (const attribute of attributes) {
|
|
22592
|
-
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
22593
|
-
generated = true;
|
|
22594
|
-
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
22595
|
-
generated = false;
|
|
22596
|
-
}
|
|
22597
|
-
if (generated === null)
|
|
22598
|
-
continue;
|
|
22599
|
-
const regex = patternRegex(dir, pattern);
|
|
22600
|
-
if (regex)
|
|
22601
|
-
out.push({ regex, generated });
|
|
22602
|
-
}
|
|
22603
|
-
return out;
|
|
22604
|
-
}
|
|
22605
|
-
function generatedByAttributes(files, paths) {
|
|
22606
|
-
const rules = [...files].map(([file, text]) => {
|
|
22607
|
-
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
22608
|
-
return {
|
|
22609
|
-
depth: dir ? dir.split("/").length : 0,
|
|
22610
|
-
rules: rulesOf(dir, text)
|
|
22611
|
-
};
|
|
22612
|
-
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
22613
|
-
const out = new Set;
|
|
22614
|
-
for (const file of paths) {
|
|
22615
|
-
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
22616
|
-
if (last?.generated)
|
|
22617
|
-
out.add(file);
|
|
22618
|
-
}
|
|
22619
|
-
return out;
|
|
22620
|
-
}
|
|
22621
|
-
|
|
22622
|
-
// packages/workit-core/src/core/policy/substance.ts
|
|
22623
|
-
var SLASH_BLOCK = [["/*", "*/"]];
|
|
22624
|
-
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
22625
|
-
var SYNTAXES = Object.fromEntries([
|
|
22626
|
-
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
22627
|
-
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
22628
|
-
ext,
|
|
22629
|
-
{ ...C_LIKE, triple: true }
|
|
22630
|
-
]),
|
|
22631
|
-
["go", { ...C_LIKE, rawBacktick: true }],
|
|
22632
|
-
...["css", "scss", "less"].map((ext) => [
|
|
22633
|
-
ext,
|
|
22634
|
-
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
22635
|
-
]),
|
|
22636
|
-
...["py", "pyi"].map((ext) => [
|
|
22637
|
-
ext,
|
|
22638
|
-
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
22639
|
-
]),
|
|
22640
|
-
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
22641
|
-
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
22642
|
-
ext,
|
|
22643
|
-
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
22644
|
-
])
|
|
22645
|
-
]);
|
|
22646
|
-
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;
|
|
22647
|
-
var extensionOf = (file) => {
|
|
22648
|
-
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
22649
|
-
const dot = name.lastIndexOf(".");
|
|
22650
|
-
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
22651
22526
|
};
|
|
22652
|
-
var
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
var NO_SPACE_BEFORE = ")]},;";
|
|
22658
|
-
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
22659
|
-
var REGEX_KEYWORDS = new Set([
|
|
22660
|
-
"return",
|
|
22661
|
-
"typeof",
|
|
22662
|
-
"instanceof",
|
|
22663
|
-
"case",
|
|
22664
|
-
"do",
|
|
22665
|
-
"else",
|
|
22666
|
-
"in",
|
|
22667
|
-
"of",
|
|
22668
|
-
"new",
|
|
22669
|
-
"delete",
|
|
22670
|
-
"void",
|
|
22671
|
-
"throw",
|
|
22672
|
-
"yield",
|
|
22673
|
-
"await"
|
|
22674
|
-
]);
|
|
22675
|
-
function substanceOf(file, text) {
|
|
22676
|
-
const syntax = syntaxOf(file);
|
|
22677
|
-
if (!syntax)
|
|
22678
|
-
return plainSubstance(text);
|
|
22679
|
-
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
22680
|
-
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
22681
|
-
return plainSubstance(text);
|
|
22682
|
-
return lex(syntax, text);
|
|
22683
|
-
}
|
|
22684
|
-
function plainSubstance(text) {
|
|
22685
|
-
return text.split(`
|
|
22686
|
-
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
22687
|
-
`);
|
|
22688
|
-
}
|
|
22689
|
-
function lex(syntax, text) {
|
|
22690
|
-
const collapse = !syntax.indented;
|
|
22691
|
-
let out = "";
|
|
22692
|
-
let lineStart = 0;
|
|
22693
|
-
let kept = 0;
|
|
22694
|
-
let pendingSpace = false;
|
|
22695
|
-
const frames = [{ kind: "code", braces: 0 }];
|
|
22696
|
-
let index = 0;
|
|
22697
|
-
if (text.startsWith("#!")) {
|
|
22698
|
-
const end = text.indexOf(`
|
|
22699
|
-
`);
|
|
22700
|
-
out = end < 0 ? text : text.slice(0, end);
|
|
22701
|
-
kept = out.length;
|
|
22702
|
-
index = end < 0 ? text.length : end;
|
|
22527
|
+
var lockAgeMs = (lock) => {
|
|
22528
|
+
for (const target of [path15.join(lock, LOCK_OWNER), lock]) {
|
|
22529
|
+
try {
|
|
22530
|
+
return Date.now() - fs12.statSync(target).mtimeMs;
|
|
22531
|
+
} catch {}
|
|
22703
22532
|
}
|
|
22704
|
-
|
|
22705
|
-
|
|
22706
|
-
|
|
22707
|
-
|
|
22708
|
-
|
|
22709
|
-
|
|
22710
|
-
|
|
22711
|
-
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
emitCode(chunk);
|
|
22715
|
-
kept = out.length;
|
|
22716
|
-
};
|
|
22717
|
-
const newline = () => {
|
|
22718
|
-
if (!collapse) {
|
|
22719
|
-
let end = out.length;
|
|
22720
|
-
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
22721
|
-
end -= 1;
|
|
22722
|
-
out = out.slice(0, end);
|
|
22723
|
-
}
|
|
22724
|
-
pendingSpace = false;
|
|
22725
|
-
if (out.length > lineStart) {
|
|
22726
|
-
out += `
|
|
22727
|
-
`;
|
|
22728
|
-
lineStart = out.length;
|
|
22533
|
+
return null;
|
|
22534
|
+
};
|
|
22535
|
+
var ledgerLock = {
|
|
22536
|
+
acquire(lock) {
|
|
22537
|
+
try {
|
|
22538
|
+
fs12.mkdirSync(lock);
|
|
22539
|
+
} catch (error) {
|
|
22540
|
+
if (error.code === "EEXIST")
|
|
22541
|
+
return null;
|
|
22542
|
+
throw error;
|
|
22729
22543
|
}
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
if (
|
|
22736
|
-
return
|
|
22737
|
-
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
22741
|
-
|
|
22742
|
-
const scanString = (quote, escapes, multiline) => {
|
|
22743
|
-
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
22744
|
-
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
22745
|
-
let end = index + quote.length;
|
|
22746
|
-
while (end < text.length) {
|
|
22747
|
-
const char = text[end];
|
|
22748
|
-
if (escapes && char === "\\") {
|
|
22749
|
-
end += 2;
|
|
22750
|
-
continue;
|
|
22751
|
-
}
|
|
22752
|
-
if (text.startsWith(quote, end)) {
|
|
22753
|
-
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
22754
|
-
return false;
|
|
22755
|
-
emitVerbatim(text.slice(index, end + quote.length));
|
|
22756
|
-
index = end + quote.length;
|
|
22757
|
-
return true;
|
|
22758
|
-
}
|
|
22759
|
-
if (char === `
|
|
22760
|
-
` && !multiline)
|
|
22761
|
-
return false;
|
|
22762
|
-
end += 1;
|
|
22544
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
22545
|
+
fs12.writeFileSync(path15.join(lock, LOCK_OWNER), token);
|
|
22546
|
+
return token;
|
|
22547
|
+
},
|
|
22548
|
+
release(lock, token) {
|
|
22549
|
+
if (lockToken(lock) !== token)
|
|
22550
|
+
return false;
|
|
22551
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
22552
|
+
try {
|
|
22553
|
+
fs12.renameSync(lock, tomb);
|
|
22554
|
+
} catch {
|
|
22555
|
+
return false;
|
|
22763
22556
|
}
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22768
|
-
|
|
22769
|
-
if (frame.kind === "template") {
|
|
22770
|
-
if (char === "\\") {
|
|
22771
|
-
emitVerbatim(text.slice(index, index + 2));
|
|
22772
|
-
index += 2;
|
|
22773
|
-
} else if (char === "`") {
|
|
22774
|
-
emitVerbatim(char);
|
|
22775
|
-
frames.pop();
|
|
22776
|
-
index += 1;
|
|
22777
|
-
} else if (text.startsWith("${", index)) {
|
|
22778
|
-
emitVerbatim("${");
|
|
22779
|
-
frames.push({ kind: "code", braces: 0 });
|
|
22780
|
-
index += 2;
|
|
22781
|
-
} else {
|
|
22782
|
-
out += char;
|
|
22783
|
-
kept = out.length;
|
|
22784
|
-
index += 1;
|
|
22785
|
-
}
|
|
22786
|
-
continue;
|
|
22557
|
+
if (lockToken(tomb) !== token) {
|
|
22558
|
+
try {
|
|
22559
|
+
fs12.renameSync(tomb, lock);
|
|
22560
|
+
} catch {}
|
|
22561
|
+
return false;
|
|
22787
22562
|
}
|
|
22788
|
-
|
|
22789
|
-
|
|
22790
|
-
|
|
22791
|
-
|
|
22792
|
-
|
|
22563
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
22564
|
+
return true;
|
|
22565
|
+
},
|
|
22566
|
+
token: lockToken,
|
|
22567
|
+
reap(lock, observed) {
|
|
22568
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
22569
|
+
try {
|
|
22570
|
+
fs12.renameSync(lock, tomb);
|
|
22571
|
+
} catch {
|
|
22572
|
+
return false;
|
|
22793
22573
|
}
|
|
22794
|
-
if (
|
|
22795
|
-
|
|
22796
|
-
|
|
22797
|
-
else
|
|
22798
|
-
out += char;
|
|
22799
|
-
index += 1;
|
|
22800
|
-
continue;
|
|
22574
|
+
if (lockToken(tomb) === observed) {
|
|
22575
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
22576
|
+
return true;
|
|
22801
22577
|
}
|
|
22802
|
-
|
|
22803
|
-
|
|
22804
|
-
|
|
22805
|
-
|
|
22806
|
-
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
22807
|
-
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
22808
|
-
emitVerbatim(comment);
|
|
22809
|
-
else
|
|
22810
|
-
pendingSpace = true;
|
|
22811
|
-
index = end < 0 ? text.length : end;
|
|
22812
|
-
continue;
|
|
22578
|
+
try {
|
|
22579
|
+
fs12.renameSync(tomb, lock);
|
|
22580
|
+
} catch {
|
|
22581
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
22813
22582
|
}
|
|
22814
|
-
|
|
22815
|
-
|
|
22816
|
-
|
|
22817
|
-
|
|
22818
|
-
|
|
22819
|
-
|
|
22820
|
-
|
|
22821
|
-
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
|
|
22825
|
-
|
|
22826
|
-
|
|
22827
|
-
|
|
22583
|
+
return false;
|
|
22584
|
+
}
|
|
22585
|
+
};
|
|
22586
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
22587
|
+
const lock = `${file}.lock`;
|
|
22588
|
+
const deadline = Date.now() + waitMs;
|
|
22589
|
+
let token = null;
|
|
22590
|
+
for (;; ) {
|
|
22591
|
+
token = ledgerLock.acquire(lock);
|
|
22592
|
+
if (token)
|
|
22593
|
+
break;
|
|
22594
|
+
const observed = lockToken(lock);
|
|
22595
|
+
const age = lockAgeMs(lock);
|
|
22596
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
22828
22597
|
continue;
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22598
|
+
if (Date.now() >= deadline)
|
|
22599
|
+
return null;
|
|
22600
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
22601
|
+
}
|
|
22602
|
+
try {
|
|
22603
|
+
return fn();
|
|
22604
|
+
} finally {
|
|
22605
|
+
ledgerLock.release(lock, token);
|
|
22606
|
+
}
|
|
22607
|
+
}
|
|
22608
|
+
var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
|
|
22609
|
+
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
22610
|
+
${body}`);
|
|
22611
|
+
var TAIL_BYTES = 64 * 1024;
|
|
22612
|
+
var lastLine = (fd, size) => {
|
|
22613
|
+
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
22614
|
+
const buffer = Buffer.alloc(span);
|
|
22615
|
+
fs12.readSync(fd, buffer, 0, span, size - span);
|
|
22616
|
+
const end = buffer.lastIndexOf(10);
|
|
22617
|
+
if (end < 0) {
|
|
22618
|
+
if (span === size)
|
|
22832
22619
|
return null;
|
|
22833
22620
|
continue;
|
|
22834
22621
|
}
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22622
|
+
let stop = end;
|
|
22623
|
+
let text = "";
|
|
22624
|
+
while (stop >= 0) {
|
|
22625
|
+
const start = buffer.lastIndexOf(10, stop - 1);
|
|
22626
|
+
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
22627
|
+
if (text) {
|
|
22628
|
+
if (start >= 0 || span === size)
|
|
22629
|
+
return text;
|
|
22630
|
+
break;
|
|
22631
|
+
}
|
|
22632
|
+
if (start < 0) {
|
|
22633
|
+
if (span === size)
|
|
22634
|
+
return null;
|
|
22635
|
+
break;
|
|
22636
|
+
}
|
|
22637
|
+
stop = start;
|
|
22839
22638
|
}
|
|
22840
|
-
if (
|
|
22841
|
-
|
|
22842
|
-
|
|
22843
|
-
|
|
22639
|
+
if (span === size)
|
|
22640
|
+
return text || null;
|
|
22641
|
+
}
|
|
22642
|
+
return null;
|
|
22643
|
+
};
|
|
22644
|
+
var tighten = (fd) => {
|
|
22645
|
+
try {
|
|
22646
|
+
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
22647
|
+
fs12.fchmodSync(fd, 384);
|
|
22648
|
+
} catch {}
|
|
22649
|
+
};
|
|
22650
|
+
function appendLine(file, build, options) {
|
|
22651
|
+
const write = () => {
|
|
22652
|
+
const fd = fs12.openSync(file, "a+", 384);
|
|
22653
|
+
try {
|
|
22654
|
+
if (process.platform !== "win32")
|
|
22655
|
+
tighten(fd);
|
|
22656
|
+
const size = fs12.fstatSync(fd).size;
|
|
22657
|
+
let fence = "";
|
|
22658
|
+
if (size > 0) {
|
|
22659
|
+
const last = Buffer.alloc(1);
|
|
22660
|
+
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
22661
|
+
if (last[0] !== 10)
|
|
22662
|
+
fence = `
|
|
22663
|
+
`;
|
|
22664
|
+
}
|
|
22665
|
+
const line = build(lastLine(fd, size));
|
|
22666
|
+
if (!line.ok)
|
|
22667
|
+
return line;
|
|
22668
|
+
const bytes = Buffer.from(fence + line.value);
|
|
22669
|
+
const written = fs12.writeSync(fd, bytes);
|
|
22670
|
+
if (written !== bytes.length)
|
|
22671
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
22672
|
+
return { ok: true, value: undefined };
|
|
22673
|
+
} finally {
|
|
22674
|
+
fs12.closeSync(fd);
|
|
22844
22675
|
}
|
|
22845
|
-
|
|
22846
|
-
|
|
22847
|
-
|
|
22676
|
+
};
|
|
22677
|
+
try {
|
|
22678
|
+
fs12.mkdirSync(path15.dirname(file), { recursive: true });
|
|
22679
|
+
if (!options.forceLock && !onNetworkFs(path15.dirname(file)))
|
|
22680
|
+
return write();
|
|
22681
|
+
const locked = withLedgerLock(file, write);
|
|
22682
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
22683
|
+
} catch (error) {
|
|
22684
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
22685
|
+
}
|
|
22686
|
+
}
|
|
22687
|
+
var sealed = (row, id, at, previous) => {
|
|
22688
|
+
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
22689
|
+
const prevHash = previous === null ? null : sha2562(previous);
|
|
22690
|
+
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
22691
|
+
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
22692
|
+
return { full, line: `${JSON.stringify(full)}
|
|
22693
|
+
` };
|
|
22694
|
+
};
|
|
22695
|
+
function appendRaw(cwd, row, options) {
|
|
22696
|
+
const file = ledgerPath(cwd);
|
|
22697
|
+
if (!file.ok)
|
|
22698
|
+
return file;
|
|
22699
|
+
const id = newId2();
|
|
22700
|
+
const at = (options.now ?? new Date).toISOString();
|
|
22701
|
+
let full = null;
|
|
22702
|
+
const written = appendLine(file.value, (previous) => {
|
|
22703
|
+
const next = sealed(row, id, at, previous);
|
|
22704
|
+
const bytes = Buffer.byteLength(next.line);
|
|
22705
|
+
if (bytes > MAX_LINE_BYTES)
|
|
22706
|
+
return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
|
|
22707
|
+
full = next.full;
|
|
22708
|
+
return { ok: true, value: next.line };
|
|
22709
|
+
}, options);
|
|
22710
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
22711
|
+
}
|
|
22712
|
+
function observedRowBytes(row) {
|
|
22713
|
+
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
22714
|
+
return Buffer.byteLength(sample.line);
|
|
22715
|
+
}
|
|
22716
|
+
function appendObserved(cwd, row, options = {}) {
|
|
22717
|
+
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
22718
|
+
}
|
|
22719
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22720
|
+
var str2 = (value) => typeof value === "string" ? value : null;
|
|
22721
|
+
var normalizeActor = (value) => {
|
|
22722
|
+
const actor = isRecord3(value) ? value : {};
|
|
22723
|
+
return {
|
|
22724
|
+
host: str2(actor.host) ?? "unknown",
|
|
22725
|
+
session: str2(actor.session),
|
|
22726
|
+
agentId: str2(actor.agentId)
|
|
22727
|
+
};
|
|
22728
|
+
};
|
|
22729
|
+
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
22730
|
+
if (by.type !== target.type)
|
|
22731
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
22732
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
22733
|
+
return "only the session that wrote a row can supersede it";
|
|
22734
|
+
if (!onBranch(target))
|
|
22735
|
+
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
22736
|
+
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
22737
|
+
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
22738
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
22739
|
+
return "a self verdict cannot replace an independent verdict";
|
|
22740
|
+
return null;
|
|
22741
|
+
}
|
|
22742
|
+
function readLedger(cwd, options = {}) {
|
|
22743
|
+
const file = ledgerPath(cwd);
|
|
22744
|
+
if (!file.ok)
|
|
22745
|
+
return file;
|
|
22746
|
+
let text = "";
|
|
22747
|
+
let stat = null;
|
|
22748
|
+
try {
|
|
22749
|
+
stat = fs12.statSync(file.value);
|
|
22750
|
+
text = fs12.readFileSync(file.value, "utf8");
|
|
22751
|
+
} catch (error) {
|
|
22752
|
+
if (error.code === "ENOENT")
|
|
22753
|
+
return {
|
|
22754
|
+
ok: true,
|
|
22755
|
+
value: {
|
|
22756
|
+
path: file.value,
|
|
22757
|
+
rows: [],
|
|
22758
|
+
skipped: 0,
|
|
22759
|
+
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
22760
|
+
}
|
|
22761
|
+
};
|
|
22762
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
22763
|
+
}
|
|
22764
|
+
const rows = [];
|
|
22765
|
+
let skipped = 0;
|
|
22766
|
+
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
22767
|
+
const earlier = new Set([null]);
|
|
22768
|
+
let chainStarted = false;
|
|
22769
|
+
const ids = new Set;
|
|
22770
|
+
for (const raw of text.split(`
|
|
22771
|
+
`)) {
|
|
22772
|
+
const trimmed = raw.trim();
|
|
22773
|
+
if (!trimmed)
|
|
22848
22774
|
continue;
|
|
22849
|
-
|
|
22850
|
-
if (
|
|
22851
|
-
|
|
22852
|
-
|
|
22853
|
-
|
|
22775
|
+
const linked = (prevHash) => earlier.has(prevHash);
|
|
22776
|
+
if (options.integrity)
|
|
22777
|
+
earlier.add(sha2562(trimmed));
|
|
22778
|
+
let parsed;
|
|
22779
|
+
try {
|
|
22780
|
+
parsed = JSON.parse(trimmed);
|
|
22781
|
+
} catch {
|
|
22782
|
+
skipped += 1;
|
|
22854
22783
|
continue;
|
|
22855
22784
|
}
|
|
22856
|
-
if (
|
|
22857
|
-
|
|
22858
|
-
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
22859
|
-
const lineEnd = text.indexOf(`
|
|
22860
|
-
`, index);
|
|
22861
|
-
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
22862
|
-
return null;
|
|
22863
|
-
}
|
|
22864
|
-
if (syntax.js && char === "/" && regexAllowed()) {
|
|
22865
|
-
let end = index + 1;
|
|
22866
|
-
let inClass = false;
|
|
22867
|
-
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
22868
|
-
if (text[end] === `
|
|
22869
|
-
`)
|
|
22870
|
-
return null;
|
|
22871
|
-
if (text[end] === "\\")
|
|
22872
|
-
end += 1;
|
|
22873
|
-
else if (text[end] === "[")
|
|
22874
|
-
inClass = true;
|
|
22875
|
-
else if (text[end] === "]")
|
|
22876
|
-
inClass = false;
|
|
22877
|
-
end += 1;
|
|
22878
|
-
}
|
|
22879
|
-
if (end >= text.length)
|
|
22880
|
-
return null;
|
|
22881
|
-
emitVerbatim(text.slice(index, end + 1));
|
|
22882
|
-
index = end + 1;
|
|
22785
|
+
if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
22786
|
+
skipped += 1;
|
|
22883
22787
|
continue;
|
|
22884
22788
|
}
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
|
|
22890
|
-
|
|
22891
|
-
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
|
|
22896
|
-
|
|
22897
|
-
|
|
22898
|
-
|
|
22899
|
-
|
|
22789
|
+
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
22790
|
+
let reason = null;
|
|
22791
|
+
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
22792
|
+
chainStarted = true;
|
|
22793
|
+
const { rowHash, ...body } = parsed;
|
|
22794
|
+
const prevHash = str2(parsed.prevHash);
|
|
22795
|
+
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
22796
|
+
reason = "hash_mismatch";
|
|
22797
|
+
else if (!linked(prevHash))
|
|
22798
|
+
reason = "broken_link";
|
|
22799
|
+
} else if (chainStarted)
|
|
22800
|
+
reason = "unsigned";
|
|
22801
|
+
else
|
|
22802
|
+
rowIntegrity = "legacy";
|
|
22803
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
22804
|
+
reason = "duplicate_id";
|
|
22805
|
+
if (options.integrity)
|
|
22806
|
+
ids.add(parsed.id);
|
|
22807
|
+
if (reason)
|
|
22808
|
+
rowIntegrity = "unverified";
|
|
22809
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
22810
|
+
const row = {
|
|
22811
|
+
...parsed,
|
|
22812
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
22813
|
+
id: parsed.id,
|
|
22814
|
+
at: parsed.at,
|
|
22815
|
+
type: parsed.type,
|
|
22816
|
+
actor: normalizeActor(parsed.actor),
|
|
22817
|
+
branch: str2(parsed.branch),
|
|
22818
|
+
head: str2(parsed.head),
|
|
22819
|
+
base: str2(parsed.base),
|
|
22820
|
+
baseSha: str2(parsed.baseSha),
|
|
22821
|
+
patchId: str2(parsed.patchId),
|
|
22822
|
+
diffHash: str2(parsed.diffHash),
|
|
22823
|
+
tree: str2(parsed.tree),
|
|
22824
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
22825
|
+
...pr === undefined ? {} : { pr },
|
|
22826
|
+
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
22827
|
+
seq: rows.length + 1,
|
|
22828
|
+
superseded: false,
|
|
22829
|
+
supersedeIgnored: false,
|
|
22830
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
22831
|
+
...reason ? { integrityReason: reason } : {}
|
|
22832
|
+
};
|
|
22833
|
+
rows.push(row);
|
|
22834
|
+
if (!options.integrity)
|
|
22835
|
+
continue;
|
|
22836
|
+
if (rowIntegrity === "verified")
|
|
22837
|
+
integrity.chained += 1;
|
|
22838
|
+
else if (rowIntegrity === "legacy")
|
|
22839
|
+
integrity.legacy += 1;
|
|
22840
|
+
else if (reason)
|
|
22841
|
+
integrity.unverified.push({
|
|
22842
|
+
seq: row.seq,
|
|
22843
|
+
id: row.id,
|
|
22844
|
+
type: row.type,
|
|
22845
|
+
branch: row.branch,
|
|
22846
|
+
reason
|
|
22847
|
+
});
|
|
22900
22848
|
}
|
|
22901
|
-
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22849
|
+
const scopes = new Map;
|
|
22850
|
+
const scopeOf = (branch) => {
|
|
22851
|
+
let scope = scopes.get(branch);
|
|
22852
|
+
if (!scope)
|
|
22853
|
+
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
22854
|
+
return scope;
|
|
22855
|
+
};
|
|
22856
|
+
const byId = new Map;
|
|
22857
|
+
for (const row of rows) {
|
|
22858
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
22859
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
22860
|
+
target.superseded = true;
|
|
22861
|
+
else if (row.supersedes)
|
|
22862
|
+
row.supersedeIgnored = true;
|
|
22863
|
+
if (!byId.has(row.id))
|
|
22864
|
+
byId.set(row.id, row);
|
|
22913
22865
|
}
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
22917
|
-
|
|
22866
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
22867
|
+
tamperCache.set(file.value, {
|
|
22868
|
+
size: stat.size,
|
|
22869
|
+
stamp: fileStamp(stat),
|
|
22870
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
22871
|
+
});
|
|
22872
|
+
return {
|
|
22873
|
+
ok: true,
|
|
22874
|
+
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
22875
|
+
};
|
|
22918
22876
|
}
|
|
22919
|
-
var
|
|
22920
|
-
|
|
22921
|
-
|
|
22922
|
-
|
|
22877
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
22878
|
+
var tamperCache = new Map;
|
|
22879
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
22880
|
+
function ledgerTampering(cwd) {
|
|
22881
|
+
const file = ledgerPath(cwd);
|
|
22882
|
+
if (!file.ok)
|
|
22883
|
+
return [];
|
|
22923
22884
|
try {
|
|
22924
|
-
|
|
22885
|
+
const stat = fs12.statSync(file.value);
|
|
22886
|
+
const cached = tamperCache.get(file.value);
|
|
22887
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
22888
|
+
return cached.breaks;
|
|
22925
22889
|
} catch {
|
|
22926
|
-
return
|
|
22890
|
+
return [];
|
|
22927
22891
|
}
|
|
22892
|
+
const read = readLedger(cwd, { integrity: true });
|
|
22893
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
22928
22894
|
}
|
|
22929
|
-
function
|
|
22930
|
-
const
|
|
22931
|
-
const
|
|
22932
|
-
if (
|
|
22933
|
-
return false;
|
|
22934
|
-
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
22935
|
-
return false;
|
|
22936
|
-
const a = substanceOf(file, left);
|
|
22937
|
-
return a !== null && a === substanceOf(file, right);
|
|
22895
|
+
function tamperedUnblock(cwd) {
|
|
22896
|
+
const file = ledgerPath(cwd);
|
|
22897
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
22898
|
+
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
22938
22899
|
}
|
|
22939
|
-
|
|
22940
|
-
|
|
22941
|
-
|
|
22942
|
-
|
|
22943
|
-
|
|
22944
|
-
|
|
22945
|
-
|
|
22946
|
-
var RISK_TOKENS = new Set([
|
|
22947
|
-
"auth",
|
|
22948
|
-
"authn",
|
|
22949
|
-
"authz",
|
|
22950
|
-
"oauth",
|
|
22951
|
-
"security",
|
|
22952
|
-
"permission",
|
|
22953
|
-
"permissions",
|
|
22954
|
-
"secret",
|
|
22955
|
-
"secrets",
|
|
22956
|
-
"crypto",
|
|
22957
|
-
"gate",
|
|
22958
|
-
"gates",
|
|
22959
|
-
"policy",
|
|
22960
|
-
"policies",
|
|
22961
|
-
"hook",
|
|
22962
|
-
"hooks",
|
|
22963
|
-
"husky",
|
|
22964
|
-
"lefthook",
|
|
22965
|
-
"release",
|
|
22966
|
-
"releases",
|
|
22967
|
-
"migration",
|
|
22968
|
-
"migrations",
|
|
22969
|
-
"migrate",
|
|
22970
|
-
"workflows"
|
|
22971
|
-
]);
|
|
22972
|
-
var RISK_FILES = new Set([
|
|
22973
|
-
".gitlab-ci.yml",
|
|
22974
|
-
"Jenkinsfile",
|
|
22975
|
-
"azure-pipelines.yml",
|
|
22976
|
-
".pre-commit-config.yaml",
|
|
22977
|
-
".workit/risk-paths"
|
|
22978
|
-
]);
|
|
22979
|
-
var RISK_NAMES = new Set([".gitattributes"]);
|
|
22980
|
-
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
22981
|
-
var LOCKFILES = new Set([
|
|
22982
|
-
"bun.lock",
|
|
22983
|
-
"bun.lockb",
|
|
22984
|
-
"package-lock.json",
|
|
22985
|
-
"npm-shrinkwrap.json",
|
|
22986
|
-
"yarn.lock",
|
|
22987
|
-
"pnpm-lock.yaml",
|
|
22988
|
-
"Cargo.lock",
|
|
22989
|
-
"go.sum",
|
|
22990
|
-
"poetry.lock",
|
|
22991
|
-
"uv.lock",
|
|
22992
|
-
"Pipfile.lock",
|
|
22993
|
-
"Gemfile.lock",
|
|
22994
|
-
"composer.lock"
|
|
22995
|
-
]);
|
|
22996
|
-
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
22997
|
-
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
22998
|
-
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
22999
|
-
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
23000
|
-
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
23001
|
-
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
23002
|
-
function kindOf(file) {
|
|
23003
|
-
if (file.binary)
|
|
23004
|
-
return "behavior";
|
|
23005
|
-
if (file.generated)
|
|
23006
|
-
return "generated";
|
|
23007
|
-
if (LOCKFILES.has(basename(file.path)))
|
|
23008
|
-
return "lockfile";
|
|
23009
|
-
if (isDoc(file.path))
|
|
23010
|
-
return "doc";
|
|
23011
|
-
if (file.cosmetic)
|
|
23012
|
-
return "cosmetic";
|
|
23013
|
-
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
22900
|
+
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
22901
|
+
function verdictBasis(verdict, current) {
|
|
22902
|
+
if (verdict.head && verdict.head === current.head)
|
|
22903
|
+
return "fresh";
|
|
22904
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
22905
|
+
return "carried";
|
|
22906
|
+
return "stale";
|
|
23014
22907
|
}
|
|
23015
|
-
var
|
|
23016
|
-
function
|
|
23017
|
-
|
|
23018
|
-
|
|
23019
|
-
|
|
23020
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
23023
|
-
|
|
23024
|
-
|
|
23025
|
-
|
|
23026
|
-
|
|
23027
|
-
|
|
23028
|
-
|
|
23029
|
-
|
|
23030
|
-
|
|
22908
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
22909
|
+
function trailerSessions(cwd, base, head) {
|
|
22910
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
22911
|
+
return [];
|
|
22912
|
+
const out = git4(cwd, [
|
|
22913
|
+
"log",
|
|
22914
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
22915
|
+
`${base}..${head}`
|
|
22916
|
+
]);
|
|
22917
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
22918
|
+
}
|
|
22919
|
+
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
22920
|
+
function branchScope(cwd, branch) {
|
|
22921
|
+
const renamedAt = new Map;
|
|
22922
|
+
let since = Number.NEGATIVE_INFINITY;
|
|
22923
|
+
if (safeRef2(branch)) {
|
|
22924
|
+
const log = git4(cwd, [
|
|
22925
|
+
"log",
|
|
22926
|
+
"-g",
|
|
22927
|
+
"--date=unix",
|
|
22928
|
+
"--format=%gd%x09%gs",
|
|
22929
|
+
`refs/heads/${branch}`,
|
|
22930
|
+
"--"
|
|
22931
|
+
]);
|
|
22932
|
+
for (const line of (log ?? "").split(`
|
|
22933
|
+
`)) {
|
|
22934
|
+
const [selector = "", subject = ""] = line.split("\t");
|
|
22935
|
+
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
22936
|
+
if (!Number.isFinite(seconds))
|
|
22937
|
+
continue;
|
|
22938
|
+
since = seconds * 1000;
|
|
22939
|
+
const match = RENAMED.exec(subject);
|
|
22940
|
+
if (!match || match[1] === branch)
|
|
22941
|
+
continue;
|
|
22942
|
+
const until = (seconds + 1) * 1000;
|
|
22943
|
+
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
23031
22944
|
}
|
|
23032
|
-
regex = new RegExp(`^${source}$`, "u");
|
|
23033
|
-
globCache.set(pattern, regex);
|
|
23034
22945
|
}
|
|
23035
|
-
return
|
|
22946
|
+
return (row) => {
|
|
22947
|
+
if (row.branch === branch)
|
|
22948
|
+
return true;
|
|
22949
|
+
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
22950
|
+
if (until === undefined)
|
|
22951
|
+
return false;
|
|
22952
|
+
const at = Date.parse(row.at);
|
|
22953
|
+
return at >= since && at <= until;
|
|
22954
|
+
};
|
|
23036
22955
|
}
|
|
23037
|
-
|
|
23038
|
-
|
|
23039
|
-
|
|
23040
|
-
|
|
22956
|
+
var renameAwareBranch = (scopeOf, branch) => {
|
|
22957
|
+
return (target) => {
|
|
22958
|
+
const own = typeof branch === "string" ? branch : null;
|
|
22959
|
+
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
22960
|
+
if (own === theirs)
|
|
22961
|
+
return true;
|
|
22962
|
+
if (own === null || theirs === null || typeof target.at !== "string")
|
|
22963
|
+
return false;
|
|
22964
|
+
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
22965
|
+
};
|
|
22966
|
+
};
|
|
22967
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
22968
|
+
const sessions = new Set;
|
|
22969
|
+
for (const row of rows) {
|
|
22970
|
+
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
22971
|
+
continue;
|
|
22972
|
+
const session = str2(row.session) ?? row.actor.session;
|
|
22973
|
+
if (session)
|
|
22974
|
+
sessions.add(session);
|
|
22975
|
+
}
|
|
22976
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
22977
|
+
sessions.add(session);
|
|
22978
|
+
return sessions;
|
|
22979
|
+
}
|
|
22980
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
22981
|
+
var authoredBy = (authors, session) => {
|
|
22982
|
+
if (authors.has(session))
|
|
23041
22983
|
return true;
|
|
23042
|
-
|
|
23043
|
-
|
|
23044
|
-
|
|
22984
|
+
for (const author of authors)
|
|
22985
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
22986
|
+
return true;
|
|
22987
|
+
return false;
|
|
22988
|
+
};
|
|
22989
|
+
var TRUST_REASONS = new Set([
|
|
22990
|
+
"unsigned",
|
|
22991
|
+
"hash_mismatch",
|
|
22992
|
+
"broken_link",
|
|
22993
|
+
"duplicate_id",
|
|
22994
|
+
"unissued_session",
|
|
22995
|
+
"unbound_session"
|
|
22996
|
+
]);
|
|
22997
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
22998
|
+
"cli",
|
|
22999
|
+
"opencode",
|
|
23000
|
+
"pi",
|
|
23001
|
+
"codex_cli",
|
|
23002
|
+
"codex_desktop"
|
|
23003
|
+
]);
|
|
23004
|
+
function sessionTrust(cwd, row) {
|
|
23005
|
+
const session = row.actor.session;
|
|
23006
|
+
if (!session || row.self === true)
|
|
23007
|
+
return null;
|
|
23008
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
23009
|
+
const host = row.actor.host;
|
|
23010
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
23011
|
+
if (!issuedOnly)
|
|
23012
|
+
return null;
|
|
23013
|
+
const binding = issuedBinding(cwd, session);
|
|
23014
|
+
if (binding === null)
|
|
23015
|
+
return "unissued_session";
|
|
23016
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
23045
23017
|
}
|
|
23046
|
-
|
|
23047
|
-
|
|
23048
|
-
|
|
23049
|
-
|
|
23050
|
-
|
|
23051
|
-
const
|
|
23052
|
-
|
|
23053
|
-
|
|
23054
|
-
|
|
23055
|
-
|
|
23056
|
-
|
|
23057
|
-
const
|
|
23058
|
-
const
|
|
23059
|
-
|
|
23060
|
-
|
|
23061
|
-
|
|
23062
|
-
|
|
23063
|
-
|
|
23018
|
+
function trustReasons(cwd, row) {
|
|
23019
|
+
if (row.integrity === "unverified")
|
|
23020
|
+
return [row.integrityReason ?? "unsigned"];
|
|
23021
|
+
if (row.integrity !== "verified")
|
|
23022
|
+
return ["unsigned"];
|
|
23023
|
+
const session = sessionTrust(cwd, row);
|
|
23024
|
+
return session ? [session] : [];
|
|
23025
|
+
}
|
|
23026
|
+
function withIntegrity(cwd, rows) {
|
|
23027
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
23028
|
+
return rows;
|
|
23029
|
+
const read = readLedger(cwd, { integrity: true });
|
|
23030
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
23031
|
+
return rows.map((row) => {
|
|
23032
|
+
if (row.integrity !== undefined)
|
|
23033
|
+
return row;
|
|
23034
|
+
const fresh = bySeq.get(row.seq);
|
|
23035
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
23064
23036
|
});
|
|
23065
|
-
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
23066
|
-
if (input.riskTier === "high")
|
|
23067
|
-
return result("full", ["the task judgment records high risk"]);
|
|
23068
|
-
if (riskFiles.length)
|
|
23069
|
-
return result("full", [
|
|
23070
|
-
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
23071
|
-
]);
|
|
23072
|
-
if (!behavior.length)
|
|
23073
|
-
return result("none", [
|
|
23074
|
-
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
23075
|
-
]);
|
|
23076
|
-
if (lines >= fullMinLines)
|
|
23077
|
-
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
23078
|
-
if (lines > selfMaxLines)
|
|
23079
|
-
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
23080
|
-
if (behavior.length > selfMaxFiles)
|
|
23081
|
-
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
23082
|
-
if (opaque.length)
|
|
23083
|
-
return result("focused", [
|
|
23084
|
-
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
23085
|
-
]);
|
|
23086
|
-
return result("self", [
|
|
23087
|
-
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
23088
|
-
]);
|
|
23089
23037
|
}
|
|
23090
|
-
|
|
23091
|
-
|
|
23092
|
-
|
|
23093
|
-
|
|
23094
|
-
|
|
23095
|
-
|
|
23096
|
-
|
|
23097
|
-
|
|
23098
|
-
|
|
23099
|
-
|
|
23100
|
-
|
|
23101
|
-
|
|
23102
|
-
|
|
23103
|
-
|
|
23038
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
23039
|
+
var independenceReasons = (row, authors) => {
|
|
23040
|
+
const reasons = [];
|
|
23041
|
+
if (!row.actor.session)
|
|
23042
|
+
reasons.push("no_session");
|
|
23043
|
+
else if (authoredBy(authors, row.actor.session))
|
|
23044
|
+
reasons.push("author_session");
|
|
23045
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
23046
|
+
reasons.push("self");
|
|
23047
|
+
return reasons;
|
|
23048
|
+
};
|
|
23049
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
23050
|
+
let effective = null;
|
|
23051
|
+
for (const row of rows) {
|
|
23052
|
+
if (!effective) {
|
|
23053
|
+
effective = row;
|
|
23054
|
+
continue;
|
|
23055
|
+
}
|
|
23056
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
23057
|
+
continue;
|
|
23058
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
23059
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
23060
|
+
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
23061
|
+
continue;
|
|
23062
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
23063
|
+
continue;
|
|
23064
|
+
effective = row;
|
|
23104
23065
|
}
|
|
23066
|
+
return effective;
|
|
23105
23067
|
}
|
|
23106
|
-
var
|
|
23107
|
-
|
|
23108
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
23111
|
-
|
|
23112
|
-
|
|
23113
|
-
|
|
23114
|
-
|
|
23115
|
-
|
|
23116
|
-
|
|
23117
|
-
|
|
23118
|
-
|
|
23119
|
-
|
|
23120
|
-
|
|
23121
|
-
|
|
23122
|
-
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
|
|
23068
|
+
var restackExact = new WeakMap;
|
|
23069
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
23070
|
+
if (!from || !to)
|
|
23071
|
+
return false;
|
|
23072
|
+
const exact = (row, fromHead, head) => {
|
|
23073
|
+
if (exactIn === null)
|
|
23074
|
+
return true;
|
|
23075
|
+
const cwd = exactIn;
|
|
23076
|
+
let known = restackExact.get(row);
|
|
23077
|
+
if (known === undefined) {
|
|
23078
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
23079
|
+
from: fromHead,
|
|
23080
|
+
to: head,
|
|
23081
|
+
fromBase: row.fromBase,
|
|
23082
|
+
toBase: row.toBase
|
|
23083
|
+
});
|
|
23084
|
+
restackExact.set(row, known);
|
|
23085
|
+
}
|
|
23086
|
+
return known;
|
|
23087
|
+
};
|
|
23088
|
+
const edges = new Map;
|
|
23089
|
+
for (const row of rows)
|
|
23090
|
+
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))
|
|
23091
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
23092
|
+
const seen = new Set([from]);
|
|
23093
|
+
const queue = [from];
|
|
23094
|
+
while (queue.length) {
|
|
23095
|
+
const next = queue.shift();
|
|
23096
|
+
if (next === to)
|
|
23097
|
+
return true;
|
|
23098
|
+
for (const target of edges.get(next) ?? [])
|
|
23099
|
+
if (!seen.has(target)) {
|
|
23100
|
+
seen.add(target);
|
|
23101
|
+
queue.push(target);
|
|
23126
23102
|
}
|
|
23127
|
-
if (before && after && cosmeticChange(entry.path, before, after))
|
|
23128
|
-
out.add(entry.path);
|
|
23129
|
-
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
23130
|
-
opaque.add(entry.path);
|
|
23131
23103
|
}
|
|
23132
|
-
return
|
|
23104
|
+
return false;
|
|
23133
23105
|
}
|
|
23134
|
-
function
|
|
23135
|
-
const
|
|
23136
|
-
const
|
|
23137
|
-
|
|
23138
|
-
|
|
23139
|
-
const
|
|
23140
|
-
|
|
23141
|
-
|
|
23142
|
-
|
|
23143
|
-
|
|
23106
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
23107
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
23108
|
+
const head = branchHead(cwd, branch);
|
|
23109
|
+
const fallbackBase = defaultBase(cwd);
|
|
23110
|
+
const scope = branchScope(cwd, branch);
|
|
23111
|
+
const keyCache = new Map;
|
|
23112
|
+
const keysAt = (base, at) => {
|
|
23113
|
+
if (!base || !at)
|
|
23114
|
+
return { patchId: null, diffHash: null };
|
|
23115
|
+
const cacheKey = `${base}\x00${at}`;
|
|
23116
|
+
let key = keyCache.get(cacheKey);
|
|
23117
|
+
if (!key) {
|
|
23118
|
+
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
23119
|
+
keyCache.set(cacheKey, key);
|
|
23120
|
+
}
|
|
23121
|
+
return key;
|
|
23122
|
+
};
|
|
23123
|
+
const keyFor = (base) => keysAt(base, head);
|
|
23124
|
+
const driftCache = new Map;
|
|
23125
|
+
const driftOf = (row) => {
|
|
23126
|
+
if (driftCache.has(row))
|
|
23127
|
+
return driftCache.get(row) ?? null;
|
|
23128
|
+
let drift = null;
|
|
23129
|
+
const toBase = row.base ?? fallbackBase;
|
|
23130
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
23131
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
23132
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
23133
|
+
drift = verdictDrift(cwd, {
|
|
23134
|
+
from: row.head,
|
|
23135
|
+
to: head,
|
|
23136
|
+
fromBase,
|
|
23137
|
+
toBase: toBaseSha,
|
|
23138
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
23139
|
+
});
|
|
23140
|
+
driftCache.set(row, drift);
|
|
23141
|
+
return drift;
|
|
23142
|
+
};
|
|
23143
|
+
const exactCache = new Map;
|
|
23144
|
+
const exactCarry = (row) => {
|
|
23145
|
+
if (FAILING.has(String(row.result)))
|
|
23146
|
+
return true;
|
|
23147
|
+
let exact = exactCache.get(row);
|
|
23148
|
+
if (exact === undefined) {
|
|
23149
|
+
const toBase = row.base ?? fallbackBase;
|
|
23150
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
23151
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
23152
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
23153
|
+
exactCache.set(row, exact);
|
|
23154
|
+
}
|
|
23155
|
+
return exact;
|
|
23156
|
+
};
|
|
23157
|
+
const keyBasis = (row) => {
|
|
23158
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
23159
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
23160
|
+
};
|
|
23161
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
23162
|
+
const carryOf = (row) => {
|
|
23163
|
+
if (keyBasis(row) !== "stale")
|
|
23164
|
+
return null;
|
|
23165
|
+
if (restacked(row))
|
|
23166
|
+
return null;
|
|
23167
|
+
const drift = driftOf(row);
|
|
23168
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
23169
|
+
};
|
|
23170
|
+
const basisOf = (row) => {
|
|
23171
|
+
const basis = keyBasis(row);
|
|
23172
|
+
if (basis !== "stale")
|
|
23173
|
+
return basis;
|
|
23174
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
23175
|
+
};
|
|
23176
|
+
const sameCode = (failed, row) => {
|
|
23177
|
+
if (failed.head && failed.head === row.head)
|
|
23178
|
+
return true;
|
|
23179
|
+
const base = failed.base ?? fallbackBase;
|
|
23180
|
+
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
23181
|
+
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
23182
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
23183
|
+
};
|
|
23184
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
23185
|
+
const failureAuthorCache = new Map;
|
|
23186
|
+
const failureAuthors = (row) => {
|
|
23187
|
+
let set = failureAuthorCache.get(row.seq);
|
|
23188
|
+
if (!set) {
|
|
23189
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
23190
|
+
failureAuthorCache.set(row.seq, set);
|
|
23191
|
+
}
|
|
23192
|
+
return set;
|
|
23193
|
+
};
|
|
23194
|
+
const reasonCache = new Map;
|
|
23195
|
+
const reasonsOf = (row) => {
|
|
23196
|
+
let reasons = reasonCache.get(row);
|
|
23197
|
+
if (!reasons) {
|
|
23198
|
+
const failing = FAILING.has(String(row.result));
|
|
23199
|
+
reasons = [
|
|
23200
|
+
...trustReasons(cwd, row),
|
|
23201
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
23202
|
+
];
|
|
23203
|
+
reasonCache.set(row, reasons);
|
|
23204
|
+
}
|
|
23205
|
+
return reasons;
|
|
23206
|
+
};
|
|
23207
|
+
const retired = new Set;
|
|
23208
|
+
for (const row of rows)
|
|
23209
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
23210
|
+
retired.add(row.supersedes);
|
|
23211
|
+
const byKind = new Map;
|
|
23212
|
+
for (const row of rows)
|
|
23213
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
23214
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
23215
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
23216
|
+
}
|
|
23217
|
+
const verdicts = [];
|
|
23218
|
+
const warnings = [];
|
|
23219
|
+
for (const [kind, list] of byKind) {
|
|
23220
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
23221
|
+
if (!row)
|
|
23222
|
+
continue;
|
|
23223
|
+
const basis = basisOf(row);
|
|
23224
|
+
const current = basis !== "stale";
|
|
23225
|
+
const independence = reasonsOf(row);
|
|
23226
|
+
const reasons = [
|
|
23227
|
+
...current ? [] : ["stale"],
|
|
23228
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
23229
|
+
...independence
|
|
23230
|
+
];
|
|
23231
|
+
verdicts.push({
|
|
23232
|
+
kind,
|
|
23233
|
+
basis,
|
|
23234
|
+
current,
|
|
23235
|
+
independent: independence.length === 0,
|
|
23236
|
+
accepted: reasons.length === 0,
|
|
23237
|
+
reasons,
|
|
23238
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
23239
|
+
verdict: row
|
|
23240
|
+
});
|
|
23241
|
+
if (reasons.length === 0) {
|
|
23242
|
+
for (const later of list)
|
|
23243
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
23244
|
+
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
23245
|
+
}
|
|
23144
23246
|
}
|
|
23145
|
-
|
|
23146
|
-
|
|
23147
|
-
|
|
23148
|
-
|
|
23149
|
-
|
|
23150
|
-
|
|
23151
|
-
|
|
23152
|
-
|
|
23153
|
-
|
|
23154
|
-
|
|
23155
|
-
...entry,
|
|
23156
|
-
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
23157
|
-
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
23158
|
-
})),
|
|
23159
|
-
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
23160
|
-
];
|
|
23161
|
-
const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
|
|
23162
|
-
return files.map((entry) => ({
|
|
23163
|
-
...entry,
|
|
23164
|
-
generated: generated.has(entry.path)
|
|
23165
|
-
}));
|
|
23166
|
-
}
|
|
23167
|
-
function branchLane(cwd, options) {
|
|
23168
|
-
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
23169
|
-
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
23170
|
-
const head = options.workingTree ? null : options.head;
|
|
23171
|
-
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
23172
|
-
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
23173
|
-
const riskTier = options.judgment?.riskTier ?? null;
|
|
23174
|
-
if (!fork || !files) {
|
|
23175
|
-
const high = riskTier === "high";
|
|
23176
|
-
return {
|
|
23177
|
-
lane: high ? "full" : "focused",
|
|
23178
|
-
reasons: [
|
|
23179
|
-
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
23180
|
-
...high ? ["the task judgment records high risk"] : []
|
|
23181
|
-
],
|
|
23182
|
-
lines: 0,
|
|
23183
|
-
files: 0,
|
|
23184
|
-
riskFiles: [],
|
|
23185
|
-
base,
|
|
23186
|
-
head: options.head
|
|
23187
|
-
};
|
|
23247
|
+
for (const row of rows)
|
|
23248
|
+
if (row.integrity === "unverified" && scope(row))
|
|
23249
|
+
warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
|
|
23250
|
+
const tampered = ledgerTampering(cwd);
|
|
23251
|
+
if (tampered.length) {
|
|
23252
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
23253
|
+
for (const entry of verdicts) {
|
|
23254
|
+
entry.accepted = false;
|
|
23255
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
23256
|
+
}
|
|
23188
23257
|
}
|
|
23189
|
-
|
|
23258
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
23259
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
23260
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
23261
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
23262
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
23263
|
+
accepted: false,
|
|
23264
|
+
verdict: failing?.verdict ?? null,
|
|
23265
|
+
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
23266
|
+
};
|
|
23267
|
+
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))));
|
|
23268
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
23269
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
23270
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
23271
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
23272
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
23190
23273
|
return {
|
|
23191
|
-
|
|
23192
|
-
|
|
23193
|
-
|
|
23194
|
-
|
|
23195
|
-
|
|
23196
|
-
base,
|
|
23197
|
-
|
|
23274
|
+
branch,
|
|
23275
|
+
head,
|
|
23276
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
23277
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
23278
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
23279
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
23280
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
23281
|
+
carry: carryEntry?.carry ?? null,
|
|
23282
|
+
reverify,
|
|
23283
|
+
accepted,
|
|
23284
|
+
verdicts,
|
|
23285
|
+
authors: [...authors].toSorted(),
|
|
23286
|
+
warnings
|
|
23198
23287
|
};
|
|
23199
23288
|
}
|
|
23200
|
-
var
|
|
23289
|
+
var OBSERVED_ONLY = new Set([
|
|
23290
|
+
"check",
|
|
23291
|
+
"merge.unverified",
|
|
23292
|
+
"policy.judged",
|
|
23293
|
+
...PR_ROW_TYPES
|
|
23294
|
+
]);
|
|
23201
23295
|
|
|
23202
23296
|
// packages/workit-core/src/core/policy/derive.ts
|
|
23203
23297
|
var RULES = {
|
|
@@ -23352,7 +23446,7 @@ var productDecision = (task, ledger) => {
|
|
|
23352
23446
|
};
|
|
23353
23447
|
};
|
|
23354
23448
|
var planRecorded = (task, root) => {
|
|
23355
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(
|
|
23449
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path16.resolve(root, ref.path)));
|
|
23356
23450
|
if (cited)
|
|
23357
23451
|
return {
|
|
23358
23452
|
met: true,
|
|
@@ -23419,14 +23513,14 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
23419
23513
|
|
|
23420
23514
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
23421
23515
|
var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
|
|
23422
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
23423
|
-
var relative = (root, candidate) =>
|
|
23516
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path17.sep}`);
|
|
23517
|
+
var relative = (root, candidate) => path17.relative(root, candidate).split(path17.sep).join("/") || ".";
|
|
23424
23518
|
var canonicalPath = (value) => {
|
|
23425
23519
|
if (typeof value !== "string" || !value)
|
|
23426
23520
|
return null;
|
|
23427
23521
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
23428
23522
|
return null;
|
|
23429
|
-
const normalized =
|
|
23523
|
+
const normalized = path17.posix.normalize(value);
|
|
23430
23524
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
23431
23525
|
return null;
|
|
23432
23526
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -23462,7 +23556,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
23462
23556
|
return left.length - right.length;
|
|
23463
23557
|
};
|
|
23464
23558
|
var gitPaths = (root) => {
|
|
23465
|
-
const result =
|
|
23559
|
+
const result = spawnSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
23466
23560
|
cwd: root,
|
|
23467
23561
|
encoding: "buffer"
|
|
23468
23562
|
});
|
|
@@ -23474,21 +23568,21 @@ var gitPaths = (root) => {
|
|
|
23474
23568
|
git: !/not a git repository/i.test(stderr)
|
|
23475
23569
|
};
|
|
23476
23570
|
}
|
|
23477
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
23478
|
-
const stagedDeleted =
|
|
23571
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")) : [];
|
|
23572
|
+
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
23479
23573
|
if (stagedDeleted.status !== 0) {
|
|
23480
23574
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
23481
23575
|
if (!/not a git repository/i.test(stderr))
|
|
23482
23576
|
return { paths, uncertain: true, git: true };
|
|
23483
23577
|
} else if (stagedDeleted.stdout) {
|
|
23484
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
23578
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")));
|
|
23485
23579
|
}
|
|
23486
23580
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
23487
23581
|
};
|
|
23488
23582
|
var walk = (root, directory, output) => {
|
|
23489
23583
|
let names;
|
|
23490
23584
|
try {
|
|
23491
|
-
names =
|
|
23585
|
+
names = fs13.readdirSync(directory);
|
|
23492
23586
|
} catch {
|
|
23493
23587
|
return true;
|
|
23494
23588
|
}
|
|
@@ -23496,10 +23590,10 @@ var walk = (root, directory, output) => {
|
|
|
23496
23590
|
for (const name of names) {
|
|
23497
23591
|
if (name === ".git" || name === ".workit")
|
|
23498
23592
|
continue;
|
|
23499
|
-
const target =
|
|
23593
|
+
const target = path17.join(directory, name);
|
|
23500
23594
|
let stat;
|
|
23501
23595
|
try {
|
|
23502
|
-
stat =
|
|
23596
|
+
stat = fs13.lstatSync(target);
|
|
23503
23597
|
} catch {
|
|
23504
23598
|
uncertain = true;
|
|
23505
23599
|
continue;
|
|
@@ -23514,14 +23608,14 @@ var walk = (root, directory, output) => {
|
|
|
23514
23608
|
var scopeRoots = (root, scope) => {
|
|
23515
23609
|
const roots = [];
|
|
23516
23610
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
23517
|
-
const target =
|
|
23611
|
+
const target = path17.resolve(root, item);
|
|
23518
23612
|
if (!inside(root, target))
|
|
23519
23613
|
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` }] });
|
|
23520
23614
|
let ancestor = target;
|
|
23521
|
-
while (!
|
|
23522
|
-
ancestor =
|
|
23615
|
+
while (!fs13.existsSync(ancestor) && ancestor !== root)
|
|
23616
|
+
ancestor = path17.dirname(ancestor);
|
|
23523
23617
|
try {
|
|
23524
|
-
if (!inside(root,
|
|
23618
|
+
if (!inside(root, fs13.realpathSync(ancestor)))
|
|
23525
23619
|
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` }] });
|
|
23526
23620
|
} catch {
|
|
23527
23621
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -23533,7 +23627,7 @@ var scopeRoots = (root, scope) => {
|
|
|
23533
23627
|
function captureCandidate(root, scope, environment = []) {
|
|
23534
23628
|
let checkout;
|
|
23535
23629
|
try {
|
|
23536
|
-
checkout =
|
|
23630
|
+
checkout = fs13.realpathSync(root);
|
|
23537
23631
|
} catch {
|
|
23538
23632
|
return failure2("invalid_input", "candidate root does not exist");
|
|
23539
23633
|
}
|
|
@@ -23551,11 +23645,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
23551
23645
|
if (item === ".git" || item === ".workit")
|
|
23552
23646
|
continue;
|
|
23553
23647
|
names.add(item);
|
|
23554
|
-
if (!
|
|
23648
|
+
if (!fs13.existsSync(target))
|
|
23555
23649
|
continue;
|
|
23556
23650
|
let stat;
|
|
23557
23651
|
try {
|
|
23558
|
-
stat =
|
|
23652
|
+
stat = fs13.lstatSync(target);
|
|
23559
23653
|
} catch {
|
|
23560
23654
|
uncertain = true;
|
|
23561
23655
|
continue;
|
|
@@ -23571,17 +23665,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
23571
23665
|
for (const item of [...names].toSorted()) {
|
|
23572
23666
|
if (!scopeMatches(item, normalizedScope))
|
|
23573
23667
|
continue;
|
|
23574
|
-
const target =
|
|
23668
|
+
const target = path17.join(checkout, item);
|
|
23575
23669
|
try {
|
|
23576
|
-
const stat =
|
|
23670
|
+
const stat = fs13.lstatSync(target);
|
|
23577
23671
|
if (stat.isSymbolicLink()) {
|
|
23578
|
-
const link =
|
|
23672
|
+
const link = fs13.readlinkSync(target);
|
|
23579
23673
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
23580
23674
|
} else if (stat.isFile()) {
|
|
23581
23675
|
files.push({
|
|
23582
23676
|
path: item,
|
|
23583
23677
|
kind: "file",
|
|
23584
|
-
digest: digestBytes(
|
|
23678
|
+
digest: digestBytes(fs13.readFileSync(target)),
|
|
23585
23679
|
executable: (stat.mode & 73) !== 0
|
|
23586
23680
|
});
|
|
23587
23681
|
} else if (!stat.isDirectory()) {
|
|
@@ -23606,7 +23700,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
23606
23700
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
23607
23701
|
if (values.some(({ value }) => value === null))
|
|
23608
23702
|
completeness = "uncertain";
|
|
23609
|
-
const headResult =
|
|
23703
|
+
const headResult = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
23610
23704
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
23611
23705
|
completeness = "uncertain";
|
|
23612
23706
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -23746,7 +23840,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
23746
23840
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
23747
23841
|
let root;
|
|
23748
23842
|
try {
|
|
23749
|
-
root =
|
|
23843
|
+
root = fs13.realpathSync(checkoutRoot);
|
|
23750
23844
|
} catch {
|
|
23751
23845
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
23752
23846
|
}
|
|
@@ -23755,23 +23849,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
23755
23849
|
continue;
|
|
23756
23850
|
if (!reference.digest)
|
|
23757
23851
|
return failure2("invalid_input", "document references require a byte digest");
|
|
23758
|
-
const target =
|
|
23759
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
23852
|
+
const target = path17.resolve(root, reference.path);
|
|
23853
|
+
if (target !== root && !target.startsWith(`${root}${path17.sep}`))
|
|
23760
23854
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
23761
23855
|
fields: [
|
|
23762
23856
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
23763
23857
|
]
|
|
23764
23858
|
});
|
|
23765
23859
|
try {
|
|
23766
|
-
const below =
|
|
23860
|
+
const below = path17.relative(root, target);
|
|
23767
23861
|
let current = root;
|
|
23768
|
-
for (const segment of below.split(
|
|
23862
|
+
for (const segment of below.split(path17.sep)) {
|
|
23769
23863
|
if (!segment)
|
|
23770
23864
|
continue;
|
|
23771
|
-
current =
|
|
23772
|
-
const stat =
|
|
23773
|
-
const real =
|
|
23774
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
23865
|
+
current = path17.join(current, segment);
|
|
23866
|
+
const stat = fs13.lstatSync(current);
|
|
23867
|
+
const real = fs13.realpathSync(current);
|
|
23868
|
+
if (real !== root && !real.startsWith(`${root}${path17.sep}`))
|
|
23775
23869
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
23776
23870
|
fields: [
|
|
23777
23871
|
{
|
|
@@ -23787,7 +23881,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
23787
23881
|
if (current !== target && !stat.isDirectory())
|
|
23788
23882
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
23789
23883
|
}
|
|
23790
|
-
if (digestBytes(
|
|
23884
|
+
if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
|
|
23791
23885
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
23792
23886
|
} catch {
|
|
23793
23887
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -24630,7 +24724,7 @@ var judgeInputSummary = (raw) => {
|
|
|
24630
24724
|
|
|
24631
24725
|
// packages/workit-core/src/autonomy.ts
|
|
24632
24726
|
import os4 from "node:os";
|
|
24633
|
-
import
|
|
24727
|
+
import path18 from "node:path";
|
|
24634
24728
|
var accountHome = () => {
|
|
24635
24729
|
try {
|
|
24636
24730
|
return os4.userInfo().homedir;
|
|
@@ -24639,10 +24733,10 @@ var accountHome = () => {
|
|
|
24639
24733
|
}
|
|
24640
24734
|
};
|
|
24641
24735
|
var grantsHome = { resolve: accountHome };
|
|
24642
|
-
var grantsDir = () =>
|
|
24736
|
+
var grantsDir = () => path18.join(grantsHome.resolve(), ".config", "workit");
|
|
24643
24737
|
var grantsOverride = () => {
|
|
24644
|
-
const configured =
|
|
24645
|
-
return configured ===
|
|
24738
|
+
const configured = path18.resolve(resolveConfigDir());
|
|
24739
|
+
return configured === path18.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 ${path18.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
24646
24740
|
};
|
|
24647
24741
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
24648
24742
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -26451,9 +26545,9 @@ class WorkitCore {
|
|
|
26451
26545
|
}
|
|
26452
26546
|
}
|
|
26453
26547
|
// packages/workit-core/src/core/repo-context.ts
|
|
26454
|
-
import { spawnSync as
|
|
26548
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
26455
26549
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26456
|
-
import
|
|
26550
|
+
import path19 from "node:path";
|
|
26457
26551
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26458
26552
|
try {
|
|
26459
26553
|
gitRevisionParts(value);
|
|
@@ -26480,7 +26574,7 @@ var invalidRange = (cwd) => ({
|
|
|
26480
26574
|
cwd
|
|
26481
26575
|
});
|
|
26482
26576
|
var runGit = (cwd, args) => {
|
|
26483
|
-
const result =
|
|
26577
|
+
const result = spawnSync11("git", args, { cwd, encoding: "utf8" });
|
|
26484
26578
|
return {
|
|
26485
26579
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
26486
26580
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -26587,7 +26681,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26587
26681
|
]) {
|
|
26588
26682
|
const parts = rel.split("/");
|
|
26589
26683
|
const base = parts[parts.length - 1];
|
|
26590
|
-
const dir = parts.length > 1 ?
|
|
26684
|
+
const dir = parts.length > 1 ? path19.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26591
26685
|
const actual = probeCaseInsensitive(dir, base);
|
|
26592
26686
|
if (actual)
|
|
26593
26687
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26618,7 +26712,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26618
26712
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26619
26713
|
};
|
|
26620
26714
|
var packageScripts = (cwd, keys) => {
|
|
26621
|
-
const pkgPath =
|
|
26715
|
+
const pkgPath = path19.join(cwd, "package.json");
|
|
26622
26716
|
if (!existsSync7(pkgPath))
|
|
26623
26717
|
return [];
|
|
26624
26718
|
try {
|
|
@@ -26644,9 +26738,9 @@ var documentationFiles = (cwd) => {
|
|
|
26644
26738
|
if (entry.isDirectory()) {
|
|
26645
26739
|
if (excluded.has(entry.name))
|
|
26646
26740
|
continue;
|
|
26647
|
-
walk(
|
|
26741
|
+
walk(path19.join(dir, entry.name), depth + 1);
|
|
26648
26742
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26649
|
-
matches.push("./" +
|
|
26743
|
+
matches.push("./" + path19.relative(cwd, path19.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26650
26744
|
}
|
|
26651
26745
|
}
|
|
26652
26746
|
};
|
|
@@ -26662,7 +26756,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
26662
26756
|
`);
|
|
26663
26757
|
};
|
|
26664
26758
|
function prReadyContext(root, range) {
|
|
26665
|
-
const cwd =
|
|
26759
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
26666
26760
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26667
26761
|
return invalidRange(cwd);
|
|
26668
26762
|
let stdout = "";
|
|
@@ -26720,7 +26814,7 @@ ${status.stderr}` : "");
|
|
|
26720
26814
|
stdout += `template_path: ${template}
|
|
26721
26815
|
|
|
26722
26816
|
`;
|
|
26723
|
-
stdout += readTrimmed2(
|
|
26817
|
+
stdout += readTrimmed2(path19.join(cwd, template), 220);
|
|
26724
26818
|
} else {
|
|
26725
26819
|
stdout += `template_path: none
|
|
26726
26820
|
|
|
@@ -26730,7 +26824,7 @@ ${status.stderr}` : "");
|
|
|
26730
26824
|
stdout += `
|
|
26731
26825
|
`;
|
|
26732
26826
|
stdout += printSection("Recent Validation Signals");
|
|
26733
|
-
if (existsSync7(
|
|
26827
|
+
if (existsSync7(path19.join(cwd, "package.json"))) {
|
|
26734
26828
|
stdout += `package.json detected. Common scripts:
|
|
26735
26829
|
`;
|
|
26736
26830
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26739,7 +26833,7 @@ ${status.stderr}` : "");
|
|
|
26739
26833
|
`) + `
|
|
26740
26834
|
`;
|
|
26741
26835
|
}
|
|
26742
|
-
if (existsSync7(
|
|
26836
|
+
if (existsSync7(path19.join(cwd, "Cargo.toml")) || existsSync7(path19.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26743
26837
|
stdout += `Rust project detected.
|
|
26744
26838
|
`;
|
|
26745
26839
|
}
|
|
@@ -26772,7 +26866,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26772
26866
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
26773
26867
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26774
26868
|
function changelogContext(root, range) {
|
|
26775
|
-
const cwd =
|
|
26869
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
26776
26870
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26777
26871
|
return invalidRange(cwd);
|
|
26778
26872
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26790,7 +26884,7 @@ function changelogContext(root, range) {
|
|
|
26790
26884
|
stdout += CHANGELOG_RULES + `
|
|
26791
26885
|
`;
|
|
26792
26886
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26793
|
-
const changelogPath =
|
|
26887
|
+
const changelogPath = path19.join(cwd, "CHANGELOG.md");
|
|
26794
26888
|
if (existsSync7(changelogPath)) {
|
|
26795
26889
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
26796
26890
|
`;
|
|
@@ -26810,7 +26904,7 @@ function changelogContext(root, range) {
|
|
|
26810
26904
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26811
26905
|
}
|
|
26812
26906
|
function docsRefreshContext(root, range) {
|
|
26813
|
-
const cwd =
|
|
26907
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
26814
26908
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26815
26909
|
return invalidRange(cwd);
|
|
26816
26910
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26832,7 +26926,7 @@ function docsRefreshContext(root, range) {
|
|
|
26832
26926
|
`) + `
|
|
26833
26927
|
`;
|
|
26834
26928
|
stdout += printSection("README Preview");
|
|
26835
|
-
const readme =
|
|
26929
|
+
const readme = path19.join(cwd, "README.md");
|
|
26836
26930
|
if (existsSync7(readme)) {
|
|
26837
26931
|
stdout += readTrimmed2(readme, 220) + `
|
|
26838
26932
|
`;
|
|
@@ -26841,7 +26935,7 @@ function docsRefreshContext(root, range) {
|
|
|
26841
26935
|
`;
|
|
26842
26936
|
}
|
|
26843
26937
|
stdout += printSection("Package Scripts");
|
|
26844
|
-
const pkgPath =
|
|
26938
|
+
const pkgPath = path19.join(cwd, "package.json");
|
|
26845
26939
|
if (existsSync7(pkgPath)) {
|
|
26846
26940
|
try {
|
|
26847
26941
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26855,7 +26949,7 @@ function docsRefreshContext(root, range) {
|
|
|
26855
26949
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26856
26950
|
}
|
|
26857
26951
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26858
|
-
const cwd =
|
|
26952
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
26859
26953
|
if (!rangeOrTag) {
|
|
26860
26954
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26861
26955
|
`, exitCode: 1, cwd };
|
|
@@ -26893,7 +26987,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26893
26987
|
`;
|
|
26894
26988
|
stdout += printSection("Existing Release Files");
|
|
26895
26989
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26896
|
-
if (existsSync7(
|
|
26990
|
+
if (existsSync7(path19.join(cwd, rel)))
|
|
26897
26991
|
stdout += rel + `
|
|
26898
26992
|
`;
|
|
26899
26993
|
}
|
|
@@ -26922,6 +27016,46 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26922
27016
|
|
|
26923
27017
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
26924
27018
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
27019
|
+
|
|
27020
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
27021
|
+
import fs14 from "node:fs";
|
|
27022
|
+
import path20 from "node:path";
|
|
27023
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
27024
|
+
// packages/workit-core/src/forge/pr-body.ts
|
|
27025
|
+
function parseGhIssue(value) {
|
|
27026
|
+
const m = /issues\/(\d+)/.exec(value);
|
|
27027
|
+
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
27028
|
+
}
|
|
27029
|
+
|
|
27030
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
27031
|
+
function whichOnPath(tool) {
|
|
27032
|
+
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
27033
|
+
for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
|
|
27034
|
+
if (!dir)
|
|
27035
|
+
continue;
|
|
27036
|
+
for (const name of names) {
|
|
27037
|
+
const candidate = path20.join(dir, name);
|
|
27038
|
+
try {
|
|
27039
|
+
fs14.accessSync(candidate, fs14.constants.X_OK);
|
|
27040
|
+
return candidate;
|
|
27041
|
+
} catch {}
|
|
27042
|
+
}
|
|
27043
|
+
}
|
|
27044
|
+
return null;
|
|
27045
|
+
}
|
|
27046
|
+
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
27047
|
+
var pushRemote = (cwd) => {
|
|
27048
|
+
const result = spawnSync12("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
27049
|
+
cwd,
|
|
27050
|
+
encoding: "utf8"
|
|
27051
|
+
});
|
|
27052
|
+
if (result.status !== 0)
|
|
27053
|
+
return null;
|
|
27054
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
27055
|
+
return urls.length === 1 ? urls[0] : null;
|
|
27056
|
+
};
|
|
27057
|
+
|
|
27058
|
+
// packages/workit-core/src/core/tracker-issues.ts
|
|
26925
27059
|
var repoPathFromRemote = (remote) => {
|
|
26926
27060
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
26927
27061
|
if (!rest)
|