@brainervirus/workit-cursor 8.24.0 → 8.25.0

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