@brainervirus/workit-cursor 8.24.0 → 8.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +2203 -1952
- package/dist/mcp-server.js +1655 -1494
- package/dist/workit-hook.js +2223 -1950
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +4 -4
- package/skills/workit-fanout/references/brief.md +4 -4
- package/skills/workit-fanout/references/orchestration.md +26 -17
- package/skills/workit-implement/SKILL.md +1 -1
- package/skills/workit-review/SKILL.md +4 -1
- package/skills/workit-ship/SKILL.md +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -17245,6 +17245,7 @@ var workspaceConfigSchema = object2({
|
|
|
17245
17245
|
trailers: boolean2().optional(),
|
|
17246
17246
|
liveSessionGuard: boolean2().optional(),
|
|
17247
17247
|
fanout: object2({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
17248
|
+
models: object2({ roles: unknown().optional() }).passthrough().optional(),
|
|
17248
17249
|
autonomy: object2(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
17249
17250
|
kind,
|
|
17250
17251
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -17271,7 +17272,10 @@ var workspaceConfigSchema = object2({
|
|
|
17271
17272
|
});
|
|
17272
17273
|
}
|
|
17273
17274
|
});
|
|
17274
|
-
var workspacesFileSchema = object2({
|
|
17275
|
+
var workspacesFileSchema = object2({
|
|
17276
|
+
workspaces: array(workspaceConfigSchema).optional(),
|
|
17277
|
+
models: object2({ roles: unknown().optional() }).passthrough().optional()
|
|
17278
|
+
}).passthrough().superRefine((file, context) => {
|
|
17275
17279
|
const names = new Set;
|
|
17276
17280
|
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
17277
17281
|
if (names.has(workspace.name)) {
|
|
@@ -17781,6 +17785,26 @@ function resolveRef(cwd, ref) {
|
|
|
17781
17785
|
return null;
|
|
17782
17786
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
17783
17787
|
}
|
|
17788
|
+
function originHeadRef(cwd) {
|
|
17789
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
17790
|
+
}
|
|
17791
|
+
function ownChangeDrift(cwd, range) {
|
|
17792
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
17793
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
17794
|
+
if (!before || !after)
|
|
17795
|
+
return null;
|
|
17796
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
17797
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
17798
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
17799
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
17800
|
+
}
|
|
17801
|
+
function sameOwnChange(cwd, range) {
|
|
17802
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
17803
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
17804
|
+
if (!fromFork || !toFork)
|
|
17805
|
+
return false;
|
|
17806
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
17807
|
+
}
|
|
17784
17808
|
var LANE_DIFF = [
|
|
17785
17809
|
"-c",
|
|
17786
17810
|
"core.quotePath=false",
|
|
@@ -21407,9 +21431,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
21407
21431
|
|
|
21408
21432
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
21409
21433
|
import { createHash as createHash10 } from "node:crypto";
|
|
21410
|
-
import * as
|
|
21411
|
-
import { spawnSync as
|
|
21412
|
-
import
|
|
21434
|
+
import * as fs13 from "node:fs";
|
|
21435
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
21436
|
+
import path17 from "node:path";
|
|
21413
21437
|
|
|
21414
21438
|
// packages/workit-core/src/check-config.ts
|
|
21415
21439
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
@@ -21630,68 +21654,20 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
21630
21654
|
|
|
21631
21655
|
// packages/workit-core/src/core/policy/derive.ts
|
|
21632
21656
|
import { existsSync as existsSync5 } from "node:fs";
|
|
21633
|
-
import
|
|
21657
|
+
import path16 from "node:path";
|
|
21634
21658
|
|
|
21635
21659
|
// packages/workit-core/src/ledger.ts
|
|
21636
21660
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
21637
21661
|
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
21638
21662
|
import fs12 from "node:fs";
|
|
21663
|
+
import path15 from "node:path";
|
|
21664
|
+
|
|
21665
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
21666
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
21639
21667
|
import path14 from "node:path";
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
21643
|
-
var FAILING = new Set(["failed", "blocked"]);
|
|
21644
|
-
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
21645
|
-
var PR_ROW_TYPES = new Set([
|
|
21646
|
-
"pr.created",
|
|
21647
|
-
"pr.status",
|
|
21648
|
-
"pr.merged",
|
|
21649
|
-
"pr.linked"
|
|
21650
|
-
]);
|
|
21651
|
-
var err = (code, error, unblock) => ({
|
|
21652
|
-
ok: false,
|
|
21653
|
-
code,
|
|
21654
|
-
error,
|
|
21655
|
-
...unblock ? { unblock } : {}
|
|
21656
|
-
});
|
|
21657
|
-
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
21658
|
-
const run = spawnSync9("git", args, {
|
|
21659
|
-
cwd,
|
|
21660
|
-
encoding: "utf8",
|
|
21661
|
-
timeout: timeoutMs,
|
|
21662
|
-
killSignal: "SIGKILL",
|
|
21663
|
-
windowsHide: true,
|
|
21664
|
-
maxBuffer: 256 * 1024 * 1024,
|
|
21665
|
-
env: {
|
|
21666
|
-
...process.env,
|
|
21667
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
21668
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
21669
|
-
LC_ALL: "C",
|
|
21670
|
-
LANGUAGE: "C"
|
|
21671
|
-
}
|
|
21672
|
-
});
|
|
21673
|
-
return {
|
|
21674
|
-
ok: run.status === 0,
|
|
21675
|
-
stdout: run.stdout ?? "",
|
|
21676
|
-
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
21677
|
-
};
|
|
21678
|
-
};
|
|
21679
|
-
var git4 = (cwd, args) => {
|
|
21680
|
-
const run = gitRun2(cwd, args);
|
|
21681
|
-
const value = run.ok ? run.stdout.trim() : "";
|
|
21682
|
-
return value ? value : null;
|
|
21683
|
-
};
|
|
21668
|
+
|
|
21669
|
+
// packages/workit-core/src/git/default-base.ts
|
|
21684
21670
|
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
21685
|
-
function resolveCommit(cwd, ref) {
|
|
21686
|
-
if (!safeRef(ref))
|
|
21687
|
-
return null;
|
|
21688
|
-
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
21689
|
-
}
|
|
21690
|
-
function branchHead(cwd, branch) {
|
|
21691
|
-
if (!safeRef(branch))
|
|
21692
|
-
return null;
|
|
21693
|
-
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
21694
|
-
}
|
|
21695
21671
|
var trackTrunk = (cwd) => {
|
|
21696
21672
|
try {
|
|
21697
21673
|
const resolved = vcsConfig("resolve", cwd);
|
|
@@ -21707,11 +21683,11 @@ function defaultBase(cwd) {
|
|
|
21707
21683
|
const trunk = trackTrunk(cwd);
|
|
21708
21684
|
if (trunk) {
|
|
21709
21685
|
for (const candidate of [`origin/${trunk}`, trunk])
|
|
21710
|
-
if (
|
|
21686
|
+
if (resolveRef(cwd, candidate))
|
|
21711
21687
|
return candidate;
|
|
21712
21688
|
}
|
|
21713
|
-
const originHead =
|
|
21714
|
-
if (originHead &&
|
|
21689
|
+
const originHead = originHeadRef(cwd);
|
|
21690
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
21715
21691
|
return originHead;
|
|
21716
21692
|
for (const candidate of [
|
|
21717
21693
|
"origin/main",
|
|
@@ -21721,1489 +21697,1611 @@ function defaultBase(cwd) {
|
|
|
21721
21697
|
"origin/develop",
|
|
21722
21698
|
"develop"
|
|
21723
21699
|
])
|
|
21724
|
-
if (
|
|
21700
|
+
if (resolveRef(cwd, candidate))
|
|
21725
21701
|
return candidate;
|
|
21726
21702
|
return null;
|
|
21727
21703
|
}
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
"diff",
|
|
21733
|
-
"-U3",
|
|
21734
|
-
"--diff-algorithm=myers",
|
|
21735
|
-
"--indent-heuristic",
|
|
21736
|
-
"--no-color",
|
|
21737
|
-
"--no-ext-diff",
|
|
21738
|
-
"--no-textconv",
|
|
21739
|
-
"--no-renames",
|
|
21740
|
-
"--full-index",
|
|
21741
|
-
"--binary",
|
|
21742
|
-
"--src-prefix=a/",
|
|
21743
|
-
"--dst-prefix=b/",
|
|
21744
|
-
`${base}...${head}`,
|
|
21745
|
-
"--"
|
|
21746
|
-
], GIT_TIMEOUTS.worktree);
|
|
21747
|
-
if (!diff.ok || !diff.stdout)
|
|
21704
|
+
|
|
21705
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
21706
|
+
function patternRegex(dir, pattern) {
|
|
21707
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
21748
21708
|
return null;
|
|
21749
|
-
const
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
|
|
21755
|
-
|
|
21756
|
-
|
|
21757
|
-
|
|
21758
|
-
|
|
21759
|
-
|
|
21760
|
-
|
|
21761
|
-
|
|
21762
|
-
|
|
21763
|
-
|
|
21764
|
-
|
|
21765
|
-
|
|
21766
|
-
|
|
21767
|
-
|
|
21768
|
-
|
|
21769
|
-
}
|
|
21770
|
-
|
|
21771
|
-
|
|
21772
|
-
|
|
21773
|
-
|
|
21774
|
-
4266872130,
|
|
21775
|
-
4283649346,
|
|
21776
|
-
1702057286,
|
|
21777
|
-
1397113167,
|
|
21778
|
-
12805120,
|
|
21779
|
-
16914839,
|
|
21780
|
-
1196443219,
|
|
21781
|
-
198183888
|
|
21782
|
-
]);
|
|
21783
|
-
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
21784
|
-
var onNetworkFs = (dir) => {
|
|
21785
|
-
if (process.platform !== "linux")
|
|
21786
|
-
return false;
|
|
21787
|
-
try {
|
|
21788
|
-
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
21789
|
-
} catch {
|
|
21790
|
-
return false;
|
|
21709
|
+
const anchored = pattern.includes("/");
|
|
21710
|
+
const body = pattern.replace(/^\//u, "");
|
|
21711
|
+
let source = "";
|
|
21712
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
21713
|
+
const char = body[index];
|
|
21714
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
21715
|
+
const slash = body[index + 2] === "/";
|
|
21716
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
21717
|
+
index += slash ? 2 : 1;
|
|
21718
|
+
} else if (char === "*")
|
|
21719
|
+
source += "[^/]*";
|
|
21720
|
+
else if (char === "?")
|
|
21721
|
+
source += "[^/]";
|
|
21722
|
+
else if (char === "[") {
|
|
21723
|
+
const end = body.indexOf("]", index + 2);
|
|
21724
|
+
if (end < 0)
|
|
21725
|
+
return null;
|
|
21726
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
21727
|
+
source += `[${set}]`;
|
|
21728
|
+
index = end;
|
|
21729
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
21730
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
21731
|
+
index += 1;
|
|
21732
|
+
} else
|
|
21733
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
21791
21734
|
}
|
|
21792
|
-
};
|
|
21793
|
-
var LOCK_WAIT_MS = 5000;
|
|
21794
|
-
var LOCK_STALE_MS = 30000;
|
|
21795
|
-
var LOCK_OWNER = "owner";
|
|
21796
|
-
var lockToken = (lock) => {
|
|
21735
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
21797
21736
|
try {
|
|
21798
|
-
return
|
|
21737
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
21799
21738
|
} catch {
|
|
21800
21739
|
return null;
|
|
21801
21740
|
}
|
|
21802
|
-
}
|
|
21803
|
-
|
|
21804
|
-
|
|
21805
|
-
|
|
21806
|
-
|
|
21807
|
-
|
|
21808
|
-
|
|
21809
|
-
|
|
21810
|
-
|
|
21811
|
-
|
|
21812
|
-
|
|
21813
|
-
|
|
21814
|
-
|
|
21815
|
-
|
|
21816
|
-
|
|
21817
|
-
return null;
|
|
21818
|
-
throw error;
|
|
21819
|
-
}
|
|
21820
|
-
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
21821
|
-
fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
|
|
21822
|
-
return token;
|
|
21823
|
-
},
|
|
21824
|
-
release(lock, token) {
|
|
21825
|
-
if (lockToken(lock) !== token)
|
|
21826
|
-
return false;
|
|
21827
|
-
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
21828
|
-
try {
|
|
21829
|
-
fs12.renameSync(lock, tomb);
|
|
21830
|
-
} catch {
|
|
21831
|
-
return false;
|
|
21832
|
-
}
|
|
21833
|
-
if (lockToken(tomb) !== token) {
|
|
21834
|
-
try {
|
|
21835
|
-
fs12.renameSync(tomb, lock);
|
|
21836
|
-
} catch {}
|
|
21837
|
-
return false;
|
|
21838
|
-
}
|
|
21839
|
-
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
21840
|
-
return true;
|
|
21841
|
-
},
|
|
21842
|
-
token: lockToken,
|
|
21843
|
-
reap(lock, observed) {
|
|
21844
|
-
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
21845
|
-
try {
|
|
21846
|
-
fs12.renameSync(lock, tomb);
|
|
21847
|
-
} catch {
|
|
21848
|
-
return false;
|
|
21849
|
-
}
|
|
21850
|
-
if (lockToken(tomb) === observed) {
|
|
21851
|
-
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
21852
|
-
return true;
|
|
21853
|
-
}
|
|
21854
|
-
try {
|
|
21855
|
-
fs12.renameSync(tomb, lock);
|
|
21856
|
-
} catch {
|
|
21857
|
-
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
21741
|
+
}
|
|
21742
|
+
function rulesOf(dir, text) {
|
|
21743
|
+
const out = [];
|
|
21744
|
+
for (const raw of text.split(`
|
|
21745
|
+
`)) {
|
|
21746
|
+
const line = raw.trim();
|
|
21747
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
21748
|
+
continue;
|
|
21749
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
21750
|
+
let generated = null;
|
|
21751
|
+
for (const attribute of attributes) {
|
|
21752
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
21753
|
+
generated = true;
|
|
21754
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
21755
|
+
generated = false;
|
|
21858
21756
|
}
|
|
21859
|
-
|
|
21860
|
-
}
|
|
21861
|
-
};
|
|
21862
|
-
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
21863
|
-
const lock = `${file}.lock`;
|
|
21864
|
-
const deadline = Date.now() + waitMs;
|
|
21865
|
-
let token = null;
|
|
21866
|
-
for (;; ) {
|
|
21867
|
-
token = ledgerLock.acquire(lock);
|
|
21868
|
-
if (token)
|
|
21869
|
-
break;
|
|
21870
|
-
const observed = lockToken(lock);
|
|
21871
|
-
const age = lockAgeMs(lock);
|
|
21872
|
-
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
21757
|
+
if (generated === null)
|
|
21873
21758
|
continue;
|
|
21874
|
-
|
|
21875
|
-
|
|
21876
|
-
|
|
21759
|
+
const regex = patternRegex(dir, pattern);
|
|
21760
|
+
if (regex)
|
|
21761
|
+
out.push({ regex, generated });
|
|
21877
21762
|
}
|
|
21878
|
-
|
|
21879
|
-
|
|
21880
|
-
|
|
21881
|
-
|
|
21763
|
+
return out;
|
|
21764
|
+
}
|
|
21765
|
+
function generatedByAttributes(files, paths) {
|
|
21766
|
+
const rules = [...files].map(([file, text]) => {
|
|
21767
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
21768
|
+
return {
|
|
21769
|
+
depth: dir ? dir.split("/").length : 0,
|
|
21770
|
+
rules: rulesOf(dir, text)
|
|
21771
|
+
};
|
|
21772
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
21773
|
+
const out = new Set;
|
|
21774
|
+
for (const file of paths) {
|
|
21775
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
21776
|
+
if (last?.generated)
|
|
21777
|
+
out.add(file);
|
|
21882
21778
|
}
|
|
21779
|
+
return out;
|
|
21883
21780
|
}
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
var
|
|
21888
|
-
var
|
|
21889
|
-
|
|
21890
|
-
|
|
21891
|
-
|
|
21892
|
-
|
|
21893
|
-
|
|
21894
|
-
|
|
21895
|
-
|
|
21896
|
-
|
|
21781
|
+
|
|
21782
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
21783
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
21784
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
21785
|
+
var SYNTAXES = Object.fromEntries([
|
|
21786
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
21787
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
21788
|
+
ext,
|
|
21789
|
+
{ ...C_LIKE, triple: true }
|
|
21790
|
+
]),
|
|
21791
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
21792
|
+
...["css", "scss", "less"].map((ext) => [
|
|
21793
|
+
ext,
|
|
21794
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
21795
|
+
]),
|
|
21796
|
+
...["py", "pyi"].map((ext) => [
|
|
21797
|
+
ext,
|
|
21798
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
21799
|
+
]),
|
|
21800
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
21801
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
21802
|
+
ext,
|
|
21803
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
21804
|
+
])
|
|
21805
|
+
]);
|
|
21806
|
+
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;
|
|
21807
|
+
var extensionOf = (file) => {
|
|
21808
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
21809
|
+
const dot = name.lastIndexOf(".");
|
|
21810
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
21811
|
+
};
|
|
21812
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
21813
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
21814
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
21815
|
+
var WORD = /[\w$]/u;
|
|
21816
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
21817
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
21818
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
21819
|
+
var REGEX_KEYWORDS = new Set([
|
|
21820
|
+
"return",
|
|
21821
|
+
"typeof",
|
|
21822
|
+
"instanceof",
|
|
21823
|
+
"case",
|
|
21824
|
+
"do",
|
|
21825
|
+
"else",
|
|
21826
|
+
"in",
|
|
21827
|
+
"of",
|
|
21828
|
+
"new",
|
|
21829
|
+
"delete",
|
|
21830
|
+
"void",
|
|
21831
|
+
"throw",
|
|
21832
|
+
"yield",
|
|
21833
|
+
"await"
|
|
21834
|
+
]);
|
|
21835
|
+
function substanceOf(file, text) {
|
|
21836
|
+
const syntax = syntaxOf(file);
|
|
21837
|
+
if (!syntax)
|
|
21838
|
+
return plainSubstance(text);
|
|
21839
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
21840
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
21841
|
+
return plainSubstance(text);
|
|
21842
|
+
return lex(syntax, text);
|
|
21843
|
+
}
|
|
21844
|
+
function plainSubstance(text) {
|
|
21845
|
+
return text.split(`
|
|
21846
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
21847
|
+
`);
|
|
21848
|
+
}
|
|
21849
|
+
function lex(syntax, text) {
|
|
21850
|
+
const collapse = !syntax.indented;
|
|
21851
|
+
let out = "";
|
|
21852
|
+
let lineStart = 0;
|
|
21853
|
+
let kept = 0;
|
|
21854
|
+
let pendingSpace = false;
|
|
21855
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
21856
|
+
let index = 0;
|
|
21857
|
+
if (text.startsWith("#!")) {
|
|
21858
|
+
const end = text.indexOf(`
|
|
21859
|
+
`);
|
|
21860
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
21861
|
+
kept = out.length;
|
|
21862
|
+
index = end < 0 ? text.length : end;
|
|
21863
|
+
}
|
|
21864
|
+
const emitCode = (chunk) => {
|
|
21865
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
21866
|
+
const before = out[out.length - 1];
|
|
21867
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
21868
|
+
out += " ";
|
|
21897
21869
|
}
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
|
|
21902
|
-
|
|
21903
|
-
|
|
21904
|
-
|
|
21905
|
-
|
|
21906
|
-
|
|
21907
|
-
|
|
21908
|
-
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
break;
|
|
21912
|
-
}
|
|
21913
|
-
stop = start;
|
|
21870
|
+
pendingSpace = false;
|
|
21871
|
+
out += chunk;
|
|
21872
|
+
};
|
|
21873
|
+
const emitVerbatim = (chunk) => {
|
|
21874
|
+
emitCode(chunk);
|
|
21875
|
+
kept = out.length;
|
|
21876
|
+
};
|
|
21877
|
+
const newline = () => {
|
|
21878
|
+
if (!collapse) {
|
|
21879
|
+
let end = out.length;
|
|
21880
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
21881
|
+
end -= 1;
|
|
21882
|
+
out = out.slice(0, end);
|
|
21914
21883
|
}
|
|
21915
|
-
|
|
21916
|
-
|
|
21917
|
-
|
|
21918
|
-
return null;
|
|
21919
|
-
};
|
|
21920
|
-
var tighten = (fd) => {
|
|
21921
|
-
try {
|
|
21922
|
-
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
21923
|
-
fs12.fchmodSync(fd, 384);
|
|
21924
|
-
} catch {}
|
|
21925
|
-
};
|
|
21926
|
-
function appendLine(file, build, options) {
|
|
21927
|
-
const write = () => {
|
|
21928
|
-
const fd = fs12.openSync(file, "a+", 384);
|
|
21929
|
-
try {
|
|
21930
|
-
if (process.platform !== "win32")
|
|
21931
|
-
tighten(fd);
|
|
21932
|
-
const size = fs12.fstatSync(fd).size;
|
|
21933
|
-
let fence = "";
|
|
21934
|
-
if (size > 0) {
|
|
21935
|
-
const last = Buffer.alloc(1);
|
|
21936
|
-
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
21937
|
-
if (last[0] !== 10)
|
|
21938
|
-
fence = `
|
|
21884
|
+
pendingSpace = false;
|
|
21885
|
+
if (out.length > lineStart) {
|
|
21886
|
+
out += `
|
|
21939
21887
|
`;
|
|
21940
|
-
|
|
21941
|
-
const line = build(lastLine(fd, size));
|
|
21942
|
-
if (!line.ok)
|
|
21943
|
-
return line;
|
|
21944
|
-
const bytes = Buffer.from(fence + line.value);
|
|
21945
|
-
const written = fs12.writeSync(fd, bytes);
|
|
21946
|
-
if (written !== bytes.length)
|
|
21947
|
-
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
21948
|
-
return { ok: true, value: undefined };
|
|
21949
|
-
} finally {
|
|
21950
|
-
fs12.closeSync(fd);
|
|
21888
|
+
lineStart = out.length;
|
|
21951
21889
|
}
|
|
21952
21890
|
};
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
var sealed = (row, id, at, previous) => {
|
|
21964
|
-
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
21965
|
-
const prevHash = previous === null ? null : sha2562(previous);
|
|
21966
|
-
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
21967
|
-
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
21968
|
-
return { full, line: `${JSON.stringify(full)}
|
|
21969
|
-
` };
|
|
21970
|
-
};
|
|
21971
|
-
function appendRaw(cwd, row, options) {
|
|
21972
|
-
const file = ledgerPath(cwd);
|
|
21973
|
-
if (!file.ok)
|
|
21974
|
-
return file;
|
|
21975
|
-
const id = newId2();
|
|
21976
|
-
const at = (options.now ?? new Date).toISOString();
|
|
21977
|
-
let full = null;
|
|
21978
|
-
const written = appendLine(file.value, (previous) => {
|
|
21979
|
-
const next = sealed(row, id, at, previous);
|
|
21980
|
-
const bytes = Buffer.byteLength(next.line);
|
|
21981
|
-
if (bytes > MAX_LINE_BYTES)
|
|
21982
|
-
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>`);
|
|
21983
|
-
full = next.full;
|
|
21984
|
-
return { ok: true, value: next.line };
|
|
21985
|
-
}, options);
|
|
21986
|
-
return written.ok ? { ok: true, value: full } : written;
|
|
21987
|
-
}
|
|
21988
|
-
function observedRowBytes(row) {
|
|
21989
|
-
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
21990
|
-
return Buffer.byteLength(sample.line);
|
|
21991
|
-
}
|
|
21992
|
-
function appendObserved(cwd, row, options = {}) {
|
|
21993
|
-
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
21994
|
-
}
|
|
21995
|
-
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21996
|
-
var str2 = (value) => typeof value === "string" ? value : null;
|
|
21997
|
-
var normalizeActor = (value) => {
|
|
21998
|
-
const actor = isRecord3(value) ? value : {};
|
|
21999
|
-
return {
|
|
22000
|
-
host: str2(actor.host) ?? "unknown",
|
|
22001
|
-
session: str2(actor.session),
|
|
22002
|
-
agentId: str2(actor.agentId)
|
|
21891
|
+
const regexAllowed = () => {
|
|
21892
|
+
let end = out.length;
|
|
21893
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
21894
|
+
end -= 1;
|
|
21895
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
21896
|
+
return true;
|
|
21897
|
+
let start = end;
|
|
21898
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
21899
|
+
start -= 1;
|
|
21900
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
22003
21901
|
};
|
|
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
|
-
|
|
22043
|
-
|
|
22044
|
-
|
|
22045
|
-
|
|
22046
|
-
|
|
22047
|
-
|
|
22048
|
-
const trimmed = raw.trim();
|
|
22049
|
-
if (!trimmed)
|
|
21902
|
+
const scanString = (quote, escapes, multiline) => {
|
|
21903
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
21904
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
21905
|
+
let end = index + quote.length;
|
|
21906
|
+
while (end < text.length) {
|
|
21907
|
+
const char = text[end];
|
|
21908
|
+
if (escapes && char === "\\") {
|
|
21909
|
+
end += 2;
|
|
21910
|
+
continue;
|
|
21911
|
+
}
|
|
21912
|
+
if (text.startsWith(quote, end)) {
|
|
21913
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
21914
|
+
return false;
|
|
21915
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
21916
|
+
index = end + quote.length;
|
|
21917
|
+
return true;
|
|
21918
|
+
}
|
|
21919
|
+
if (char === `
|
|
21920
|
+
` && !multiline)
|
|
21921
|
+
return false;
|
|
21922
|
+
end += 1;
|
|
21923
|
+
}
|
|
21924
|
+
return false;
|
|
21925
|
+
};
|
|
21926
|
+
while (index < text.length) {
|
|
21927
|
+
const frame = frames[frames.length - 1];
|
|
21928
|
+
const char = text[index];
|
|
21929
|
+
if (frame.kind === "template") {
|
|
21930
|
+
if (char === "\\") {
|
|
21931
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
21932
|
+
index += 2;
|
|
21933
|
+
} else if (char === "`") {
|
|
21934
|
+
emitVerbatim(char);
|
|
21935
|
+
frames.pop();
|
|
21936
|
+
index += 1;
|
|
21937
|
+
} else if (text.startsWith("${", index)) {
|
|
21938
|
+
emitVerbatim("${");
|
|
21939
|
+
frames.push({ kind: "code", braces: 0 });
|
|
21940
|
+
index += 2;
|
|
21941
|
+
} else {
|
|
21942
|
+
out += char;
|
|
21943
|
+
kept = out.length;
|
|
21944
|
+
index += 1;
|
|
21945
|
+
}
|
|
22050
21946
|
continue;
|
|
22051
|
-
|
|
22052
|
-
if (
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
parsed = JSON.parse(trimmed);
|
|
22057
|
-
} catch {
|
|
22058
|
-
skipped += 1;
|
|
21947
|
+
}
|
|
21948
|
+
if (char === `
|
|
21949
|
+
`) {
|
|
21950
|
+
newline();
|
|
21951
|
+
index += 1;
|
|
22059
21952
|
continue;
|
|
22060
21953
|
}
|
|
22061
|
-
if (
|
|
22062
|
-
|
|
21954
|
+
if (SPACE.test(char)) {
|
|
21955
|
+
if (collapse)
|
|
21956
|
+
pendingSpace = true;
|
|
21957
|
+
else
|
|
21958
|
+
out += char;
|
|
21959
|
+
index += 1;
|
|
22063
21960
|
continue;
|
|
22064
21961
|
}
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
const
|
|
22070
|
-
|
|
22071
|
-
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
} else if (chainStarted)
|
|
22076
|
-
reason = "unsigned";
|
|
22077
|
-
else
|
|
22078
|
-
rowIntegrity = "legacy";
|
|
22079
|
-
if (options.integrity && !reason && ids.has(parsed.id))
|
|
22080
|
-
reason = "duplicate_id";
|
|
22081
|
-
if (options.integrity)
|
|
22082
|
-
ids.add(parsed.id);
|
|
22083
|
-
if (reason)
|
|
22084
|
-
rowIntegrity = "unverified";
|
|
22085
|
-
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
22086
|
-
const row = {
|
|
22087
|
-
...parsed,
|
|
22088
|
-
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
22089
|
-
id: parsed.id,
|
|
22090
|
-
at: parsed.at,
|
|
22091
|
-
type: parsed.type,
|
|
22092
|
-
actor: normalizeActor(parsed.actor),
|
|
22093
|
-
branch: str2(parsed.branch),
|
|
22094
|
-
head: str2(parsed.head),
|
|
22095
|
-
base: str2(parsed.base),
|
|
22096
|
-
baseSha: str2(parsed.baseSha),
|
|
22097
|
-
patchId: str2(parsed.patchId),
|
|
22098
|
-
diffHash: str2(parsed.diffHash),
|
|
22099
|
-
tree: str2(parsed.tree),
|
|
22100
|
-
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
22101
|
-
...pr === undefined ? {} : { pr },
|
|
22102
|
-
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
22103
|
-
seq: rows.length + 1,
|
|
22104
|
-
superseded: false,
|
|
22105
|
-
supersedeIgnored: false,
|
|
22106
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
22107
|
-
...reason ? { integrityReason: reason } : {}
|
|
22108
|
-
};
|
|
22109
|
-
rows.push(row);
|
|
22110
|
-
if (!options.integrity)
|
|
21962
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
21963
|
+
if (lineOpener) {
|
|
21964
|
+
const end = text.indexOf(`
|
|
21965
|
+
`, index);
|
|
21966
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
21967
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
21968
|
+
emitVerbatim(comment);
|
|
21969
|
+
else
|
|
21970
|
+
pendingSpace = true;
|
|
21971
|
+
index = end < 0 ? text.length : end;
|
|
22111
21972
|
continue;
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
22115
|
-
|
|
22116
|
-
|
|
22117
|
-
|
|
22118
|
-
|
|
22119
|
-
|
|
22120
|
-
|
|
22121
|
-
|
|
22122
|
-
|
|
22123
|
-
|
|
21973
|
+
}
|
|
21974
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
21975
|
+
if (block) {
|
|
21976
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
21977
|
+
if (end < 0)
|
|
21978
|
+
return null;
|
|
21979
|
+
const comment = text.slice(index, end + block[1].length);
|
|
21980
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
21981
|
+
emitVerbatim(comment);
|
|
21982
|
+
else
|
|
21983
|
+
pendingSpace = true;
|
|
21984
|
+
if (comment.includes(`
|
|
21985
|
+
`))
|
|
21986
|
+
newline();
|
|
21987
|
+
index = end + block[1].length;
|
|
21988
|
+
continue;
|
|
21989
|
+
}
|
|
21990
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
21991
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
21992
|
+
return null;
|
|
21993
|
+
continue;
|
|
21994
|
+
}
|
|
21995
|
+
if (syntax.quotes.includes(char)) {
|
|
21996
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
21997
|
+
return null;
|
|
21998
|
+
continue;
|
|
21999
|
+
}
|
|
22000
|
+
if (syntax.literal?.includes(char)) {
|
|
22001
|
+
if (!scanString(char, false, false))
|
|
22002
|
+
return null;
|
|
22003
|
+
continue;
|
|
22004
|
+
}
|
|
22005
|
+
if (syntax.rawBacktick && char === "`") {
|
|
22006
|
+
if (!scanString("`", false, true))
|
|
22007
|
+
return null;
|
|
22008
|
+
continue;
|
|
22009
|
+
}
|
|
22010
|
+
if (syntax.js && char === "`") {
|
|
22011
|
+
emitVerbatim(char);
|
|
22012
|
+
frames.push({ kind: "template" });
|
|
22013
|
+
index += 1;
|
|
22014
|
+
continue;
|
|
22015
|
+
}
|
|
22016
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
22017
|
+
return null;
|
|
22018
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
22019
|
+
const lineEnd = text.indexOf(`
|
|
22020
|
+
`, index);
|
|
22021
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
22022
|
+
return null;
|
|
22023
|
+
}
|
|
22024
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
22025
|
+
let end = index + 1;
|
|
22026
|
+
let inClass = false;
|
|
22027
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
22028
|
+
if (text[end] === `
|
|
22029
|
+
`)
|
|
22030
|
+
return null;
|
|
22031
|
+
if (text[end] === "\\")
|
|
22032
|
+
end += 1;
|
|
22033
|
+
else if (text[end] === "[")
|
|
22034
|
+
inClass = true;
|
|
22035
|
+
else if (text[end] === "]")
|
|
22036
|
+
inClass = false;
|
|
22037
|
+
end += 1;
|
|
22038
|
+
}
|
|
22039
|
+
if (end >= text.length)
|
|
22040
|
+
return null;
|
|
22041
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
22042
|
+
index = end + 1;
|
|
22043
|
+
continue;
|
|
22044
|
+
}
|
|
22045
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
22046
|
+
if (char === "{")
|
|
22047
|
+
frame.braces += 1;
|
|
22048
|
+
else if (char === "}") {
|
|
22049
|
+
if (frame.braces === 0) {
|
|
22050
|
+
emitVerbatim(char);
|
|
22051
|
+
frames.pop();
|
|
22052
|
+
index += 1;
|
|
22053
|
+
continue;
|
|
22054
|
+
}
|
|
22055
|
+
frame.braces -= 1;
|
|
22056
|
+
}
|
|
22057
|
+
}
|
|
22058
|
+
emitCode(char);
|
|
22059
|
+
index += 1;
|
|
22124
22060
|
}
|
|
22125
|
-
|
|
22126
|
-
|
|
22127
|
-
|
|
22128
|
-
|
|
22129
|
-
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
const
|
|
22133
|
-
|
|
22134
|
-
|
|
22135
|
-
if (
|
|
22136
|
-
|
|
22137
|
-
else if (row.supersedes)
|
|
22138
|
-
row.supersedeIgnored = true;
|
|
22139
|
-
if (!byId.has(row.id))
|
|
22140
|
-
byId.set(row.id, row);
|
|
22061
|
+
if (frames.length !== 1)
|
|
22062
|
+
return null;
|
|
22063
|
+
newline();
|
|
22064
|
+
return out;
|
|
22065
|
+
}
|
|
22066
|
+
function unbalanced(body) {
|
|
22067
|
+
let depth = 0;
|
|
22068
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
22069
|
+
if (part === "{")
|
|
22070
|
+
depth += 1;
|
|
22071
|
+
else if (part === "}")
|
|
22072
|
+
depth = Math.max(0, depth - 1);
|
|
22141
22073
|
}
|
|
22142
|
-
|
|
22143
|
-
tamperCache.set(file.value, {
|
|
22144
|
-
size: stat.size,
|
|
22145
|
-
stamp: fileStamp(stat),
|
|
22146
|
-
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
22147
|
-
});
|
|
22148
|
-
return {
|
|
22149
|
-
ok: true,
|
|
22150
|
-
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
22151
|
-
};
|
|
22074
|
+
return depth > 0;
|
|
22152
22075
|
}
|
|
22153
|
-
|
|
22154
|
-
|
|
22155
|
-
|
|
22156
|
-
|
|
22157
|
-
|
|
22158
|
-
if (
|
|
22159
|
-
return
|
|
22076
|
+
function hasOpaqueLineBreak(text) {
|
|
22077
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
22078
|
+
}
|
|
22079
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
22080
|
+
function textOf(bytes) {
|
|
22081
|
+
if (bytes.includes(0))
|
|
22082
|
+
return null;
|
|
22160
22083
|
try {
|
|
22161
|
-
|
|
22162
|
-
const cached = tamperCache.get(file.value);
|
|
22163
|
-
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
22164
|
-
return cached.breaks;
|
|
22084
|
+
return decoder.decode(bytes);
|
|
22165
22085
|
} catch {
|
|
22166
|
-
return
|
|
22086
|
+
return null;
|
|
22167
22087
|
}
|
|
22168
|
-
const read = readLedger(cwd, { integrity: true });
|
|
22169
|
-
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
22170
|
-
}
|
|
22171
|
-
function tamperedUnblock(cwd) {
|
|
22172
|
-
const file = ledgerPath(cwd);
|
|
22173
|
-
const where = file.ok ? file.value : "the ledger file";
|
|
22174
|
-
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`;
|
|
22175
22088
|
}
|
|
22176
|
-
|
|
22177
|
-
|
|
22178
|
-
|
|
22179
|
-
|
|
22180
|
-
|
|
22181
|
-
|
|
22182
|
-
|
|
22089
|
+
function cosmeticChange(file, before, after) {
|
|
22090
|
+
const left = textOf(before);
|
|
22091
|
+
const right = textOf(after);
|
|
22092
|
+
if (left === null || right === null)
|
|
22093
|
+
return false;
|
|
22094
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
22095
|
+
return false;
|
|
22096
|
+
const a = substanceOf(file, left);
|
|
22097
|
+
return a !== null && a === substanceOf(file, right);
|
|
22183
22098
|
}
|
|
22184
|
-
|
|
22185
|
-
|
|
22186
|
-
|
|
22187
|
-
|
|
22188
|
-
|
|
22189
|
-
|
|
22190
|
-
|
|
22191
|
-
|
|
22192
|
-
|
|
22193
|
-
|
|
22099
|
+
|
|
22100
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
22101
|
+
var LANE_THRESHOLDS = {
|
|
22102
|
+
selfMaxLines: 100,
|
|
22103
|
+
selfMaxFiles: 5,
|
|
22104
|
+
fullMinLines: 500
|
|
22105
|
+
};
|
|
22106
|
+
var RISK_TOKENS = new Set([
|
|
22107
|
+
"auth",
|
|
22108
|
+
"authn",
|
|
22109
|
+
"authz",
|
|
22110
|
+
"oauth",
|
|
22111
|
+
"security",
|
|
22112
|
+
"permission",
|
|
22113
|
+
"permissions",
|
|
22114
|
+
"secret",
|
|
22115
|
+
"secrets",
|
|
22116
|
+
"crypto",
|
|
22117
|
+
"gate",
|
|
22118
|
+
"gates",
|
|
22119
|
+
"policy",
|
|
22120
|
+
"policies",
|
|
22121
|
+
"hook",
|
|
22122
|
+
"hooks",
|
|
22123
|
+
"husky",
|
|
22124
|
+
"lefthook",
|
|
22125
|
+
"release",
|
|
22126
|
+
"releases",
|
|
22127
|
+
"migration",
|
|
22128
|
+
"migrations",
|
|
22129
|
+
"migrate",
|
|
22130
|
+
"workflows"
|
|
22131
|
+
]);
|
|
22132
|
+
var RISK_FILES = new Set([
|
|
22133
|
+
".gitlab-ci.yml",
|
|
22134
|
+
"Jenkinsfile",
|
|
22135
|
+
"azure-pipelines.yml",
|
|
22136
|
+
".pre-commit-config.yaml",
|
|
22137
|
+
".workit/risk-paths"
|
|
22138
|
+
]);
|
|
22139
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
22140
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
22141
|
+
var LOCKFILES = new Set([
|
|
22142
|
+
"bun.lock",
|
|
22143
|
+
"bun.lockb",
|
|
22144
|
+
"package-lock.json",
|
|
22145
|
+
"npm-shrinkwrap.json",
|
|
22146
|
+
"yarn.lock",
|
|
22147
|
+
"pnpm-lock.yaml",
|
|
22148
|
+
"Cargo.lock",
|
|
22149
|
+
"go.sum",
|
|
22150
|
+
"poetry.lock",
|
|
22151
|
+
"uv.lock",
|
|
22152
|
+
"Pipfile.lock",
|
|
22153
|
+
"Gemfile.lock",
|
|
22154
|
+
"composer.lock"
|
|
22155
|
+
]);
|
|
22156
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
22157
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
22158
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
22159
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
22160
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
22161
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
22162
|
+
function kindOf(file) {
|
|
22163
|
+
if (file.binary)
|
|
22164
|
+
return "behavior";
|
|
22165
|
+
if (file.generated)
|
|
22166
|
+
return "generated";
|
|
22167
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
22168
|
+
return "lockfile";
|
|
22169
|
+
if (isDoc(file.path))
|
|
22170
|
+
return "doc";
|
|
22171
|
+
if (file.cosmetic)
|
|
22172
|
+
return "cosmetic";
|
|
22173
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
22194
22174
|
}
|
|
22195
|
-
var
|
|
22196
|
-
function
|
|
22197
|
-
|
|
22198
|
-
|
|
22199
|
-
|
|
22200
|
-
|
|
22201
|
-
|
|
22202
|
-
"
|
|
22203
|
-
|
|
22204
|
-
|
|
22205
|
-
|
|
22206
|
-
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22210
|
-
|
|
22211
|
-
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
22212
|
-
if (!Number.isFinite(seconds))
|
|
22213
|
-
continue;
|
|
22214
|
-
since = seconds * 1000;
|
|
22215
|
-
const match = RENAMED.exec(subject);
|
|
22216
|
-
if (!match || match[1] === branch)
|
|
22217
|
-
continue;
|
|
22218
|
-
const until = (seconds + 1) * 1000;
|
|
22219
|
-
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
22175
|
+
var globCache = new Map;
|
|
22176
|
+
function globMatch(pattern, file) {
|
|
22177
|
+
let regex = globCache.get(pattern);
|
|
22178
|
+
if (!regex) {
|
|
22179
|
+
let source = "";
|
|
22180
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
22181
|
+
const char = pattern[index];
|
|
22182
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
22183
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
22184
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
22185
|
+
} else if (char === "*")
|
|
22186
|
+
source += "[^/]*";
|
|
22187
|
+
else if (char === "?")
|
|
22188
|
+
source += "[^/]";
|
|
22189
|
+
else
|
|
22190
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
22220
22191
|
}
|
|
22192
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
22193
|
+
globCache.set(pattern, regex);
|
|
22221
22194
|
}
|
|
22222
|
-
return (
|
|
22223
|
-
if (row.branch === branch)
|
|
22224
|
-
return true;
|
|
22225
|
-
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
22226
|
-
if (until === undefined)
|
|
22227
|
-
return false;
|
|
22228
|
-
const at = Date.parse(row.at);
|
|
22229
|
-
return at >= since && at <= until;
|
|
22230
|
-
};
|
|
22231
|
-
}
|
|
22232
|
-
var renameAwareBranch = (scopeOf, branch) => {
|
|
22233
|
-
return (target) => {
|
|
22234
|
-
const own = typeof branch === "string" ? branch : null;
|
|
22235
|
-
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
22236
|
-
if (own === theirs)
|
|
22237
|
-
return true;
|
|
22238
|
-
if (own === null || theirs === null || typeof target.at !== "string")
|
|
22239
|
-
return false;
|
|
22240
|
-
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
22241
|
-
};
|
|
22242
|
-
};
|
|
22243
|
-
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
22244
|
-
const sessions = new Set;
|
|
22245
|
-
for (const row of rows) {
|
|
22246
|
-
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
22247
|
-
continue;
|
|
22248
|
-
const session = str2(row.session) ?? row.actor.session;
|
|
22249
|
-
if (session)
|
|
22250
|
-
sessions.add(session);
|
|
22251
|
-
}
|
|
22252
|
-
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
22253
|
-
sessions.add(session);
|
|
22254
|
-
return sessions;
|
|
22195
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
22255
22196
|
}
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
if (authors.has(session))
|
|
22197
|
+
function isRiskPath(file, extra = []) {
|
|
22198
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
22259
22199
|
return true;
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
22265
|
-
var TRUST_REASONS = new Set([
|
|
22266
|
-
"unsigned",
|
|
22267
|
-
"hash_mismatch",
|
|
22268
|
-
"broken_link",
|
|
22269
|
-
"duplicate_id",
|
|
22270
|
-
"unissued_session",
|
|
22271
|
-
"unbound_session"
|
|
22272
|
-
]);
|
|
22273
|
-
var SHELL_VOUCHED_HOSTS = new Set([
|
|
22274
|
-
"cli",
|
|
22275
|
-
"opencode",
|
|
22276
|
-
"pi",
|
|
22277
|
-
"codex_cli",
|
|
22278
|
-
"codex_desktop"
|
|
22279
|
-
]);
|
|
22280
|
-
function sessionTrust(cwd, row) {
|
|
22281
|
-
const session = row.actor.session;
|
|
22282
|
-
if (!session || row.self === true)
|
|
22283
|
-
return null;
|
|
22284
|
-
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
22285
|
-
const host = row.actor.host;
|
|
22286
|
-
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
22287
|
-
if (!issuedOnly)
|
|
22288
|
-
return null;
|
|
22289
|
-
const binding = issuedBinding(cwd, session);
|
|
22290
|
-
if (binding === null)
|
|
22291
|
-
return "unissued_session";
|
|
22292
|
-
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
22293
|
-
}
|
|
22294
|
-
function trustReasons(cwd, row) {
|
|
22295
|
-
if (row.integrity === "unverified")
|
|
22296
|
-
return [row.integrityReason ?? "unsigned"];
|
|
22297
|
-
if (row.integrity !== "verified")
|
|
22298
|
-
return ["unsigned"];
|
|
22299
|
-
const session = sessionTrust(cwd, row);
|
|
22300
|
-
return session ? [session] : [];
|
|
22200
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
22201
|
+
return true;
|
|
22202
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
22203
|
+
return false;
|
|
22204
|
+
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)));
|
|
22301
22205
|
}
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
const
|
|
22306
|
-
const
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22206
|
+
var parseRiskPaths = (text) => text.split(`
|
|
22207
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
22208
|
+
function computeLane(input) {
|
|
22209
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
22210
|
+
const risky = new Set(riskFiles);
|
|
22211
|
+
const kinds = input.files.map((file) => ({
|
|
22212
|
+
file,
|
|
22213
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
22214
|
+
}));
|
|
22215
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
22216
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
22217
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
22218
|
+
const result = (lane, reasons) => ({
|
|
22219
|
+
lane,
|
|
22220
|
+
reasons,
|
|
22221
|
+
lines,
|
|
22222
|
+
files: behavior.length,
|
|
22223
|
+
riskFiles
|
|
22312
22224
|
});
|
|
22225
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
22226
|
+
if (input.riskTier === "high")
|
|
22227
|
+
return result("full", ["the task judgment records high risk"]);
|
|
22228
|
+
if (riskFiles.length)
|
|
22229
|
+
return result("full", [
|
|
22230
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
22231
|
+
]);
|
|
22232
|
+
if (!behavior.length)
|
|
22233
|
+
return result("none", [
|
|
22234
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
22235
|
+
]);
|
|
22236
|
+
if (lines >= fullMinLines)
|
|
22237
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
22238
|
+
if (lines > selfMaxLines)
|
|
22239
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
22240
|
+
if (behavior.length > selfMaxFiles)
|
|
22241
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
22242
|
+
if (opaque.length)
|
|
22243
|
+
return result("focused", [
|
|
22244
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
22245
|
+
]);
|
|
22246
|
+
return result("self", [
|
|
22247
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
22248
|
+
]);
|
|
22313
22249
|
}
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
|
|
22323
|
-
|
|
22324
|
-
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
if (!effective) {
|
|
22329
|
-
effective = row;
|
|
22330
|
-
continue;
|
|
22331
|
-
}
|
|
22332
|
-
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
22333
|
-
continue;
|
|
22334
|
-
const rowIndependent = reasonsOf(row).length === 0;
|
|
22335
|
-
const effIndependent = reasonsOf(effective).length === 0;
|
|
22336
|
-
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
22337
|
-
continue;
|
|
22338
|
-
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
22339
|
-
continue;
|
|
22340
|
-
effective = row;
|
|
22250
|
+
function untrackedStat(cwd, file) {
|
|
22251
|
+
try {
|
|
22252
|
+
const bytes = readFileSync4(path14.join(cwd, file));
|
|
22253
|
+
if (bytes.includes(0))
|
|
22254
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
22255
|
+
const text = bytes.toString("utf8");
|
|
22256
|
+
if (hasOpaqueLineBreak(text))
|
|
22257
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
22258
|
+
const lines = text.split(`
|
|
22259
|
+
`).length - (text.endsWith(`
|
|
22260
|
+
`) ? 1 : 0);
|
|
22261
|
+
return { path: file, added: lines, deleted: 0 };
|
|
22262
|
+
} catch {
|
|
22263
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
22341
22264
|
}
|
|
22342
|
-
return effective;
|
|
22343
22265
|
}
|
|
22344
|
-
|
|
22345
|
-
|
|
22346
|
-
|
|
22347
|
-
const
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
const
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22355
|
-
|
|
22356
|
-
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22266
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
22267
|
+
var ABSENT = /^0+$/u;
|
|
22268
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
22269
|
+
const raw = diffRaw(cwd, fork, head);
|
|
22270
|
+
if (!raw)
|
|
22271
|
+
return null;
|
|
22272
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
22273
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
22274
|
+
if (!blobs)
|
|
22275
|
+
return null;
|
|
22276
|
+
const out = new Set;
|
|
22277
|
+
const opaque = new Set;
|
|
22278
|
+
for (const entry of candidates) {
|
|
22279
|
+
const before = blobs.get(entry.oldBlob);
|
|
22280
|
+
let after = blobs.get(entry.newBlob);
|
|
22281
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
22282
|
+
try {
|
|
22283
|
+
after = readFileSync4(path14.join(cwd, entry.path));
|
|
22284
|
+
} catch {
|
|
22285
|
+
after = undefined;
|
|
22361
22286
|
}
|
|
22287
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
22288
|
+
out.add(entry.path);
|
|
22289
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
22290
|
+
opaque.add(entry.path);
|
|
22362
22291
|
}
|
|
22363
|
-
return
|
|
22292
|
+
return { cosmetic: out, opaque };
|
|
22364
22293
|
}
|
|
22365
|
-
function
|
|
22366
|
-
const
|
|
22367
|
-
const
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
const
|
|
22371
|
-
const
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
|
|
22377
|
-
|
|
22378
|
-
|
|
22379
|
-
|
|
22380
|
-
|
|
22381
|
-
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
|
|
22385
|
-
|
|
22386
|
-
|
|
22387
|
-
|
|
22388
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
|
|
22394
|
-
|
|
22395
|
-
|
|
22396
|
-
|
|
22397
|
-
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
|
|
22401
|
-
|
|
22402
|
-
|
|
22403
|
-
|
|
22404
|
-
|
|
22405
|
-
const
|
|
22406
|
-
const
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
};
|
|
22418
|
-
const
|
|
22419
|
-
|
|
22420
|
-
|
|
22421
|
-
|
|
22422
|
-
const
|
|
22423
|
-
|
|
22424
|
-
|
|
22425
|
-
|
|
22426
|
-
|
|
22427
|
-
|
|
22428
|
-
|
|
22429
|
-
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
|
|
22433
|
-
|
|
22434
|
-
|
|
22435
|
-
|
|
22436
|
-
|
|
22437
|
-
|
|
22438
|
-
|
|
22439
|
-
|
|
22440
|
-
|
|
22441
|
-
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
22447
|
-
accepted: reasons.length === 0,
|
|
22448
|
-
reasons,
|
|
22449
|
-
verdict: row
|
|
22450
|
-
});
|
|
22451
|
-
if (reasons.length === 0) {
|
|
22452
|
-
for (const later of list)
|
|
22453
|
-
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
22454
|
-
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`);
|
|
22455
|
-
}
|
|
22456
|
-
}
|
|
22457
|
-
for (const row of rows)
|
|
22458
|
-
if (row.integrity === "unverified" && scope(row))
|
|
22459
|
-
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)`);
|
|
22460
|
-
const tampered = ledgerTampering(cwd);
|
|
22461
|
-
if (tampered.length) {
|
|
22462
|
-
warnings.push(tamperedWarning(cwd, tampered));
|
|
22463
|
-
for (const entry of verdicts) {
|
|
22464
|
-
entry.accepted = false;
|
|
22465
|
-
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
22466
|
-
}
|
|
22467
|
-
}
|
|
22468
|
-
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
22469
|
-
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
22470
|
-
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
22471
|
-
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
22472
|
-
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
22473
|
-
accepted: false,
|
|
22474
|
-
verdict: failing?.verdict ?? null,
|
|
22475
|
-
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
22476
|
-
};
|
|
22477
|
-
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))));
|
|
22478
|
-
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
22479
|
-
return {
|
|
22480
|
-
branch,
|
|
22481
|
-
head,
|
|
22482
|
-
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
22483
|
-
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
22484
|
-
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
22485
|
-
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
22486
|
-
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
22487
|
-
accepted,
|
|
22488
|
-
verdicts,
|
|
22489
|
-
authors: [...authors].toSorted(),
|
|
22490
|
-
warnings
|
|
22294
|
+
function generatedAt(cwd, fork, paths) {
|
|
22295
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
22296
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
22297
|
+
if (!files || !blobs)
|
|
22298
|
+
return new Set;
|
|
22299
|
+
const texts = new Map;
|
|
22300
|
+
for (const [file, id] of files) {
|
|
22301
|
+
const bytes = blobs.get(id);
|
|
22302
|
+
if (bytes)
|
|
22303
|
+
texts.set(file, bytes.toString("utf8"));
|
|
22304
|
+
}
|
|
22305
|
+
return generatedByAttributes(texts, paths);
|
|
22306
|
+
}
|
|
22307
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
22308
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
22309
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
22310
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
22311
|
+
if (!stats || !cosmetic)
|
|
22312
|
+
return null;
|
|
22313
|
+
const files = [
|
|
22314
|
+
...stats.map((entry) => ({
|
|
22315
|
+
...entry,
|
|
22316
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
22317
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
22318
|
+
})),
|
|
22319
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
22320
|
+
];
|
|
22321
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
22322
|
+
return files.map((entry) => ({
|
|
22323
|
+
...entry,
|
|
22324
|
+
generated: generated.has(entry.path)
|
|
22325
|
+
}));
|
|
22326
|
+
}
|
|
22327
|
+
function driftFiles(cwd, range) {
|
|
22328
|
+
if (range.fromFork === range.toFork)
|
|
22329
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
22330
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
22331
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
22332
|
+
if (!drifted || !stats)
|
|
22333
|
+
return null;
|
|
22334
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
22335
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
22336
|
+
return drifted.map((file) => ({
|
|
22337
|
+
path: file,
|
|
22338
|
+
added: byPath.get(file)?.added ?? 0,
|
|
22339
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
22340
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
22341
|
+
cosmetic: false,
|
|
22342
|
+
generated: generated.has(file)
|
|
22343
|
+
}));
|
|
22344
|
+
}
|
|
22345
|
+
function branchLane(cwd, options) {
|
|
22346
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
22347
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
22348
|
+
const head = options.workingTree ? null : options.head;
|
|
22349
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
22350
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
22351
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
22352
|
+
if (!fork || !files) {
|
|
22353
|
+
const high = riskTier === "high";
|
|
22354
|
+
return {
|
|
22355
|
+
lane: high ? "full" : "focused",
|
|
22356
|
+
reasons: [
|
|
22357
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
22358
|
+
...high ? ["the task judgment records high risk"] : []
|
|
22359
|
+
],
|
|
22360
|
+
lines: 0,
|
|
22361
|
+
files: 0,
|
|
22362
|
+
riskFiles: [],
|
|
22363
|
+
base,
|
|
22364
|
+
head: options.head
|
|
22365
|
+
};
|
|
22366
|
+
}
|
|
22367
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
22368
|
+
return {
|
|
22369
|
+
...computeLane({
|
|
22370
|
+
files,
|
|
22371
|
+
riskTier,
|
|
22372
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
22373
|
+
}),
|
|
22374
|
+
base,
|
|
22375
|
+
head: options.head
|
|
22491
22376
|
};
|
|
22492
22377
|
}
|
|
22493
|
-
var
|
|
22494
|
-
"check",
|
|
22495
|
-
"merge.unverified",
|
|
22496
|
-
"policy.judged",
|
|
22497
|
-
...PR_ROW_TYPES
|
|
22498
|
-
]);
|
|
22499
|
-
|
|
22500
|
-
// packages/workit-core/src/core/policy/lane.ts
|
|
22501
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
22502
|
-
import path16 from "node:path";
|
|
22378
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
22503
22379
|
|
|
22504
|
-
// packages/workit-core/src/core/
|
|
22505
|
-
|
|
22506
|
-
|
|
22507
|
-
|
|
22508
|
-
|
|
22509
|
-
|
|
22510
|
-
|
|
22511
|
-
|
|
22380
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
22381
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
22382
|
+
function verdictDrift(cwd, input) {
|
|
22383
|
+
const since = input.from;
|
|
22384
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
22385
|
+
carries: false,
|
|
22386
|
+
since,
|
|
22387
|
+
files,
|
|
22388
|
+
focused,
|
|
22389
|
+
reason
|
|
22390
|
+
});
|
|
22391
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
22392
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
22393
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
22394
|
+
if (!fromFork || !toFork || !files)
|
|
22395
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
22396
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
22397
|
+
const lane = computeLane({ files, riskPaths });
|
|
22398
|
+
const paths = files.map((file) => file.path);
|
|
22399
|
+
if (lane.lane === "none")
|
|
22400
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
22401
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
22402
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
22403
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
22512
22404
|
}
|
|
22513
22405
|
|
|
22514
|
-
// packages/workit-core/src/
|
|
22515
|
-
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22519
|
-
|
|
22520
|
-
|
|
22521
|
-
|
|
22522
|
-
|
|
22523
|
-
|
|
22524
|
-
|
|
22525
|
-
|
|
22526
|
-
|
|
22527
|
-
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
|
|
22532
|
-
|
|
22406
|
+
// packages/workit-core/src/ledger.ts
|
|
22407
|
+
var LEDGER_VERSION = 1;
|
|
22408
|
+
var MAX_LINE_BYTES = 4096;
|
|
22409
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
22410
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
22411
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
22412
|
+
var PR_ROW_TYPES = new Set([
|
|
22413
|
+
"pr.created",
|
|
22414
|
+
"pr.status",
|
|
22415
|
+
"pr.merged",
|
|
22416
|
+
"pr.linked"
|
|
22417
|
+
]);
|
|
22418
|
+
var err = (code, error, unblock) => ({
|
|
22419
|
+
ok: false,
|
|
22420
|
+
code,
|
|
22421
|
+
error,
|
|
22422
|
+
...unblock ? { unblock } : {}
|
|
22423
|
+
});
|
|
22424
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
22425
|
+
const run = spawnSync9("git", args, {
|
|
22533
22426
|
cwd,
|
|
22534
|
-
encoding: "utf8"
|
|
22427
|
+
encoding: "utf8",
|
|
22428
|
+
timeout: timeoutMs,
|
|
22429
|
+
killSignal: "SIGKILL",
|
|
22430
|
+
windowsHide: true,
|
|
22431
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
22432
|
+
env: {
|
|
22433
|
+
...process.env,
|
|
22434
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
22435
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
22436
|
+
LC_ALL: "C",
|
|
22437
|
+
LANGUAGE: "C"
|
|
22438
|
+
}
|
|
22535
22439
|
});
|
|
22536
|
-
|
|
22537
|
-
|
|
22538
|
-
|
|
22539
|
-
|
|
22440
|
+
return {
|
|
22441
|
+
ok: run.status === 0,
|
|
22442
|
+
stdout: run.stdout ?? "",
|
|
22443
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
22444
|
+
};
|
|
22540
22445
|
};
|
|
22541
|
-
|
|
22542
|
-
|
|
22543
|
-
|
|
22544
|
-
|
|
22545
|
-
|
|
22546
|
-
var
|
|
22547
|
-
|
|
22548
|
-
|
|
22549
|
-
var COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
22550
|
-
|
|
22551
|
-
// packages/workit-core/src/core/policy/attributes.ts
|
|
22552
|
-
function patternRegex(dir, pattern) {
|
|
22553
|
-
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
22446
|
+
var git4 = (cwd, args) => {
|
|
22447
|
+
const run = gitRun2(cwd, args);
|
|
22448
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
22449
|
+
return value ? value : null;
|
|
22450
|
+
};
|
|
22451
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
22452
|
+
function resolveCommit(cwd, ref) {
|
|
22453
|
+
if (!safeRef2(ref))
|
|
22554
22454
|
return null;
|
|
22555
|
-
|
|
22556
|
-
|
|
22557
|
-
|
|
22558
|
-
|
|
22559
|
-
const char = body[index];
|
|
22560
|
-
if (char === "*" && body[index + 1] === "*") {
|
|
22561
|
-
const slash = body[index + 2] === "/";
|
|
22562
|
-
source += slash ? "(?:.*/)?" : ".*";
|
|
22563
|
-
index += slash ? 2 : 1;
|
|
22564
|
-
} else if (char === "*")
|
|
22565
|
-
source += "[^/]*";
|
|
22566
|
-
else if (char === "?")
|
|
22567
|
-
source += "[^/]";
|
|
22568
|
-
else if (char === "[") {
|
|
22569
|
-
const end = body.indexOf("]", index + 2);
|
|
22570
|
-
if (end < 0)
|
|
22571
|
-
return null;
|
|
22572
|
-
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
22573
|
-
source += `[${set}]`;
|
|
22574
|
-
index = end;
|
|
22575
|
-
} else if (char === "\\" && index + 1 < body.length) {
|
|
22576
|
-
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
22577
|
-
index += 1;
|
|
22578
|
-
} else
|
|
22579
|
-
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
22580
|
-
}
|
|
22581
|
-
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
22582
|
-
try {
|
|
22583
|
-
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
22584
|
-
} catch {
|
|
22455
|
+
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
22456
|
+
}
|
|
22457
|
+
function branchHead(cwd, branch) {
|
|
22458
|
+
if (!safeRef2(branch))
|
|
22585
22459
|
return null;
|
|
22586
|
-
}
|
|
22460
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
22587
22461
|
}
|
|
22588
|
-
function
|
|
22589
|
-
|
|
22590
|
-
|
|
22462
|
+
function diffHash(cwd, base, head) {
|
|
22463
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
22464
|
+
return null;
|
|
22465
|
+
const diff = gitRun2(cwd, [
|
|
22466
|
+
"diff",
|
|
22467
|
+
"-U3",
|
|
22468
|
+
"--diff-algorithm=myers",
|
|
22469
|
+
"--indent-heuristic",
|
|
22470
|
+
"--no-color",
|
|
22471
|
+
"--no-ext-diff",
|
|
22472
|
+
"--no-textconv",
|
|
22473
|
+
"--no-renames",
|
|
22474
|
+
"--full-index",
|
|
22475
|
+
"--binary",
|
|
22476
|
+
"--src-prefix=a/",
|
|
22477
|
+
"--dst-prefix=b/",
|
|
22478
|
+
`${base}...${head}`,
|
|
22479
|
+
"--"
|
|
22480
|
+
], GIT_TIMEOUTS.worktree);
|
|
22481
|
+
if (!diff.ok || !diff.stdout)
|
|
22482
|
+
return null;
|
|
22483
|
+
const hash = createHash9("sha256");
|
|
22484
|
+
for (const line of diff.stdout.split(`
|
|
22591
22485
|
`)) {
|
|
22592
|
-
|
|
22593
|
-
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
22486
|
+
if (line.startsWith("index "))
|
|
22594
22487
|
continue;
|
|
22595
|
-
|
|
22596
|
-
|
|
22597
|
-
|
|
22598
|
-
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
22599
|
-
generated = true;
|
|
22600
|
-
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
22601
|
-
generated = false;
|
|
22602
|
-
}
|
|
22603
|
-
if (generated === null)
|
|
22604
|
-
continue;
|
|
22605
|
-
const regex = patternRegex(dir, pattern);
|
|
22606
|
-
if (regex)
|
|
22607
|
-
out.push({ regex, generated });
|
|
22488
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
22489
|
+
hash.update(`
|
|
22490
|
+
`);
|
|
22608
22491
|
}
|
|
22609
|
-
return
|
|
22492
|
+
return `sha256:${hash.digest("hex")}`;
|
|
22610
22493
|
}
|
|
22611
|
-
function
|
|
22612
|
-
const
|
|
22613
|
-
|
|
22614
|
-
return
|
|
22615
|
-
|
|
22616
|
-
rules: rulesOf(dir, text)
|
|
22617
|
-
};
|
|
22618
|
-
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
22619
|
-
const out = new Set;
|
|
22620
|
-
for (const file of paths) {
|
|
22621
|
-
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
22622
|
-
if (last?.generated)
|
|
22623
|
-
out.add(file);
|
|
22624
|
-
}
|
|
22625
|
-
return out;
|
|
22494
|
+
function storeRoot(cwd) {
|
|
22495
|
+
const location = resolveStore(path15.resolve(cwd));
|
|
22496
|
+
if (location instanceof Error)
|
|
22497
|
+
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");
|
|
22498
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
22626
22499
|
}
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
var
|
|
22632
|
-
|
|
22633
|
-
|
|
22634
|
-
|
|
22635
|
-
|
|
22636
|
-
|
|
22637
|
-
|
|
22638
|
-
|
|
22639
|
-
|
|
22640
|
-
|
|
22641
|
-
|
|
22642
|
-
|
|
22643
|
-
ext,
|
|
22644
|
-
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
22645
|
-
]),
|
|
22646
|
-
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
22647
|
-
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
22648
|
-
ext,
|
|
22649
|
-
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
22650
|
-
])
|
|
22500
|
+
function ledgerPath(cwd) {
|
|
22501
|
+
const root = storeRoot(cwd);
|
|
22502
|
+
return root.ok ? { ok: true, value: path15.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
22503
|
+
}
|
|
22504
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
22505
|
+
var NETWORK_FS_TYPES = new Set([
|
|
22506
|
+
26985,
|
|
22507
|
+
20859,
|
|
22508
|
+
4266872130,
|
|
22509
|
+
4283649346,
|
|
22510
|
+
1702057286,
|
|
22511
|
+
1397113167,
|
|
22512
|
+
12805120,
|
|
22513
|
+
16914839,
|
|
22514
|
+
1196443219,
|
|
22515
|
+
198183888
|
|
22651
22516
|
]);
|
|
22652
|
-
var
|
|
22653
|
-
var
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22517
|
+
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
22518
|
+
var onNetworkFs = (dir) => {
|
|
22519
|
+
if (process.platform !== "linux")
|
|
22520
|
+
return false;
|
|
22521
|
+
try {
|
|
22522
|
+
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
22523
|
+
} catch {
|
|
22524
|
+
return false;
|
|
22525
|
+
}
|
|
22657
22526
|
};
|
|
22658
|
-
var
|
|
22659
|
-
var
|
|
22660
|
-
var
|
|
22661
|
-
var
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
"return",
|
|
22667
|
-
"typeof",
|
|
22668
|
-
"instanceof",
|
|
22669
|
-
"case",
|
|
22670
|
-
"do",
|
|
22671
|
-
"else",
|
|
22672
|
-
"in",
|
|
22673
|
-
"of",
|
|
22674
|
-
"new",
|
|
22675
|
-
"delete",
|
|
22676
|
-
"void",
|
|
22677
|
-
"throw",
|
|
22678
|
-
"yield",
|
|
22679
|
-
"await"
|
|
22680
|
-
]);
|
|
22681
|
-
function substanceOf(file, text) {
|
|
22682
|
-
const syntax = syntaxOf(file);
|
|
22683
|
-
if (!syntax)
|
|
22684
|
-
return plainSubstance(text);
|
|
22685
|
-
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
22686
|
-
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
22687
|
-
return plainSubstance(text);
|
|
22688
|
-
return lex(syntax, text);
|
|
22689
|
-
}
|
|
22690
|
-
function plainSubstance(text) {
|
|
22691
|
-
return text.split(`
|
|
22692
|
-
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
22693
|
-
`);
|
|
22694
|
-
}
|
|
22695
|
-
function lex(syntax, text) {
|
|
22696
|
-
const collapse = !syntax.indented;
|
|
22697
|
-
let out = "";
|
|
22698
|
-
let lineStart = 0;
|
|
22699
|
-
let kept = 0;
|
|
22700
|
-
let pendingSpace = false;
|
|
22701
|
-
const frames = [{ kind: "code", braces: 0 }];
|
|
22702
|
-
let index = 0;
|
|
22703
|
-
if (text.startsWith("#!")) {
|
|
22704
|
-
const end = text.indexOf(`
|
|
22705
|
-
`);
|
|
22706
|
-
out = end < 0 ? text : text.slice(0, end);
|
|
22707
|
-
kept = out.length;
|
|
22708
|
-
index = end < 0 ? text.length : end;
|
|
22527
|
+
var LOCK_WAIT_MS = 5000;
|
|
22528
|
+
var LOCK_STALE_MS = 30000;
|
|
22529
|
+
var LOCK_OWNER = "owner";
|
|
22530
|
+
var lockToken = (lock) => {
|
|
22531
|
+
try {
|
|
22532
|
+
return fs12.readFileSync(path15.join(lock, LOCK_OWNER), "utf8");
|
|
22533
|
+
} catch {
|
|
22534
|
+
return null;
|
|
22709
22535
|
}
|
|
22710
|
-
|
|
22711
|
-
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
}
|
|
22716
|
-
|
|
22717
|
-
|
|
22718
|
-
|
|
22719
|
-
|
|
22720
|
-
|
|
22721
|
-
|
|
22722
|
-
|
|
22723
|
-
|
|
22724
|
-
|
|
22725
|
-
let end = out.length;
|
|
22726
|
-
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
22727
|
-
end -= 1;
|
|
22728
|
-
out = out.slice(0, end);
|
|
22729
|
-
}
|
|
22730
|
-
pendingSpace = false;
|
|
22731
|
-
if (out.length > lineStart) {
|
|
22732
|
-
out += `
|
|
22733
|
-
`;
|
|
22734
|
-
lineStart = out.length;
|
|
22735
|
-
}
|
|
22736
|
-
};
|
|
22737
|
-
const regexAllowed = () => {
|
|
22738
|
-
let end = out.length;
|
|
22739
|
-
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
22740
|
-
end -= 1;
|
|
22741
|
-
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
22742
|
-
return true;
|
|
22743
|
-
let start = end;
|
|
22744
|
-
while (start > 0 && WORD.test(out[start - 1]))
|
|
22745
|
-
start -= 1;
|
|
22746
|
-
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
22747
|
-
};
|
|
22748
|
-
const scanString = (quote, escapes, multiline) => {
|
|
22749
|
-
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
22750
|
-
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
22751
|
-
let end = index + quote.length;
|
|
22752
|
-
while (end < text.length) {
|
|
22753
|
-
const char = text[end];
|
|
22754
|
-
if (escapes && char === "\\") {
|
|
22755
|
-
end += 2;
|
|
22756
|
-
continue;
|
|
22757
|
-
}
|
|
22758
|
-
if (text.startsWith(quote, end)) {
|
|
22759
|
-
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
22760
|
-
return false;
|
|
22761
|
-
emitVerbatim(text.slice(index, end + quote.length));
|
|
22762
|
-
index = end + quote.length;
|
|
22763
|
-
return true;
|
|
22764
|
-
}
|
|
22765
|
-
if (char === `
|
|
22766
|
-
` && !multiline)
|
|
22767
|
-
return false;
|
|
22768
|
-
end += 1;
|
|
22769
|
-
}
|
|
22770
|
-
return false;
|
|
22771
|
-
};
|
|
22772
|
-
while (index < text.length) {
|
|
22773
|
-
const frame = frames[frames.length - 1];
|
|
22774
|
-
const char = text[index];
|
|
22775
|
-
if (frame.kind === "template") {
|
|
22776
|
-
if (char === "\\") {
|
|
22777
|
-
emitVerbatim(text.slice(index, index + 2));
|
|
22778
|
-
index += 2;
|
|
22779
|
-
} else if (char === "`") {
|
|
22780
|
-
emitVerbatim(char);
|
|
22781
|
-
frames.pop();
|
|
22782
|
-
index += 1;
|
|
22783
|
-
} else if (text.startsWith("${", index)) {
|
|
22784
|
-
emitVerbatim("${");
|
|
22785
|
-
frames.push({ kind: "code", braces: 0 });
|
|
22786
|
-
index += 2;
|
|
22787
|
-
} else {
|
|
22788
|
-
out += char;
|
|
22789
|
-
kept = out.length;
|
|
22790
|
-
index += 1;
|
|
22791
|
-
}
|
|
22792
|
-
continue;
|
|
22793
|
-
}
|
|
22794
|
-
if (char === `
|
|
22795
|
-
`) {
|
|
22796
|
-
newline();
|
|
22797
|
-
index += 1;
|
|
22798
|
-
continue;
|
|
22799
|
-
}
|
|
22800
|
-
if (SPACE.test(char)) {
|
|
22801
|
-
if (collapse)
|
|
22802
|
-
pendingSpace = true;
|
|
22803
|
-
else
|
|
22804
|
-
out += char;
|
|
22805
|
-
index += 1;
|
|
22806
|
-
continue;
|
|
22807
|
-
}
|
|
22808
|
-
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
22809
|
-
if (lineOpener) {
|
|
22810
|
-
const end = text.indexOf(`
|
|
22811
|
-
`, index);
|
|
22812
|
-
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
22813
|
-
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
22814
|
-
emitVerbatim(comment);
|
|
22815
|
-
else
|
|
22816
|
-
pendingSpace = true;
|
|
22817
|
-
index = end < 0 ? text.length : end;
|
|
22818
|
-
continue;
|
|
22819
|
-
}
|
|
22820
|
-
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
22821
|
-
if (block) {
|
|
22822
|
-
const end = text.indexOf(block[1], index + block[0].length);
|
|
22823
|
-
if (end < 0)
|
|
22824
|
-
return null;
|
|
22825
|
-
const comment = text.slice(index, end + block[1].length);
|
|
22826
|
-
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
22827
|
-
emitVerbatim(comment);
|
|
22828
|
-
else
|
|
22829
|
-
pendingSpace = true;
|
|
22830
|
-
if (comment.includes(`
|
|
22831
|
-
`))
|
|
22832
|
-
newline();
|
|
22833
|
-
index = end + block[1].length;
|
|
22834
|
-
continue;
|
|
22835
|
-
}
|
|
22836
|
-
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
22837
|
-
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
22536
|
+
};
|
|
22537
|
+
var lockAgeMs = (lock) => {
|
|
22538
|
+
for (const target of [path15.join(lock, LOCK_OWNER), lock]) {
|
|
22539
|
+
try {
|
|
22540
|
+
return Date.now() - fs12.statSync(target).mtimeMs;
|
|
22541
|
+
} catch {}
|
|
22542
|
+
}
|
|
22543
|
+
return null;
|
|
22544
|
+
};
|
|
22545
|
+
var ledgerLock = {
|
|
22546
|
+
acquire(lock) {
|
|
22547
|
+
try {
|
|
22548
|
+
fs12.mkdirSync(lock);
|
|
22549
|
+
} catch (error) {
|
|
22550
|
+
if (error.code === "EEXIST")
|
|
22838
22551
|
return null;
|
|
22839
|
-
|
|
22552
|
+
throw error;
|
|
22840
22553
|
}
|
|
22841
|
-
|
|
22842
|
-
|
|
22843
|
-
|
|
22844
|
-
|
|
22554
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
22555
|
+
fs12.writeFileSync(path15.join(lock, LOCK_OWNER), token);
|
|
22556
|
+
return token;
|
|
22557
|
+
},
|
|
22558
|
+
release(lock, token) {
|
|
22559
|
+
if (lockToken(lock) !== token)
|
|
22560
|
+
return false;
|
|
22561
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
22562
|
+
try {
|
|
22563
|
+
fs12.renameSync(lock, tomb);
|
|
22564
|
+
} catch {
|
|
22565
|
+
return false;
|
|
22845
22566
|
}
|
|
22846
|
-
if (
|
|
22847
|
-
|
|
22848
|
-
|
|
22849
|
-
|
|
22567
|
+
if (lockToken(tomb) !== token) {
|
|
22568
|
+
try {
|
|
22569
|
+
fs12.renameSync(tomb, lock);
|
|
22570
|
+
} catch {}
|
|
22571
|
+
return false;
|
|
22850
22572
|
}
|
|
22851
|
-
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
|
|
22573
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
22574
|
+
return true;
|
|
22575
|
+
},
|
|
22576
|
+
token: lockToken,
|
|
22577
|
+
reap(lock, observed) {
|
|
22578
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
22579
|
+
try {
|
|
22580
|
+
fs12.renameSync(lock, tomb);
|
|
22581
|
+
} catch {
|
|
22582
|
+
return false;
|
|
22855
22583
|
}
|
|
22856
|
-
if (
|
|
22857
|
-
|
|
22858
|
-
|
|
22859
|
-
index += 1;
|
|
22860
|
-
continue;
|
|
22584
|
+
if (lockToken(tomb) === observed) {
|
|
22585
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
22586
|
+
return true;
|
|
22861
22587
|
}
|
|
22862
|
-
|
|
22588
|
+
try {
|
|
22589
|
+
fs12.renameSync(tomb, lock);
|
|
22590
|
+
} catch {
|
|
22591
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
22592
|
+
}
|
|
22593
|
+
return false;
|
|
22594
|
+
}
|
|
22595
|
+
};
|
|
22596
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
22597
|
+
const lock = `${file}.lock`;
|
|
22598
|
+
const deadline = Date.now() + waitMs;
|
|
22599
|
+
let token = null;
|
|
22600
|
+
for (;; ) {
|
|
22601
|
+
token = ledgerLock.acquire(lock);
|
|
22602
|
+
if (token)
|
|
22603
|
+
break;
|
|
22604
|
+
const observed = lockToken(lock);
|
|
22605
|
+
const age = lockAgeMs(lock);
|
|
22606
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
22607
|
+
continue;
|
|
22608
|
+
if (Date.now() >= deadline)
|
|
22863
22609
|
return null;
|
|
22864
|
-
|
|
22865
|
-
|
|
22866
|
-
|
|
22867
|
-
|
|
22610
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
22611
|
+
}
|
|
22612
|
+
try {
|
|
22613
|
+
return fn();
|
|
22614
|
+
} finally {
|
|
22615
|
+
ledgerLock.release(lock, token);
|
|
22616
|
+
}
|
|
22617
|
+
}
|
|
22618
|
+
var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
|
|
22619
|
+
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
22620
|
+
${body}`);
|
|
22621
|
+
var TAIL_BYTES = 64 * 1024;
|
|
22622
|
+
var lastLine = (fd, size) => {
|
|
22623
|
+
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
22624
|
+
const buffer = Buffer.alloc(span);
|
|
22625
|
+
fs12.readSync(fd, buffer, 0, span, size - span);
|
|
22626
|
+
const end = buffer.lastIndexOf(10);
|
|
22627
|
+
if (end < 0) {
|
|
22628
|
+
if (span === size)
|
|
22868
22629
|
return null;
|
|
22630
|
+
continue;
|
|
22869
22631
|
}
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
22874
|
-
|
|
22875
|
-
|
|
22632
|
+
let stop = end;
|
|
22633
|
+
let text = "";
|
|
22634
|
+
while (stop >= 0) {
|
|
22635
|
+
const start = buffer.lastIndexOf(10, stop - 1);
|
|
22636
|
+
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
22637
|
+
if (text) {
|
|
22638
|
+
if (start >= 0 || span === size)
|
|
22639
|
+
return text;
|
|
22640
|
+
break;
|
|
22641
|
+
}
|
|
22642
|
+
if (start < 0) {
|
|
22643
|
+
if (span === size)
|
|
22876
22644
|
return null;
|
|
22877
|
-
|
|
22878
|
-
end += 1;
|
|
22879
|
-
else if (text[end] === "[")
|
|
22880
|
-
inClass = true;
|
|
22881
|
-
else if (text[end] === "]")
|
|
22882
|
-
inClass = false;
|
|
22883
|
-
end += 1;
|
|
22645
|
+
break;
|
|
22884
22646
|
}
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22647
|
+
stop = start;
|
|
22648
|
+
}
|
|
22649
|
+
if (span === size)
|
|
22650
|
+
return text || null;
|
|
22651
|
+
}
|
|
22652
|
+
return null;
|
|
22653
|
+
};
|
|
22654
|
+
var tighten = (fd) => {
|
|
22655
|
+
try {
|
|
22656
|
+
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
22657
|
+
fs12.fchmodSync(fd, 384);
|
|
22658
|
+
} catch {}
|
|
22659
|
+
};
|
|
22660
|
+
function appendLine(file, build, options) {
|
|
22661
|
+
const write = () => {
|
|
22662
|
+
const fd = fs12.openSync(file, "a+", 384);
|
|
22663
|
+
try {
|
|
22664
|
+
if (process.platform !== "win32")
|
|
22665
|
+
tighten(fd);
|
|
22666
|
+
const size = fs12.fstatSync(fd).size;
|
|
22667
|
+
let fence = "";
|
|
22668
|
+
if (size > 0) {
|
|
22669
|
+
const last = Buffer.alloc(1);
|
|
22670
|
+
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
22671
|
+
if (last[0] !== 10)
|
|
22672
|
+
fence = `
|
|
22673
|
+
`;
|
|
22674
|
+
}
|
|
22675
|
+
const line = build(lastLine(fd, size));
|
|
22676
|
+
if (!line.ok)
|
|
22677
|
+
return line;
|
|
22678
|
+
const bytes = Buffer.from(fence + line.value);
|
|
22679
|
+
const written = fs12.writeSync(fd, bytes);
|
|
22680
|
+
if (written !== bytes.length)
|
|
22681
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
22682
|
+
return { ok: true, value: undefined };
|
|
22683
|
+
} finally {
|
|
22684
|
+
fs12.closeSync(fd);
|
|
22685
|
+
}
|
|
22686
|
+
};
|
|
22687
|
+
try {
|
|
22688
|
+
fs12.mkdirSync(path15.dirname(file), { recursive: true });
|
|
22689
|
+
if (!options.forceLock && !onNetworkFs(path15.dirname(file)))
|
|
22690
|
+
return write();
|
|
22691
|
+
const locked = withLedgerLock(file, write);
|
|
22692
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
22693
|
+
} catch (error) {
|
|
22694
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
22695
|
+
}
|
|
22696
|
+
}
|
|
22697
|
+
var sealed = (row, id, at, previous) => {
|
|
22698
|
+
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
22699
|
+
const prevHash = previous === null ? null : sha2562(previous);
|
|
22700
|
+
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
22701
|
+
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
22702
|
+
return { full, line: `${JSON.stringify(full)}
|
|
22703
|
+
` };
|
|
22704
|
+
};
|
|
22705
|
+
function appendRaw(cwd, row, options) {
|
|
22706
|
+
const file = ledgerPath(cwd);
|
|
22707
|
+
if (!file.ok)
|
|
22708
|
+
return file;
|
|
22709
|
+
const id = newId2();
|
|
22710
|
+
const at = (options.now ?? new Date).toISOString();
|
|
22711
|
+
let full = null;
|
|
22712
|
+
const written = appendLine(file.value, (previous) => {
|
|
22713
|
+
const next = sealed(row, id, at, previous);
|
|
22714
|
+
const bytes = Buffer.byteLength(next.line);
|
|
22715
|
+
if (bytes > MAX_LINE_BYTES)
|
|
22716
|
+
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>`);
|
|
22717
|
+
full = next.full;
|
|
22718
|
+
return { ok: true, value: next.line };
|
|
22719
|
+
}, options);
|
|
22720
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
22721
|
+
}
|
|
22722
|
+
function observedRowBytes(row) {
|
|
22723
|
+
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
22724
|
+
return Buffer.byteLength(sample.line);
|
|
22725
|
+
}
|
|
22726
|
+
function appendObserved(cwd, row, options = {}) {
|
|
22727
|
+
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
22728
|
+
}
|
|
22729
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22730
|
+
var str2 = (value) => typeof value === "string" ? value : null;
|
|
22731
|
+
var normalizeActor = (value) => {
|
|
22732
|
+
const actor = isRecord3(value) ? value : {};
|
|
22733
|
+
return {
|
|
22734
|
+
host: str2(actor.host) ?? "unknown",
|
|
22735
|
+
session: str2(actor.session),
|
|
22736
|
+
agentId: str2(actor.agentId)
|
|
22737
|
+
};
|
|
22738
|
+
};
|
|
22739
|
+
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
22740
|
+
if (by.type !== target.type)
|
|
22741
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
22742
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
22743
|
+
return "only the session that wrote a row can supersede it";
|
|
22744
|
+
if (!onBranch(target))
|
|
22745
|
+
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
22746
|
+
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
22747
|
+
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
22748
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
22749
|
+
return "a self verdict cannot replace an independent verdict";
|
|
22750
|
+
return null;
|
|
22751
|
+
}
|
|
22752
|
+
function readLedger(cwd, options = {}) {
|
|
22753
|
+
const file = ledgerPath(cwd);
|
|
22754
|
+
if (!file.ok)
|
|
22755
|
+
return file;
|
|
22756
|
+
let text = "";
|
|
22757
|
+
let stat = null;
|
|
22758
|
+
try {
|
|
22759
|
+
stat = fs12.statSync(file.value);
|
|
22760
|
+
text = fs12.readFileSync(file.value, "utf8");
|
|
22761
|
+
} catch (error) {
|
|
22762
|
+
if (error.code === "ENOENT")
|
|
22763
|
+
return {
|
|
22764
|
+
ok: true,
|
|
22765
|
+
value: {
|
|
22766
|
+
path: file.value,
|
|
22767
|
+
rows: [],
|
|
22768
|
+
skipped: 0,
|
|
22769
|
+
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
22770
|
+
}
|
|
22771
|
+
};
|
|
22772
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
22773
|
+
}
|
|
22774
|
+
const rows = [];
|
|
22775
|
+
let skipped = 0;
|
|
22776
|
+
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
22777
|
+
const earlier = new Set([null]);
|
|
22778
|
+
let chainStarted = false;
|
|
22779
|
+
const ids = new Set;
|
|
22780
|
+
for (const raw of text.split(`
|
|
22781
|
+
`)) {
|
|
22782
|
+
const trimmed = raw.trim();
|
|
22783
|
+
if (!trimmed)
|
|
22784
|
+
continue;
|
|
22785
|
+
const linked = (prevHash) => earlier.has(prevHash);
|
|
22786
|
+
if (options.integrity)
|
|
22787
|
+
earlier.add(sha2562(trimmed));
|
|
22788
|
+
let parsed;
|
|
22789
|
+
try {
|
|
22790
|
+
parsed = JSON.parse(trimmed);
|
|
22791
|
+
} catch {
|
|
22792
|
+
skipped += 1;
|
|
22889
22793
|
continue;
|
|
22890
22794
|
}
|
|
22891
|
-
if (
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
else if (char === "}") {
|
|
22895
|
-
if (frame.braces === 0) {
|
|
22896
|
-
emitVerbatim(char);
|
|
22897
|
-
frames.pop();
|
|
22898
|
-
index += 1;
|
|
22899
|
-
continue;
|
|
22900
|
-
}
|
|
22901
|
-
frame.braces -= 1;
|
|
22902
|
-
}
|
|
22795
|
+
if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
22796
|
+
skipped += 1;
|
|
22797
|
+
continue;
|
|
22903
22798
|
}
|
|
22904
|
-
|
|
22905
|
-
|
|
22799
|
+
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
22800
|
+
let reason = null;
|
|
22801
|
+
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
22802
|
+
chainStarted = true;
|
|
22803
|
+
const { rowHash, ...body } = parsed;
|
|
22804
|
+
const prevHash = str2(parsed.prevHash);
|
|
22805
|
+
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
22806
|
+
reason = "hash_mismatch";
|
|
22807
|
+
else if (!linked(prevHash))
|
|
22808
|
+
reason = "broken_link";
|
|
22809
|
+
} else if (chainStarted)
|
|
22810
|
+
reason = "unsigned";
|
|
22811
|
+
else
|
|
22812
|
+
rowIntegrity = "legacy";
|
|
22813
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
22814
|
+
reason = "duplicate_id";
|
|
22815
|
+
if (options.integrity)
|
|
22816
|
+
ids.add(parsed.id);
|
|
22817
|
+
if (reason)
|
|
22818
|
+
rowIntegrity = "unverified";
|
|
22819
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
22820
|
+
const row = {
|
|
22821
|
+
...parsed,
|
|
22822
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
22823
|
+
id: parsed.id,
|
|
22824
|
+
at: parsed.at,
|
|
22825
|
+
type: parsed.type,
|
|
22826
|
+
actor: normalizeActor(parsed.actor),
|
|
22827
|
+
branch: str2(parsed.branch),
|
|
22828
|
+
head: str2(parsed.head),
|
|
22829
|
+
base: str2(parsed.base),
|
|
22830
|
+
baseSha: str2(parsed.baseSha),
|
|
22831
|
+
patchId: str2(parsed.patchId),
|
|
22832
|
+
diffHash: str2(parsed.diffHash),
|
|
22833
|
+
tree: str2(parsed.tree),
|
|
22834
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
22835
|
+
...pr === undefined ? {} : { pr },
|
|
22836
|
+
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
22837
|
+
seq: rows.length + 1,
|
|
22838
|
+
superseded: false,
|
|
22839
|
+
supersedeIgnored: false,
|
|
22840
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
22841
|
+
...reason ? { integrityReason: reason } : {}
|
|
22842
|
+
};
|
|
22843
|
+
rows.push(row);
|
|
22844
|
+
if (!options.integrity)
|
|
22845
|
+
continue;
|
|
22846
|
+
if (rowIntegrity === "verified")
|
|
22847
|
+
integrity.chained += 1;
|
|
22848
|
+
else if (rowIntegrity === "legacy")
|
|
22849
|
+
integrity.legacy += 1;
|
|
22850
|
+
else if (reason)
|
|
22851
|
+
integrity.unverified.push({
|
|
22852
|
+
seq: row.seq,
|
|
22853
|
+
id: row.id,
|
|
22854
|
+
type: row.type,
|
|
22855
|
+
branch: row.branch,
|
|
22856
|
+
reason
|
|
22857
|
+
});
|
|
22906
22858
|
}
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
22917
|
-
|
|
22918
|
-
|
|
22859
|
+
const scopes = new Map;
|
|
22860
|
+
const scopeOf = (branch) => {
|
|
22861
|
+
let scope = scopes.get(branch);
|
|
22862
|
+
if (!scope)
|
|
22863
|
+
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
22864
|
+
return scope;
|
|
22865
|
+
};
|
|
22866
|
+
const byId = new Map;
|
|
22867
|
+
for (const row of rows) {
|
|
22868
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
22869
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
22870
|
+
target.superseded = true;
|
|
22871
|
+
else if (row.supersedes)
|
|
22872
|
+
row.supersedeIgnored = true;
|
|
22873
|
+
if (!byId.has(row.id))
|
|
22874
|
+
byId.set(row.id, row);
|
|
22919
22875
|
}
|
|
22920
|
-
|
|
22921
|
-
|
|
22922
|
-
|
|
22923
|
-
|
|
22876
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
22877
|
+
tamperCache.set(file.value, {
|
|
22878
|
+
size: stat.size,
|
|
22879
|
+
stamp: fileStamp(stat),
|
|
22880
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
22881
|
+
});
|
|
22882
|
+
return {
|
|
22883
|
+
ok: true,
|
|
22884
|
+
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
22885
|
+
};
|
|
22924
22886
|
}
|
|
22925
|
-
var
|
|
22926
|
-
|
|
22927
|
-
|
|
22928
|
-
|
|
22887
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
22888
|
+
var tamperCache = new Map;
|
|
22889
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
22890
|
+
function ledgerTampering(cwd) {
|
|
22891
|
+
const file = ledgerPath(cwd);
|
|
22892
|
+
if (!file.ok)
|
|
22893
|
+
return [];
|
|
22929
22894
|
try {
|
|
22930
|
-
|
|
22895
|
+
const stat = fs12.statSync(file.value);
|
|
22896
|
+
const cached = tamperCache.get(file.value);
|
|
22897
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
22898
|
+
return cached.breaks;
|
|
22931
22899
|
} catch {
|
|
22932
|
-
return
|
|
22900
|
+
return [];
|
|
22933
22901
|
}
|
|
22902
|
+
const read = readLedger(cwd, { integrity: true });
|
|
22903
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
22934
22904
|
}
|
|
22935
|
-
function
|
|
22936
|
-
const
|
|
22937
|
-
const
|
|
22938
|
-
if (
|
|
22939
|
-
return false;
|
|
22940
|
-
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
22941
|
-
return false;
|
|
22942
|
-
const a = substanceOf(file, left);
|
|
22943
|
-
return a !== null && a === substanceOf(file, right);
|
|
22905
|
+
function tamperedUnblock(cwd) {
|
|
22906
|
+
const file = ledgerPath(cwd);
|
|
22907
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
22908
|
+
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`;
|
|
22944
22909
|
}
|
|
22945
|
-
|
|
22946
|
-
|
|
22947
|
-
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
|
|
22951
|
-
|
|
22952
|
-
|
|
22953
|
-
|
|
22954
|
-
|
|
22955
|
-
|
|
22956
|
-
|
|
22957
|
-
|
|
22958
|
-
|
|
22959
|
-
|
|
22960
|
-
|
|
22961
|
-
|
|
22962
|
-
"
|
|
22963
|
-
"gate",
|
|
22964
|
-
"gates",
|
|
22965
|
-
"policy",
|
|
22966
|
-
"policies",
|
|
22967
|
-
"hook",
|
|
22968
|
-
"hooks",
|
|
22969
|
-
"husky",
|
|
22970
|
-
"lefthook",
|
|
22971
|
-
"release",
|
|
22972
|
-
"releases",
|
|
22973
|
-
"migration",
|
|
22974
|
-
"migrations",
|
|
22975
|
-
"migrate",
|
|
22976
|
-
"workflows"
|
|
22977
|
-
]);
|
|
22978
|
-
var RISK_FILES = new Set([
|
|
22979
|
-
".gitlab-ci.yml",
|
|
22980
|
-
"Jenkinsfile",
|
|
22981
|
-
"azure-pipelines.yml",
|
|
22982
|
-
".pre-commit-config.yaml",
|
|
22983
|
-
".workit/risk-paths"
|
|
22984
|
-
]);
|
|
22985
|
-
var RISK_NAMES = new Set([".gitattributes"]);
|
|
22986
|
-
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
22987
|
-
var LOCKFILES = new Set([
|
|
22988
|
-
"bun.lock",
|
|
22989
|
-
"bun.lockb",
|
|
22990
|
-
"package-lock.json",
|
|
22991
|
-
"npm-shrinkwrap.json",
|
|
22992
|
-
"yarn.lock",
|
|
22993
|
-
"pnpm-lock.yaml",
|
|
22994
|
-
"Cargo.lock",
|
|
22995
|
-
"go.sum",
|
|
22996
|
-
"poetry.lock",
|
|
22997
|
-
"uv.lock",
|
|
22998
|
-
"Pipfile.lock",
|
|
22999
|
-
"Gemfile.lock",
|
|
23000
|
-
"composer.lock"
|
|
23001
|
-
]);
|
|
23002
|
-
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
23003
|
-
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
23004
|
-
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
23005
|
-
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
23006
|
-
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
23007
|
-
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
23008
|
-
function kindOf(file) {
|
|
23009
|
-
if (file.binary)
|
|
23010
|
-
return "behavior";
|
|
23011
|
-
if (file.generated)
|
|
23012
|
-
return "generated";
|
|
23013
|
-
if (LOCKFILES.has(basename(file.path)))
|
|
23014
|
-
return "lockfile";
|
|
23015
|
-
if (isDoc(file.path))
|
|
23016
|
-
return "doc";
|
|
23017
|
-
if (file.cosmetic)
|
|
23018
|
-
return "cosmetic";
|
|
23019
|
-
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
22910
|
+
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)}`;
|
|
22911
|
+
function verdictBasis(verdict, current) {
|
|
22912
|
+
if (verdict.head && verdict.head === current.head)
|
|
22913
|
+
return "fresh";
|
|
22914
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
22915
|
+
return "carried";
|
|
22916
|
+
return "stale";
|
|
22917
|
+
}
|
|
22918
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
22919
|
+
function trailerSessions(cwd, base, head) {
|
|
22920
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
22921
|
+
return [];
|
|
22922
|
+
const out = git4(cwd, [
|
|
22923
|
+
"log",
|
|
22924
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
22925
|
+
`${base}..${head}`
|
|
22926
|
+
]);
|
|
22927
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
23020
22928
|
}
|
|
23021
|
-
var
|
|
23022
|
-
function
|
|
23023
|
-
|
|
23024
|
-
|
|
23025
|
-
|
|
23026
|
-
|
|
23027
|
-
|
|
23028
|
-
|
|
23029
|
-
|
|
23030
|
-
|
|
23031
|
-
}
|
|
23032
|
-
|
|
23033
|
-
|
|
23034
|
-
|
|
23035
|
-
|
|
23036
|
-
|
|
22929
|
+
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
22930
|
+
function branchScope(cwd, branch) {
|
|
22931
|
+
const renamedAt = new Map;
|
|
22932
|
+
let since = Number.NEGATIVE_INFINITY;
|
|
22933
|
+
if (safeRef2(branch)) {
|
|
22934
|
+
const log = git4(cwd, [
|
|
22935
|
+
"log",
|
|
22936
|
+
"-g",
|
|
22937
|
+
"--date=unix",
|
|
22938
|
+
"--format=%gd%x09%gs",
|
|
22939
|
+
`refs/heads/${branch}`,
|
|
22940
|
+
"--"
|
|
22941
|
+
]);
|
|
22942
|
+
for (const line of (log ?? "").split(`
|
|
22943
|
+
`)) {
|
|
22944
|
+
const [selector = "", subject = ""] = line.split("\t");
|
|
22945
|
+
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
22946
|
+
if (!Number.isFinite(seconds))
|
|
22947
|
+
continue;
|
|
22948
|
+
since = seconds * 1000;
|
|
22949
|
+
const match = RENAMED.exec(subject);
|
|
22950
|
+
if (!match || match[1] === branch)
|
|
22951
|
+
continue;
|
|
22952
|
+
const until = (seconds + 1) * 1000;
|
|
22953
|
+
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
23037
22954
|
}
|
|
23038
|
-
regex = new RegExp(`^${source}$`, "u");
|
|
23039
|
-
globCache.set(pattern, regex);
|
|
23040
22955
|
}
|
|
23041
|
-
return
|
|
22956
|
+
return (row) => {
|
|
22957
|
+
if (row.branch === branch)
|
|
22958
|
+
return true;
|
|
22959
|
+
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
22960
|
+
if (until === undefined)
|
|
22961
|
+
return false;
|
|
22962
|
+
const at = Date.parse(row.at);
|
|
22963
|
+
return at >= since && at <= until;
|
|
22964
|
+
};
|
|
23042
22965
|
}
|
|
23043
|
-
|
|
23044
|
-
|
|
23045
|
-
|
|
23046
|
-
|
|
22966
|
+
var renameAwareBranch = (scopeOf, branch) => {
|
|
22967
|
+
return (target) => {
|
|
22968
|
+
const own = typeof branch === "string" ? branch : null;
|
|
22969
|
+
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
22970
|
+
if (own === theirs)
|
|
22971
|
+
return true;
|
|
22972
|
+
if (own === null || theirs === null || typeof target.at !== "string")
|
|
22973
|
+
return false;
|
|
22974
|
+
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
22975
|
+
};
|
|
22976
|
+
};
|
|
22977
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
22978
|
+
const sessions = new Set;
|
|
22979
|
+
for (const row of rows) {
|
|
22980
|
+
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
22981
|
+
continue;
|
|
22982
|
+
const session = str2(row.session) ?? row.actor.session;
|
|
22983
|
+
if (session)
|
|
22984
|
+
sessions.add(session);
|
|
22985
|
+
}
|
|
22986
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
22987
|
+
sessions.add(session);
|
|
22988
|
+
return sessions;
|
|
22989
|
+
}
|
|
22990
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
22991
|
+
var authoredBy = (authors, session) => {
|
|
22992
|
+
if (authors.has(session))
|
|
23047
22993
|
return true;
|
|
23048
|
-
|
|
23049
|
-
|
|
23050
|
-
|
|
22994
|
+
for (const author of authors)
|
|
22995
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
22996
|
+
return true;
|
|
22997
|
+
return false;
|
|
22998
|
+
};
|
|
22999
|
+
var TRUST_REASONS = new Set([
|
|
23000
|
+
"unsigned",
|
|
23001
|
+
"hash_mismatch",
|
|
23002
|
+
"broken_link",
|
|
23003
|
+
"duplicate_id",
|
|
23004
|
+
"unissued_session",
|
|
23005
|
+
"unbound_session"
|
|
23006
|
+
]);
|
|
23007
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
23008
|
+
"cli",
|
|
23009
|
+
"opencode",
|
|
23010
|
+
"pi",
|
|
23011
|
+
"codex_cli",
|
|
23012
|
+
"codex_desktop"
|
|
23013
|
+
]);
|
|
23014
|
+
function sessionTrust(cwd, row) {
|
|
23015
|
+
const session = row.actor.session;
|
|
23016
|
+
if (!session || row.self === true)
|
|
23017
|
+
return null;
|
|
23018
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
23019
|
+
const host = row.actor.host;
|
|
23020
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
23021
|
+
if (!issuedOnly)
|
|
23022
|
+
return null;
|
|
23023
|
+
const binding = issuedBinding(cwd, session);
|
|
23024
|
+
if (binding === null)
|
|
23025
|
+
return "unissued_session";
|
|
23026
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
23051
23027
|
}
|
|
23052
|
-
|
|
23053
|
-
|
|
23054
|
-
|
|
23055
|
-
|
|
23056
|
-
|
|
23057
|
-
const
|
|
23058
|
-
|
|
23059
|
-
|
|
23060
|
-
|
|
23061
|
-
|
|
23062
|
-
|
|
23063
|
-
const
|
|
23064
|
-
const
|
|
23065
|
-
|
|
23066
|
-
|
|
23067
|
-
|
|
23068
|
-
|
|
23069
|
-
|
|
23028
|
+
function trustReasons(cwd, row) {
|
|
23029
|
+
if (row.integrity === "unverified")
|
|
23030
|
+
return [row.integrityReason ?? "unsigned"];
|
|
23031
|
+
if (row.integrity !== "verified")
|
|
23032
|
+
return ["unsigned"];
|
|
23033
|
+
const session = sessionTrust(cwd, row);
|
|
23034
|
+
return session ? [session] : [];
|
|
23035
|
+
}
|
|
23036
|
+
function withIntegrity(cwd, rows) {
|
|
23037
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
23038
|
+
return rows;
|
|
23039
|
+
const read = readLedger(cwd, { integrity: true });
|
|
23040
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
23041
|
+
return rows.map((row) => {
|
|
23042
|
+
if (row.integrity !== undefined)
|
|
23043
|
+
return row;
|
|
23044
|
+
const fresh = bySeq.get(row.seq);
|
|
23045
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
23070
23046
|
});
|
|
23071
|
-
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
23072
|
-
if (input.riskTier === "high")
|
|
23073
|
-
return result("full", ["the task judgment records high risk"]);
|
|
23074
|
-
if (riskFiles.length)
|
|
23075
|
-
return result("full", [
|
|
23076
|
-
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
23077
|
-
]);
|
|
23078
|
-
if (!behavior.length)
|
|
23079
|
-
return result("none", [
|
|
23080
|
-
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
23081
|
-
]);
|
|
23082
|
-
if (lines >= fullMinLines)
|
|
23083
|
-
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
23084
|
-
if (lines > selfMaxLines)
|
|
23085
|
-
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
23086
|
-
if (behavior.length > selfMaxFiles)
|
|
23087
|
-
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
23088
|
-
if (opaque.length)
|
|
23089
|
-
return result("focused", [
|
|
23090
|
-
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
23091
|
-
]);
|
|
23092
|
-
return result("self", [
|
|
23093
|
-
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
23094
|
-
]);
|
|
23095
23047
|
}
|
|
23096
|
-
|
|
23097
|
-
|
|
23098
|
-
|
|
23099
|
-
|
|
23100
|
-
|
|
23101
|
-
|
|
23102
|
-
|
|
23103
|
-
|
|
23104
|
-
|
|
23105
|
-
|
|
23106
|
-
|
|
23107
|
-
|
|
23108
|
-
|
|
23109
|
-
|
|
23048
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
23049
|
+
var independenceReasons = (row, authors) => {
|
|
23050
|
+
const reasons = [];
|
|
23051
|
+
if (!row.actor.session)
|
|
23052
|
+
reasons.push("no_session");
|
|
23053
|
+
else if (authoredBy(authors, row.actor.session))
|
|
23054
|
+
reasons.push("author_session");
|
|
23055
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
23056
|
+
reasons.push("self");
|
|
23057
|
+
return reasons;
|
|
23058
|
+
};
|
|
23059
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
23060
|
+
let effective = null;
|
|
23061
|
+
for (const row of rows) {
|
|
23062
|
+
if (!effective) {
|
|
23063
|
+
effective = row;
|
|
23064
|
+
continue;
|
|
23065
|
+
}
|
|
23066
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
23067
|
+
continue;
|
|
23068
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
23069
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
23070
|
+
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
23071
|
+
continue;
|
|
23072
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
23073
|
+
continue;
|
|
23074
|
+
effective = row;
|
|
23110
23075
|
}
|
|
23076
|
+
return effective;
|
|
23111
23077
|
}
|
|
23112
|
-
var
|
|
23113
|
-
|
|
23114
|
-
|
|
23115
|
-
|
|
23116
|
-
|
|
23117
|
-
|
|
23118
|
-
|
|
23119
|
-
|
|
23120
|
-
|
|
23121
|
-
|
|
23122
|
-
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
|
|
23126
|
-
|
|
23127
|
-
|
|
23128
|
-
|
|
23129
|
-
|
|
23130
|
-
|
|
23131
|
-
|
|
23078
|
+
var restackExact = new WeakMap;
|
|
23079
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
23080
|
+
if (!from || !to)
|
|
23081
|
+
return false;
|
|
23082
|
+
const exact = (row, fromHead, head) => {
|
|
23083
|
+
if (exactIn === null)
|
|
23084
|
+
return true;
|
|
23085
|
+
const cwd = exactIn;
|
|
23086
|
+
let known = restackExact.get(row);
|
|
23087
|
+
if (known === undefined) {
|
|
23088
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
23089
|
+
from: fromHead,
|
|
23090
|
+
to: head,
|
|
23091
|
+
fromBase: row.fromBase,
|
|
23092
|
+
toBase: row.toBase
|
|
23093
|
+
});
|
|
23094
|
+
restackExact.set(row, known);
|
|
23095
|
+
}
|
|
23096
|
+
return known;
|
|
23097
|
+
};
|
|
23098
|
+
const edges = new Map;
|
|
23099
|
+
for (const row of rows)
|
|
23100
|
+
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))
|
|
23101
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
23102
|
+
const seen = new Set([from]);
|
|
23103
|
+
const queue = [from];
|
|
23104
|
+
while (queue.length) {
|
|
23105
|
+
const next = queue.shift();
|
|
23106
|
+
if (next === to)
|
|
23107
|
+
return true;
|
|
23108
|
+
for (const target of edges.get(next) ?? [])
|
|
23109
|
+
if (!seen.has(target)) {
|
|
23110
|
+
seen.add(target);
|
|
23111
|
+
queue.push(target);
|
|
23132
23112
|
}
|
|
23133
|
-
if (before && after && cosmeticChange(entry.path, before, after))
|
|
23134
|
-
out.add(entry.path);
|
|
23135
|
-
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
23136
|
-
opaque.add(entry.path);
|
|
23137
23113
|
}
|
|
23138
|
-
return
|
|
23114
|
+
return false;
|
|
23139
23115
|
}
|
|
23140
|
-
function
|
|
23141
|
-
const
|
|
23142
|
-
const
|
|
23143
|
-
|
|
23144
|
-
|
|
23145
|
-
const
|
|
23146
|
-
|
|
23147
|
-
|
|
23148
|
-
|
|
23149
|
-
|
|
23116
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
23117
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
23118
|
+
const head = branchHead(cwd, branch);
|
|
23119
|
+
const fallbackBase = defaultBase(cwd);
|
|
23120
|
+
const scope = branchScope(cwd, branch);
|
|
23121
|
+
const keyCache = new Map;
|
|
23122
|
+
const keysAt = (base, at) => {
|
|
23123
|
+
if (!base || !at)
|
|
23124
|
+
return { patchId: null, diffHash: null };
|
|
23125
|
+
const cacheKey = `${base}\x00${at}`;
|
|
23126
|
+
let key = keyCache.get(cacheKey);
|
|
23127
|
+
if (!key) {
|
|
23128
|
+
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
23129
|
+
keyCache.set(cacheKey, key);
|
|
23130
|
+
}
|
|
23131
|
+
return key;
|
|
23132
|
+
};
|
|
23133
|
+
const keyFor = (base) => keysAt(base, head);
|
|
23134
|
+
const driftCache = new Map;
|
|
23135
|
+
const driftOf = (row) => {
|
|
23136
|
+
if (driftCache.has(row))
|
|
23137
|
+
return driftCache.get(row) ?? null;
|
|
23138
|
+
let drift = null;
|
|
23139
|
+
const toBase = row.base ?? fallbackBase;
|
|
23140
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
23141
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
23142
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
23143
|
+
drift = verdictDrift(cwd, {
|
|
23144
|
+
from: row.head,
|
|
23145
|
+
to: head,
|
|
23146
|
+
fromBase,
|
|
23147
|
+
toBase: toBaseSha,
|
|
23148
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
23149
|
+
});
|
|
23150
|
+
driftCache.set(row, drift);
|
|
23151
|
+
return drift;
|
|
23152
|
+
};
|
|
23153
|
+
const exactCache = new Map;
|
|
23154
|
+
const exactCarry = (row) => {
|
|
23155
|
+
if (FAILING.has(String(row.result)))
|
|
23156
|
+
return true;
|
|
23157
|
+
let exact = exactCache.get(row);
|
|
23158
|
+
if (exact === undefined) {
|
|
23159
|
+
const toBase = row.base ?? fallbackBase;
|
|
23160
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
23161
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
23162
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
23163
|
+
exactCache.set(row, exact);
|
|
23164
|
+
}
|
|
23165
|
+
return exact;
|
|
23166
|
+
};
|
|
23167
|
+
const keyBasis = (row) => {
|
|
23168
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
23169
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
23170
|
+
};
|
|
23171
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
23172
|
+
const carryOf = (row) => {
|
|
23173
|
+
if (keyBasis(row) !== "stale")
|
|
23174
|
+
return null;
|
|
23175
|
+
if (restacked(row))
|
|
23176
|
+
return null;
|
|
23177
|
+
const drift = driftOf(row);
|
|
23178
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
23179
|
+
};
|
|
23180
|
+
const basisOf = (row) => {
|
|
23181
|
+
const basis = keyBasis(row);
|
|
23182
|
+
if (basis !== "stale")
|
|
23183
|
+
return basis;
|
|
23184
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
23185
|
+
};
|
|
23186
|
+
const sameCode = (failed, row) => {
|
|
23187
|
+
if (failed.head && failed.head === row.head)
|
|
23188
|
+
return true;
|
|
23189
|
+
const base = failed.base ?? fallbackBase;
|
|
23190
|
+
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
23191
|
+
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
23192
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
23193
|
+
};
|
|
23194
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
23195
|
+
const failureAuthorCache = new Map;
|
|
23196
|
+
const failureAuthors = (row) => {
|
|
23197
|
+
let set = failureAuthorCache.get(row.seq);
|
|
23198
|
+
if (!set) {
|
|
23199
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
23200
|
+
failureAuthorCache.set(row.seq, set);
|
|
23201
|
+
}
|
|
23202
|
+
return set;
|
|
23203
|
+
};
|
|
23204
|
+
const reasonCache = new Map;
|
|
23205
|
+
const reasonsOf = (row) => {
|
|
23206
|
+
let reasons = reasonCache.get(row);
|
|
23207
|
+
if (!reasons) {
|
|
23208
|
+
const failing = FAILING.has(String(row.result));
|
|
23209
|
+
reasons = [
|
|
23210
|
+
...trustReasons(cwd, row),
|
|
23211
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
23212
|
+
];
|
|
23213
|
+
reasonCache.set(row, reasons);
|
|
23214
|
+
}
|
|
23215
|
+
return reasons;
|
|
23216
|
+
};
|
|
23217
|
+
const retired = new Set;
|
|
23218
|
+
for (const row of rows)
|
|
23219
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
23220
|
+
retired.add(row.supersedes);
|
|
23221
|
+
const byKind = new Map;
|
|
23222
|
+
for (const row of rows)
|
|
23223
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
23224
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
23225
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
23226
|
+
}
|
|
23227
|
+
const verdicts = [];
|
|
23228
|
+
const warnings = [];
|
|
23229
|
+
for (const [kind, list] of byKind) {
|
|
23230
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
23231
|
+
if (!row)
|
|
23232
|
+
continue;
|
|
23233
|
+
const basis = basisOf(row);
|
|
23234
|
+
const current = basis !== "stale";
|
|
23235
|
+
const independence = reasonsOf(row);
|
|
23236
|
+
const reasons = [
|
|
23237
|
+
...current ? [] : ["stale"],
|
|
23238
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
23239
|
+
...independence
|
|
23240
|
+
];
|
|
23241
|
+
verdicts.push({
|
|
23242
|
+
kind,
|
|
23243
|
+
basis,
|
|
23244
|
+
current,
|
|
23245
|
+
independent: independence.length === 0,
|
|
23246
|
+
accepted: reasons.length === 0,
|
|
23247
|
+
reasons,
|
|
23248
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
23249
|
+
verdict: row
|
|
23250
|
+
});
|
|
23251
|
+
if (reasons.length === 0) {
|
|
23252
|
+
for (const later of list)
|
|
23253
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
23254
|
+
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`);
|
|
23255
|
+
}
|
|
23150
23256
|
}
|
|
23151
|
-
|
|
23152
|
-
|
|
23153
|
-
|
|
23154
|
-
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
...entry,
|
|
23162
|
-
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
23163
|
-
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
23164
|
-
})),
|
|
23165
|
-
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
23166
|
-
];
|
|
23167
|
-
const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
|
|
23168
|
-
return files.map((entry) => ({
|
|
23169
|
-
...entry,
|
|
23170
|
-
generated: generated.has(entry.path)
|
|
23171
|
-
}));
|
|
23172
|
-
}
|
|
23173
|
-
function branchLane(cwd, options) {
|
|
23174
|
-
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
23175
|
-
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
23176
|
-
const head = options.workingTree ? null : options.head;
|
|
23177
|
-
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
23178
|
-
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
23179
|
-
const riskTier = options.judgment?.riskTier ?? null;
|
|
23180
|
-
if (!fork || !files) {
|
|
23181
|
-
const high = riskTier === "high";
|
|
23182
|
-
return {
|
|
23183
|
-
lane: high ? "full" : "focused",
|
|
23184
|
-
reasons: [
|
|
23185
|
-
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
23186
|
-
...high ? ["the task judgment records high risk"] : []
|
|
23187
|
-
],
|
|
23188
|
-
lines: 0,
|
|
23189
|
-
files: 0,
|
|
23190
|
-
riskFiles: [],
|
|
23191
|
-
base,
|
|
23192
|
-
head: options.head
|
|
23193
|
-
};
|
|
23257
|
+
for (const row of rows)
|
|
23258
|
+
if (row.integrity === "unverified" && scope(row))
|
|
23259
|
+
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)`);
|
|
23260
|
+
const tampered = ledgerTampering(cwd);
|
|
23261
|
+
if (tampered.length) {
|
|
23262
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
23263
|
+
for (const entry of verdicts) {
|
|
23264
|
+
entry.accepted = false;
|
|
23265
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
23266
|
+
}
|
|
23194
23267
|
}
|
|
23195
|
-
|
|
23268
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
23269
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
23270
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
23271
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
23272
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
23273
|
+
accepted: false,
|
|
23274
|
+
verdict: failing?.verdict ?? null,
|
|
23275
|
+
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
23276
|
+
};
|
|
23277
|
+
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))));
|
|
23278
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
23279
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
23280
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
23281
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
23282
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
23196
23283
|
return {
|
|
23197
|
-
|
|
23198
|
-
|
|
23199
|
-
|
|
23200
|
-
|
|
23201
|
-
|
|
23202
|
-
base,
|
|
23203
|
-
|
|
23284
|
+
branch,
|
|
23285
|
+
head,
|
|
23286
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
23287
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
23288
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
23289
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
23290
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
23291
|
+
carry: carryEntry?.carry ?? null,
|
|
23292
|
+
reverify,
|
|
23293
|
+
accepted,
|
|
23294
|
+
verdicts,
|
|
23295
|
+
authors: [...authors].toSorted(),
|
|
23296
|
+
warnings
|
|
23204
23297
|
};
|
|
23205
23298
|
}
|
|
23206
|
-
var
|
|
23299
|
+
var OBSERVED_ONLY = new Set([
|
|
23300
|
+
"check",
|
|
23301
|
+
"merge.unverified",
|
|
23302
|
+
"policy.judged",
|
|
23303
|
+
...PR_ROW_TYPES
|
|
23304
|
+
]);
|
|
23207
23305
|
|
|
23208
23306
|
// packages/workit-core/src/core/policy/derive.ts
|
|
23209
23307
|
var RULES = {
|
|
@@ -23358,7 +23456,7 @@ var productDecision = (task, ledger) => {
|
|
|
23358
23456
|
};
|
|
23359
23457
|
};
|
|
23360
23458
|
var planRecorded = (task, root) => {
|
|
23361
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(
|
|
23459
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path16.resolve(root, ref.path)));
|
|
23362
23460
|
if (cited)
|
|
23363
23461
|
return {
|
|
23364
23462
|
met: true,
|
|
@@ -23425,14 +23523,14 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
23425
23523
|
|
|
23426
23524
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
23427
23525
|
var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
|
|
23428
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
23429
|
-
var relative = (root, candidate) =>
|
|
23526
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path17.sep}`);
|
|
23527
|
+
var relative = (root, candidate) => path17.relative(root, candidate).split(path17.sep).join("/") || ".";
|
|
23430
23528
|
var canonicalPath = (value) => {
|
|
23431
23529
|
if (typeof value !== "string" || !value)
|
|
23432
23530
|
return null;
|
|
23433
23531
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
23434
23532
|
return null;
|
|
23435
|
-
const normalized =
|
|
23533
|
+
const normalized = path17.posix.normalize(value);
|
|
23436
23534
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
23437
23535
|
return null;
|
|
23438
23536
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -23468,7 +23566,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
23468
23566
|
return left.length - right.length;
|
|
23469
23567
|
};
|
|
23470
23568
|
var gitPaths = (root) => {
|
|
23471
|
-
const result =
|
|
23569
|
+
const result = spawnSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
23472
23570
|
cwd: root,
|
|
23473
23571
|
encoding: "buffer"
|
|
23474
23572
|
});
|
|
@@ -23480,21 +23578,21 @@ var gitPaths = (root) => {
|
|
|
23480
23578
|
git: !/not a git repository/i.test(stderr)
|
|
23481
23579
|
};
|
|
23482
23580
|
}
|
|
23483
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
23484
|
-
const stagedDeleted =
|
|
23581
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")) : [];
|
|
23582
|
+
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
23485
23583
|
if (stagedDeleted.status !== 0) {
|
|
23486
23584
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
23487
23585
|
if (!/not a git repository/i.test(stderr))
|
|
23488
23586
|
return { paths, uncertain: true, git: true };
|
|
23489
23587
|
} else if (stagedDeleted.stdout) {
|
|
23490
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
23588
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")));
|
|
23491
23589
|
}
|
|
23492
23590
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
23493
23591
|
};
|
|
23494
23592
|
var walk = (root, directory, output) => {
|
|
23495
23593
|
let names;
|
|
23496
23594
|
try {
|
|
23497
|
-
names =
|
|
23595
|
+
names = fs13.readdirSync(directory);
|
|
23498
23596
|
} catch {
|
|
23499
23597
|
return true;
|
|
23500
23598
|
}
|
|
@@ -23502,10 +23600,10 @@ var walk = (root, directory, output) => {
|
|
|
23502
23600
|
for (const name of names) {
|
|
23503
23601
|
if (name === ".git" || name === ".workit")
|
|
23504
23602
|
continue;
|
|
23505
|
-
const target =
|
|
23603
|
+
const target = path17.join(directory, name);
|
|
23506
23604
|
let stat;
|
|
23507
23605
|
try {
|
|
23508
|
-
stat =
|
|
23606
|
+
stat = fs13.lstatSync(target);
|
|
23509
23607
|
} catch {
|
|
23510
23608
|
uncertain = true;
|
|
23511
23609
|
continue;
|
|
@@ -23520,14 +23618,14 @@ var walk = (root, directory, output) => {
|
|
|
23520
23618
|
var scopeRoots = (root, scope) => {
|
|
23521
23619
|
const roots = [];
|
|
23522
23620
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
23523
|
-
const target =
|
|
23621
|
+
const target = path17.resolve(root, item);
|
|
23524
23622
|
if (!inside(root, target))
|
|
23525
23623
|
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` }] });
|
|
23526
23624
|
let ancestor = target;
|
|
23527
|
-
while (!
|
|
23528
|
-
ancestor =
|
|
23625
|
+
while (!fs13.existsSync(ancestor) && ancestor !== root)
|
|
23626
|
+
ancestor = path17.dirname(ancestor);
|
|
23529
23627
|
try {
|
|
23530
|
-
if (!inside(root,
|
|
23628
|
+
if (!inside(root, fs13.realpathSync(ancestor)))
|
|
23531
23629
|
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` }] });
|
|
23532
23630
|
} catch {
|
|
23533
23631
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -23539,7 +23637,7 @@ var scopeRoots = (root, scope) => {
|
|
|
23539
23637
|
function captureCandidate(root, scope, environment = []) {
|
|
23540
23638
|
let checkout;
|
|
23541
23639
|
try {
|
|
23542
|
-
checkout =
|
|
23640
|
+
checkout = fs13.realpathSync(root);
|
|
23543
23641
|
} catch {
|
|
23544
23642
|
return failure2("invalid_input", "candidate root does not exist");
|
|
23545
23643
|
}
|
|
@@ -23557,11 +23655,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
23557
23655
|
if (item === ".git" || item === ".workit")
|
|
23558
23656
|
continue;
|
|
23559
23657
|
names.add(item);
|
|
23560
|
-
if (!
|
|
23658
|
+
if (!fs13.existsSync(target))
|
|
23561
23659
|
continue;
|
|
23562
23660
|
let stat;
|
|
23563
23661
|
try {
|
|
23564
|
-
stat =
|
|
23662
|
+
stat = fs13.lstatSync(target);
|
|
23565
23663
|
} catch {
|
|
23566
23664
|
uncertain = true;
|
|
23567
23665
|
continue;
|
|
@@ -23577,17 +23675,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
23577
23675
|
for (const item of [...names].toSorted()) {
|
|
23578
23676
|
if (!scopeMatches(item, normalizedScope))
|
|
23579
23677
|
continue;
|
|
23580
|
-
const target =
|
|
23678
|
+
const target = path17.join(checkout, item);
|
|
23581
23679
|
try {
|
|
23582
|
-
const stat =
|
|
23680
|
+
const stat = fs13.lstatSync(target);
|
|
23583
23681
|
if (stat.isSymbolicLink()) {
|
|
23584
|
-
const link =
|
|
23682
|
+
const link = fs13.readlinkSync(target);
|
|
23585
23683
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
23586
23684
|
} else if (stat.isFile()) {
|
|
23587
23685
|
files.push({
|
|
23588
23686
|
path: item,
|
|
23589
23687
|
kind: "file",
|
|
23590
|
-
digest: digestBytes(
|
|
23688
|
+
digest: digestBytes(fs13.readFileSync(target)),
|
|
23591
23689
|
executable: (stat.mode & 73) !== 0
|
|
23592
23690
|
});
|
|
23593
23691
|
} else if (!stat.isDirectory()) {
|
|
@@ -23612,7 +23710,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
23612
23710
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
23613
23711
|
if (values.some(({ value }) => value === null))
|
|
23614
23712
|
completeness = "uncertain";
|
|
23615
|
-
const headResult =
|
|
23713
|
+
const headResult = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
23616
23714
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
23617
23715
|
completeness = "uncertain";
|
|
23618
23716
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -23752,7 +23850,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
23752
23850
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
23753
23851
|
let root;
|
|
23754
23852
|
try {
|
|
23755
|
-
root =
|
|
23853
|
+
root = fs13.realpathSync(checkoutRoot);
|
|
23756
23854
|
} catch {
|
|
23757
23855
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
23758
23856
|
}
|
|
@@ -23761,23 +23859,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
23761
23859
|
continue;
|
|
23762
23860
|
if (!reference.digest)
|
|
23763
23861
|
return failure2("invalid_input", "document references require a byte digest");
|
|
23764
|
-
const target =
|
|
23765
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
23862
|
+
const target = path17.resolve(root, reference.path);
|
|
23863
|
+
if (target !== root && !target.startsWith(`${root}${path17.sep}`))
|
|
23766
23864
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
23767
23865
|
fields: [
|
|
23768
23866
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
23769
23867
|
]
|
|
23770
23868
|
});
|
|
23771
23869
|
try {
|
|
23772
|
-
const below =
|
|
23870
|
+
const below = path17.relative(root, target);
|
|
23773
23871
|
let current = root;
|
|
23774
|
-
for (const segment of below.split(
|
|
23872
|
+
for (const segment of below.split(path17.sep)) {
|
|
23775
23873
|
if (!segment)
|
|
23776
23874
|
continue;
|
|
23777
|
-
current =
|
|
23778
|
-
const stat =
|
|
23779
|
-
const real =
|
|
23780
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
23875
|
+
current = path17.join(current, segment);
|
|
23876
|
+
const stat = fs13.lstatSync(current);
|
|
23877
|
+
const real = fs13.realpathSync(current);
|
|
23878
|
+
if (real !== root && !real.startsWith(`${root}${path17.sep}`))
|
|
23781
23879
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
23782
23880
|
fields: [
|
|
23783
23881
|
{
|
|
@@ -23793,7 +23891,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
23793
23891
|
if (current !== target && !stat.isDirectory())
|
|
23794
23892
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
23795
23893
|
}
|
|
23796
|
-
if (digestBytes(
|
|
23894
|
+
if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
|
|
23797
23895
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
23798
23896
|
} catch {
|
|
23799
23897
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -24636,7 +24734,7 @@ var judgeInputSummary = (raw) => {
|
|
|
24636
24734
|
|
|
24637
24735
|
// packages/workit-core/src/autonomy.ts
|
|
24638
24736
|
import os4 from "node:os";
|
|
24639
|
-
import
|
|
24737
|
+
import path18 from "node:path";
|
|
24640
24738
|
var accountHome = () => {
|
|
24641
24739
|
try {
|
|
24642
24740
|
return os4.userInfo().homedir;
|
|
@@ -24645,10 +24743,10 @@ var accountHome = () => {
|
|
|
24645
24743
|
}
|
|
24646
24744
|
};
|
|
24647
24745
|
var grantsHome = { resolve: accountHome };
|
|
24648
|
-
var grantsDir = () =>
|
|
24746
|
+
var grantsDir = () => path18.join(grantsHome.resolve(), ".config", "workit");
|
|
24649
24747
|
var grantsOverride = () => {
|
|
24650
|
-
const configured =
|
|
24651
|
-
return configured ===
|
|
24748
|
+
const configured = path18.resolve(resolveConfigDir());
|
|
24749
|
+
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`;
|
|
24652
24750
|
};
|
|
24653
24751
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
24654
24752
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -26457,9 +26555,9 @@ class WorkitCore {
|
|
|
26457
26555
|
}
|
|
26458
26556
|
}
|
|
26459
26557
|
// packages/workit-core/src/core/repo-context.ts
|
|
26460
|
-
import { spawnSync as
|
|
26558
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
26461
26559
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26462
|
-
import
|
|
26560
|
+
import path19 from "node:path";
|
|
26463
26561
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26464
26562
|
try {
|
|
26465
26563
|
gitRevisionParts(value);
|
|
@@ -26486,7 +26584,7 @@ var invalidRange = (cwd) => ({
|
|
|
26486
26584
|
cwd
|
|
26487
26585
|
});
|
|
26488
26586
|
var runGit = (cwd, args) => {
|
|
26489
|
-
const result =
|
|
26587
|
+
const result = spawnSync11("git", args, { cwd, encoding: "utf8" });
|
|
26490
26588
|
return {
|
|
26491
26589
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
26492
26590
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -26593,7 +26691,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26593
26691
|
]) {
|
|
26594
26692
|
const parts = rel.split("/");
|
|
26595
26693
|
const base = parts[parts.length - 1];
|
|
26596
|
-
const dir = parts.length > 1 ?
|
|
26694
|
+
const dir = parts.length > 1 ? path19.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26597
26695
|
const actual = probeCaseInsensitive(dir, base);
|
|
26598
26696
|
if (actual)
|
|
26599
26697
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26624,7 +26722,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26624
26722
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26625
26723
|
};
|
|
26626
26724
|
var packageScripts = (cwd, keys) => {
|
|
26627
|
-
const pkgPath =
|
|
26725
|
+
const pkgPath = path19.join(cwd, "package.json");
|
|
26628
26726
|
if (!existsSync7(pkgPath))
|
|
26629
26727
|
return [];
|
|
26630
26728
|
try {
|
|
@@ -26650,9 +26748,9 @@ var documentationFiles = (cwd) => {
|
|
|
26650
26748
|
if (entry.isDirectory()) {
|
|
26651
26749
|
if (excluded.has(entry.name))
|
|
26652
26750
|
continue;
|
|
26653
|
-
walk(
|
|
26751
|
+
walk(path19.join(dir, entry.name), depth + 1);
|
|
26654
26752
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26655
|
-
matches.push("./" +
|
|
26753
|
+
matches.push("./" + path19.relative(cwd, path19.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26656
26754
|
}
|
|
26657
26755
|
}
|
|
26658
26756
|
};
|
|
@@ -26668,7 +26766,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
26668
26766
|
`);
|
|
26669
26767
|
};
|
|
26670
26768
|
function prReadyContext(root, range) {
|
|
26671
|
-
const cwd =
|
|
26769
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
26672
26770
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26673
26771
|
return invalidRange(cwd);
|
|
26674
26772
|
let stdout = "";
|
|
@@ -26726,7 +26824,7 @@ ${status.stderr}` : "");
|
|
|
26726
26824
|
stdout += `template_path: ${template}
|
|
26727
26825
|
|
|
26728
26826
|
`;
|
|
26729
|
-
stdout += readTrimmed2(
|
|
26827
|
+
stdout += readTrimmed2(path19.join(cwd, template), 220);
|
|
26730
26828
|
} else {
|
|
26731
26829
|
stdout += `template_path: none
|
|
26732
26830
|
|
|
@@ -26736,7 +26834,7 @@ ${status.stderr}` : "");
|
|
|
26736
26834
|
stdout += `
|
|
26737
26835
|
`;
|
|
26738
26836
|
stdout += printSection("Recent Validation Signals");
|
|
26739
|
-
if (existsSync7(
|
|
26837
|
+
if (existsSync7(path19.join(cwd, "package.json"))) {
|
|
26740
26838
|
stdout += `package.json detected. Common scripts:
|
|
26741
26839
|
`;
|
|
26742
26840
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26745,7 +26843,7 @@ ${status.stderr}` : "");
|
|
|
26745
26843
|
`) + `
|
|
26746
26844
|
`;
|
|
26747
26845
|
}
|
|
26748
|
-
if (existsSync7(
|
|
26846
|
+
if (existsSync7(path19.join(cwd, "Cargo.toml")) || existsSync7(path19.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26749
26847
|
stdout += `Rust project detected.
|
|
26750
26848
|
`;
|
|
26751
26849
|
}
|
|
@@ -26778,7 +26876,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26778
26876
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
26779
26877
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26780
26878
|
function changelogContext(root, range) {
|
|
26781
|
-
const cwd =
|
|
26879
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
26782
26880
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26783
26881
|
return invalidRange(cwd);
|
|
26784
26882
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26796,7 +26894,7 @@ function changelogContext(root, range) {
|
|
|
26796
26894
|
stdout += CHANGELOG_RULES + `
|
|
26797
26895
|
`;
|
|
26798
26896
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26799
|
-
const changelogPath =
|
|
26897
|
+
const changelogPath = path19.join(cwd, "CHANGELOG.md");
|
|
26800
26898
|
if (existsSync7(changelogPath)) {
|
|
26801
26899
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
26802
26900
|
`;
|
|
@@ -26816,7 +26914,7 @@ function changelogContext(root, range) {
|
|
|
26816
26914
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26817
26915
|
}
|
|
26818
26916
|
function docsRefreshContext(root, range) {
|
|
26819
|
-
const cwd =
|
|
26917
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
26820
26918
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26821
26919
|
return invalidRange(cwd);
|
|
26822
26920
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26838,7 +26936,7 @@ function docsRefreshContext(root, range) {
|
|
|
26838
26936
|
`) + `
|
|
26839
26937
|
`;
|
|
26840
26938
|
stdout += printSection("README Preview");
|
|
26841
|
-
const readme =
|
|
26939
|
+
const readme = path19.join(cwd, "README.md");
|
|
26842
26940
|
if (existsSync7(readme)) {
|
|
26843
26941
|
stdout += readTrimmed2(readme, 220) + `
|
|
26844
26942
|
`;
|
|
@@ -26847,7 +26945,7 @@ function docsRefreshContext(root, range) {
|
|
|
26847
26945
|
`;
|
|
26848
26946
|
}
|
|
26849
26947
|
stdout += printSection("Package Scripts");
|
|
26850
|
-
const pkgPath =
|
|
26948
|
+
const pkgPath = path19.join(cwd, "package.json");
|
|
26851
26949
|
if (existsSync7(pkgPath)) {
|
|
26852
26950
|
try {
|
|
26853
26951
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26861,7 +26959,7 @@ function docsRefreshContext(root, range) {
|
|
|
26861
26959
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26862
26960
|
}
|
|
26863
26961
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26864
|
-
const cwd =
|
|
26962
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
26865
26963
|
if (!rangeOrTag) {
|
|
26866
26964
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26867
26965
|
`, exitCode: 1, cwd };
|
|
@@ -26899,7 +26997,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26899
26997
|
`;
|
|
26900
26998
|
stdout += printSection("Existing Release Files");
|
|
26901
26999
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26902
|
-
if (existsSync7(
|
|
27000
|
+
if (existsSync7(path19.join(cwd, rel)))
|
|
26903
27001
|
stdout += rel + `
|
|
26904
27002
|
`;
|
|
26905
27003
|
}
|
|
@@ -26928,6 +27026,46 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26928
27026
|
|
|
26929
27027
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
26930
27028
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
27029
|
+
|
|
27030
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
27031
|
+
import fs14 from "node:fs";
|
|
27032
|
+
import path20 from "node:path";
|
|
27033
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
27034
|
+
// packages/workit-core/src/forge/pr-body.ts
|
|
27035
|
+
function parseGhIssue(value) {
|
|
27036
|
+
const m = /issues\/(\d+)/.exec(value);
|
|
27037
|
+
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
27038
|
+
}
|
|
27039
|
+
|
|
27040
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
27041
|
+
function whichOnPath(tool) {
|
|
27042
|
+
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
27043
|
+
for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
|
|
27044
|
+
if (!dir)
|
|
27045
|
+
continue;
|
|
27046
|
+
for (const name of names) {
|
|
27047
|
+
const candidate = path20.join(dir, name);
|
|
27048
|
+
try {
|
|
27049
|
+
fs14.accessSync(candidate, fs14.constants.X_OK);
|
|
27050
|
+
return candidate;
|
|
27051
|
+
} catch {}
|
|
27052
|
+
}
|
|
27053
|
+
}
|
|
27054
|
+
return null;
|
|
27055
|
+
}
|
|
27056
|
+
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
27057
|
+
var pushRemote = (cwd) => {
|
|
27058
|
+
const result = spawnSync12("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
27059
|
+
cwd,
|
|
27060
|
+
encoding: "utf8"
|
|
27061
|
+
});
|
|
27062
|
+
if (result.status !== 0)
|
|
27063
|
+
return null;
|
|
27064
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
27065
|
+
return urls.length === 1 ? urls[0] : null;
|
|
27066
|
+
};
|
|
27067
|
+
|
|
27068
|
+
// packages/workit-core/src/core/tracker-issues.ts
|
|
26931
27069
|
var repoPathFromRemote = (remote) => {
|
|
26932
27070
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
26933
27071
|
if (!rest)
|
|
@@ -27821,6 +27959,25 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
27821
27959
|
var signals = new Map;
|
|
27822
27960
|
var cache = new Map;
|
|
27823
27961
|
var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
27962
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
27963
|
+
var WORKIT_ALLOWED_VERBS = [
|
|
27964
|
+
"git",
|
|
27965
|
+
"pr",
|
|
27966
|
+
"ci",
|
|
27967
|
+
"stack",
|
|
27968
|
+
"release",
|
|
27969
|
+
"task",
|
|
27970
|
+
"ledger",
|
|
27971
|
+
"evidence",
|
|
27972
|
+
"finding",
|
|
27973
|
+
"decision",
|
|
27974
|
+
"worker",
|
|
27975
|
+
"verify-delivery",
|
|
27976
|
+
"policy",
|
|
27977
|
+
"doctor",
|
|
27978
|
+
"help"
|
|
27979
|
+
];
|
|
27980
|
+
var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
27824
27981
|
// packages/workit-core/src/core/heavy.ts
|
|
27825
27982
|
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
27826
27983
|
var sub = (...names) => new Set(names);
|
|
@@ -28029,6 +28186,10 @@ var WRITE_TOOLS = new Set([
|
|
|
28029
28186
|
// packages/workit-core/src/hooks/standing.ts
|
|
28030
28187
|
var sinceStart = new Map;
|
|
28031
28188
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
28189
|
+
var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
28190
|
+
var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
28191
|
+
var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
28192
|
+
var VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
28032
28193
|
var FILTERS = new Set(["tail", "head", "grep"]);
|
|
28033
28194
|
|
|
28034
28195
|
// packages/workit-core/src/hooks/worktree-confine.ts
|