@brainervirus/workit-cursor 8.24.0 → 8.26.0

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