@brainervirus/workit-codex 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.
@@ -14864,6 +14864,7 @@ var workspaceConfigSchema = object({
14864
14864
  trailers: boolean2().optional(),
14865
14865
  liveSessionGuard: boolean2().optional(),
14866
14866
  fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
14867
+ models: object({ roles: unknown().optional() }).passthrough().optional(),
14867
14868
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
14868
14869
  kind,
14869
14870
  union([boolean2(), literal("verified")]).optional()
@@ -14890,7 +14891,10 @@ var workspaceConfigSchema = object({
14890
14891
  });
14891
14892
  }
14892
14893
  });
14893
- var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).optional() }).passthrough().superRefine((file, context) => {
14894
+ var workspacesFileSchema = object({
14895
+ workspaces: array(workspaceConfigSchema).optional(),
14896
+ models: object({ roles: unknown().optional() }).passthrough().optional()
14897
+ }).passthrough().superRefine((file, context) => {
14894
14898
  const names = new Set;
14895
14899
  for (const [index, workspace] of (file.workspaces ?? []).entries()) {
14896
14900
  if (names.has(workspace.name)) {
@@ -15180,6 +15184,34 @@ var resolveRuntimeReleaseTracks = (cwd) => {
15180
15184
  const matched = matchRuntimeWorkspace(cwd);
15181
15185
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
15182
15186
  };
15187
+ var modelSources = (cwd, dir) => {
15188
+ const out = [];
15189
+ try {
15190
+ const matched = matchRuntimeWorkspace(cwd, dir);
15191
+ if (matched) {
15192
+ const enclosing = matched.matches.filter((entry) => entry !== matched.selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
15193
+ for (const entry of [matched.selected, ...enclosing])
15194
+ out.push({
15195
+ name: entry.name,
15196
+ global: false,
15197
+ models: entry.models,
15198
+ tierModels: entry.fanout?.tierModels
15199
+ });
15200
+ }
15201
+ } catch {}
15202
+ try {
15203
+ const document = JSON.parse(readFileSync2(path4.join(dir ?? configDir(), "workspaces.json"), "utf8"));
15204
+ if (document && typeof document === "object" && !Array.isArray(document))
15205
+ out.push({
15206
+ name: "global",
15207
+ global: true,
15208
+ models: document.models,
15209
+ tierModels: undefined
15210
+ });
15211
+ } catch {}
15212
+ return out;
15213
+ };
15214
+ var hostFamily = (host) => host.replace(/_(?:cli|desktop)$/u, "");
15183
15215
  var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
15184
15216
  var TIER_HOSTS = new Set([
15185
15217
  "claude_code",
@@ -15400,6 +15432,26 @@ function resolveRef(cwd, ref) {
15400
15432
  return null;
15401
15433
  return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
15402
15434
  }
15435
+ function originHeadRef(cwd) {
15436
+ return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
15437
+ }
15438
+ function ownChangeDrift(cwd, range) {
15439
+ const before = diffRaw(cwd, range.fromFork, range.from);
15440
+ const after = diffRaw(cwd, range.toFork, range.to);
15441
+ if (!before || !after)
15442
+ return null;
15443
+ const judged = new Map(before.map((entry) => [entry.path, entry]));
15444
+ const current = new Map(after.map((entry) => [entry.path, entry]));
15445
+ const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
15446
+ return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
15447
+ }
15448
+ function sameOwnChange(cwd, range) {
15449
+ const fromFork = mergeBase(cwd, range.fromBase, range.from);
15450
+ const toFork = mergeBase(cwd, range.toBase, range.to);
15451
+ if (!fromFork || !toFork)
15452
+ return false;
15453
+ return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
15454
+ }
15403
15455
  var LANE_DIFF = [
15404
15456
  "-c",
15405
15457
  "core.quotePath=false",
@@ -19026,9 +19078,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
19026
19078
 
19027
19079
  // packages/workit-core/src/core/task-evaluation.ts
19028
19080
  import { createHash as createHash10 } from "node:crypto";
19029
- import * as fs14 from "node:fs";
19030
- import { spawnSync as spawnSync11 } from "node:child_process";
19031
- import path18 from "node:path";
19081
+ import * as fs13 from "node:fs";
19082
+ import { spawnSync as spawnSync10 } from "node:child_process";
19083
+ import path17 from "node:path";
19032
19084
 
19033
19085
  // packages/workit-core/src/check-config.ts
19034
19086
  import { spawnSync as spawnSync8 } from "node:child_process";
@@ -19249,68 +19301,20 @@ function checkCommand(args, version, onPath = workitAvailable()) {
19249
19301
 
19250
19302
  // packages/workit-core/src/core/policy/derive.ts
19251
19303
  import { existsSync as existsSync5 } from "node:fs";
19252
- import path17 from "node:path";
19304
+ import path16 from "node:path";
19253
19305
 
19254
19306
  // packages/workit-core/src/ledger.ts
19255
19307
  import { spawnSync as spawnSync9 } from "node:child_process";
19256
19308
  import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
19257
19309
  import fs12 from "node:fs";
19310
+ import path15 from "node:path";
19311
+
19312
+ // packages/workit-core/src/core/policy/lane.ts
19313
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
19258
19314
  import path14 from "node:path";
19259
- var LEDGER_VERSION = 1;
19260
- var MAX_LINE_BYTES = 4096;
19261
- var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
19262
- var FAILING = new Set(["failed", "blocked"]);
19263
- var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
19264
- var PR_ROW_TYPES = new Set([
19265
- "pr.created",
19266
- "pr.status",
19267
- "pr.merged",
19268
- "pr.linked"
19269
- ]);
19270
- var err = (code, error, unblock) => ({
19271
- ok: false,
19272
- code,
19273
- error,
19274
- ...unblock ? { unblock } : {}
19275
- });
19276
- var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
19277
- const run = spawnSync9("git", args, {
19278
- cwd,
19279
- encoding: "utf8",
19280
- timeout: timeoutMs,
19281
- killSignal: "SIGKILL",
19282
- windowsHide: true,
19283
- maxBuffer: 256 * 1024 * 1024,
19284
- env: {
19285
- ...process.env,
19286
- GIT_TERMINAL_PROMPT: "0",
19287
- GIT_OPTIONAL_LOCKS: "0",
19288
- LC_ALL: "C",
19289
- LANGUAGE: "C"
19290
- }
19291
- });
19292
- return {
19293
- ok: run.status === 0,
19294
- stdout: run.stdout ?? "",
19295
- stderr: run.error ? String(run.error.message) : run.stderr ?? ""
19296
- };
19297
- };
19298
- var git4 = (cwd, args) => {
19299
- const run = gitRun2(cwd, args);
19300
- const value = run.ok ? run.stdout.trim() : "";
19301
- return value ? value : null;
19302
- };
19315
+
19316
+ // packages/workit-core/src/git/default-base.ts
19303
19317
  var safeRef = (value) => value.length > 0 && !value.startsWith("-");
19304
- function resolveCommit(cwd, ref) {
19305
- if (!safeRef(ref))
19306
- return null;
19307
- return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
19308
- }
19309
- function branchHead(cwd, branch) {
19310
- if (!safeRef(branch))
19311
- return null;
19312
- return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
19313
- }
19314
19318
  var trackTrunk = (cwd) => {
19315
19319
  try {
19316
19320
  const resolved = vcsConfig("resolve", cwd);
@@ -19326,11 +19330,11 @@ function defaultBase(cwd) {
19326
19330
  const trunk = trackTrunk(cwd);
19327
19331
  if (trunk) {
19328
19332
  for (const candidate of [`origin/${trunk}`, trunk])
19329
- if (resolveCommit(cwd, candidate))
19333
+ if (resolveRef(cwd, candidate))
19330
19334
  return candidate;
19331
19335
  }
19332
- const originHead = git4(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
19333
- if (originHead && resolveCommit(cwd, originHead))
19336
+ const originHead = originHeadRef(cwd);
19337
+ if (originHead && resolveRef(cwd, originHead))
19334
19338
  return originHead;
19335
19339
  for (const candidate of [
19336
19340
  "origin/main",
@@ -19340,1489 +19344,1611 @@ function defaultBase(cwd) {
19340
19344
  "origin/develop",
19341
19345
  "develop"
19342
19346
  ])
19343
- if (resolveCommit(cwd, candidate))
19347
+ if (resolveRef(cwd, candidate))
19344
19348
  return candidate;
19345
19349
  return null;
19346
19350
  }
19347
- function diffHash(cwd, base, head) {
19348
- if (!safeRef(base) || !safeRef(head))
19351
+
19352
+ // packages/workit-core/src/core/policy/attributes.ts
19353
+ function patternRegex(dir, pattern) {
19354
+ if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
19349
19355
  return null;
19350
- const diff = gitRun2(cwd, [
19351
- "diff",
19352
- "-U3",
19353
- "--diff-algorithm=myers",
19354
- "--indent-heuristic",
19355
- "--no-color",
19356
- "--no-ext-diff",
19357
- "--no-textconv",
19358
- "--no-renames",
19359
- "--full-index",
19360
- "--binary",
19361
- "--src-prefix=a/",
19362
- "--dst-prefix=b/",
19363
- `${base}...${head}`,
19364
- "--"
19365
- ], GIT_TIMEOUTS.worktree);
19366
- if (!diff.ok || !diff.stdout)
19356
+ const anchored = pattern.includes("/");
19357
+ const body = pattern.replace(/^\//u, "");
19358
+ let source = "";
19359
+ for (let index = 0;index < body.length; index += 1) {
19360
+ const char = body[index];
19361
+ if (char === "*" && body[index + 1] === "*") {
19362
+ const slash = body[index + 2] === "/";
19363
+ source += slash ? "(?:.*/)?" : ".*";
19364
+ index += slash ? 2 : 1;
19365
+ } else if (char === "*")
19366
+ source += "[^/]*";
19367
+ else if (char === "?")
19368
+ source += "[^/]";
19369
+ else if (char === "[") {
19370
+ const end = body.indexOf("]", index + 2);
19371
+ if (end < 0)
19372
+ return null;
19373
+ const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
19374
+ source += `[${set}]`;
19375
+ index = end;
19376
+ } else if (char === "\\" && index + 1 < body.length) {
19377
+ source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
19378
+ index += 1;
19379
+ } else
19380
+ source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
19381
+ }
19382
+ const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
19383
+ try {
19384
+ return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
19385
+ } catch {
19367
19386
  return null;
19368
- const hash = createHash9("sha256");
19369
- for (const line of diff.stdout.split(`
19387
+ }
19388
+ }
19389
+ function rulesOf(dir, text) {
19390
+ const out = [];
19391
+ for (const raw of text.split(`
19370
19392
  `)) {
19371
- if (line.startsWith("index "))
19393
+ const line = raw.trim();
19394
+ if (!line || line.startsWith("#") || line.startsWith("[attr]"))
19372
19395
  continue;
19373
- hash.update(line.startsWith("@@ ") ? "@@" : line);
19374
- hash.update(`
19375
- `);
19396
+ const [pattern, ...attributes] = line.split(/\s+/u);
19397
+ let generated = null;
19398
+ for (const attribute of attributes) {
19399
+ if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
19400
+ generated = true;
19401
+ else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
19402
+ generated = false;
19403
+ }
19404
+ if (generated === null)
19405
+ continue;
19406
+ const regex = patternRegex(dir, pattern);
19407
+ if (regex)
19408
+ out.push({ regex, generated });
19376
19409
  }
19377
- return `sha256:${hash.digest("hex")}`;
19378
- }
19379
- function storeRoot(cwd) {
19380
- const location = resolveStore(path14.resolve(cwd));
19381
- if (location instanceof Error)
19382
- 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");
19383
- return { ok: true, value: { root: location.dir, shared: location.shared } };
19410
+ return out;
19384
19411
  }
19385
- function ledgerPath(cwd) {
19386
- const root = storeRoot(cwd);
19387
- return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
19412
+ function generatedByAttributes(files, paths) {
19413
+ const rules = [...files].map(([file, text]) => {
19414
+ const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
19415
+ return {
19416
+ depth: dir ? dir.split("/").length : 0,
19417
+ rules: rulesOf(dir, text)
19418
+ };
19419
+ }).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
19420
+ const out = new Set;
19421
+ for (const file of paths) {
19422
+ const last = rules.findLast((rule) => rule.regex.test(file));
19423
+ if (last?.generated)
19424
+ out.add(file);
19425
+ }
19426
+ return out;
19388
19427
  }
19389
- var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
19390
- var NETWORK_FS_TYPES = new Set([
19391
- 26985,
19392
- 20859,
19393
- 4266872130,
19394
- 4283649346,
19395
- 1702057286,
19396
- 1397113167,
19397
- 12805120,
19398
- 16914839,
19399
- 1196443219,
19400
- 198183888
19428
+
19429
+ // packages/workit-core/src/core/policy/substance.ts
19430
+ var SLASH_BLOCK = [["/*", "*/"]];
19431
+ var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
19432
+ var SYNTAXES = Object.fromEntries([
19433
+ ...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
19434
+ ...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
19435
+ ext,
19436
+ { ...C_LIKE, triple: true }
19437
+ ]),
19438
+ ["go", { ...C_LIKE, rawBacktick: true }],
19439
+ ...["css", "scss", "less"].map((ext) => [
19440
+ ext,
19441
+ { line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
19442
+ ]),
19443
+ ...["py", "pyi"].map((ext) => [
19444
+ ext,
19445
+ { line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
19446
+ ]),
19447
+ ["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
19448
+ ...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
19449
+ ext,
19450
+ { line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
19451
+ ])
19401
19452
  ]);
19402
- var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
19403
- var onNetworkFs = (dir) => {
19404
- if (process.platform !== "linux")
19405
- return false;
19406
- try {
19407
- return isNetworkFsType(fs12.statfsSync(dir).type);
19408
- } catch {
19409
- return false;
19410
- }
19411
- };
19412
- var LOCK_WAIT_MS = 5000;
19413
- var LOCK_STALE_MS = 30000;
19414
- var LOCK_OWNER = "owner";
19415
- var lockToken = (lock) => {
19416
- try {
19417
- return fs12.readFileSync(path14.join(lock, LOCK_OWNER), "utf8");
19418
- } catch {
19419
- return null;
19420
- }
19421
- };
19422
- var lockAgeMs = (lock) => {
19423
- for (const target of [path14.join(lock, LOCK_OWNER), lock]) {
19424
- try {
19425
- return Date.now() - fs12.statSync(target).mtimeMs;
19426
- } catch {}
19427
- }
19428
- return null;
19453
+ 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;
19454
+ var extensionOf = (file) => {
19455
+ const name = file.slice(file.lastIndexOf("/") + 1);
19456
+ const dot = name.lastIndexOf(".");
19457
+ return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
19429
19458
  };
19430
- var ledgerLock = {
19431
- acquire(lock) {
19432
- try {
19433
- fs12.mkdirSync(lock);
19434
- } catch (error) {
19435
- if (error.code === "EEXIST")
19436
- return null;
19437
- throw error;
19438
- }
19439
- const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
19440
- fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
19441
- return token;
19442
- },
19443
- release(lock, token) {
19444
- if (lockToken(lock) !== token)
19445
- return false;
19446
- const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
19447
- try {
19448
- fs12.renameSync(lock, tomb);
19449
- } catch {
19450
- return false;
19459
+ var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
19460
+ var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
19461
+ var SPACE = /[ \t\r\f\v]/u;
19462
+ var WORD = /[\w$]/u;
19463
+ var NO_SPACE_AFTER = "([{,;";
19464
+ var NO_SPACE_BEFORE = ")]},;";
19465
+ var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
19466
+ var REGEX_KEYWORDS = new Set([
19467
+ "return",
19468
+ "typeof",
19469
+ "instanceof",
19470
+ "case",
19471
+ "do",
19472
+ "else",
19473
+ "in",
19474
+ "of",
19475
+ "new",
19476
+ "delete",
19477
+ "void",
19478
+ "throw",
19479
+ "yield",
19480
+ "await"
19481
+ ]);
19482
+ function substanceOf(file, text) {
19483
+ const syntax = syntaxOf(file);
19484
+ if (!syntax)
19485
+ return plainSubstance(text);
19486
+ const markup = syntax.block.some(([open]) => open === "<!--");
19487
+ if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
19488
+ return plainSubstance(text);
19489
+ return lex(syntax, text);
19490
+ }
19491
+ function plainSubstance(text) {
19492
+ return text.split(`
19493
+ `).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
19494
+ `);
19495
+ }
19496
+ function lex(syntax, text) {
19497
+ const collapse = !syntax.indented;
19498
+ let out = "";
19499
+ let lineStart = 0;
19500
+ let kept = 0;
19501
+ let pendingSpace = false;
19502
+ const frames = [{ kind: "code", braces: 0 }];
19503
+ let index = 0;
19504
+ if (text.startsWith("#!")) {
19505
+ const end = text.indexOf(`
19506
+ `);
19507
+ out = end < 0 ? text : text.slice(0, end);
19508
+ kept = out.length;
19509
+ index = end < 0 ? text.length : end;
19510
+ }
19511
+ const emitCode = (chunk) => {
19512
+ if (collapse && pendingSpace && out.length > lineStart) {
19513
+ const before = out[out.length - 1];
19514
+ if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
19515
+ out += " ";
19451
19516
  }
19452
- if (lockToken(tomb) !== token) {
19453
- try {
19454
- fs12.renameSync(tomb, lock);
19455
- } catch {}
19456
- return false;
19517
+ pendingSpace = false;
19518
+ out += chunk;
19519
+ };
19520
+ const emitVerbatim = (chunk) => {
19521
+ emitCode(chunk);
19522
+ kept = out.length;
19523
+ };
19524
+ const newline = () => {
19525
+ if (!collapse) {
19526
+ let end = out.length;
19527
+ while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
19528
+ end -= 1;
19529
+ out = out.slice(0, end);
19457
19530
  }
19458
- fs12.rmSync(tomb, { recursive: true, force: true });
19459
- return true;
19460
- },
19461
- token: lockToken,
19462
- reap(lock, observed) {
19463
- const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
19464
- try {
19465
- fs12.renameSync(lock, tomb);
19466
- } catch {
19467
- return false;
19531
+ pendingSpace = false;
19532
+ if (out.length > lineStart) {
19533
+ out += `
19534
+ `;
19535
+ lineStart = out.length;
19468
19536
  }
19469
- if (lockToken(tomb) === observed) {
19470
- fs12.rmSync(tomb, { recursive: true, force: true });
19537
+ };
19538
+ const regexAllowed = () => {
19539
+ let end = out.length;
19540
+ while (end > 0 && /\s/u.test(out[end - 1]))
19541
+ end -= 1;
19542
+ if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
19471
19543
  return true;
19472
- }
19473
- try {
19474
- fs12.renameSync(tomb, lock);
19475
- } catch {
19476
- fs12.rmSync(tomb, { recursive: true, force: true });
19544
+ let start = end;
19545
+ while (start > 0 && WORD.test(out[start - 1]))
19546
+ start -= 1;
19547
+ return REGEX_KEYWORDS.has(out.slice(start, end));
19548
+ };
19549
+ const scanString = (quote, escapes, multiline) => {
19550
+ const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
19551
+ const fstring = syntax.fstring === true && /f/iu.test(prefix);
19552
+ let end = index + quote.length;
19553
+ while (end < text.length) {
19554
+ const char = text[end];
19555
+ if (escapes && char === "\\") {
19556
+ end += 2;
19557
+ continue;
19558
+ }
19559
+ if (text.startsWith(quote, end)) {
19560
+ if (fstring && unbalanced(text.slice(index + quote.length, end)))
19561
+ return false;
19562
+ emitVerbatim(text.slice(index, end + quote.length));
19563
+ index = end + quote.length;
19564
+ return true;
19565
+ }
19566
+ if (char === `
19567
+ ` && !multiline)
19568
+ return false;
19569
+ end += 1;
19477
19570
  }
19478
19571
  return false;
19479
- }
19480
- };
19481
- function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
19482
- const lock = `${file}.lock`;
19483
- const deadline = Date.now() + waitMs;
19484
- let token = null;
19485
- for (;; ) {
19486
- token = ledgerLock.acquire(lock);
19487
- if (token)
19488
- break;
19489
- const observed = lockToken(lock);
19490
- const age = lockAgeMs(lock);
19491
- if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
19572
+ };
19573
+ while (index < text.length) {
19574
+ const frame = frames[frames.length - 1];
19575
+ const char = text[index];
19576
+ if (frame.kind === "template") {
19577
+ if (char === "\\") {
19578
+ emitVerbatim(text.slice(index, index + 2));
19579
+ index += 2;
19580
+ } else if (char === "`") {
19581
+ emitVerbatim(char);
19582
+ frames.pop();
19583
+ index += 1;
19584
+ } else if (text.startsWith("${", index)) {
19585
+ emitVerbatim("${");
19586
+ frames.push({ kind: "code", braces: 0 });
19587
+ index += 2;
19588
+ } else {
19589
+ out += char;
19590
+ kept = out.length;
19591
+ index += 1;
19592
+ }
19492
19593
  continue;
19493
- if (Date.now() >= deadline)
19494
- return null;
19495
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
19496
- }
19497
- try {
19498
- return fn();
19499
- } finally {
19500
- ledgerLock.release(lock, token);
19501
- }
19502
- }
19503
- var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
19504
- var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
19505
- ${body}`);
19506
- var TAIL_BYTES = 64 * 1024;
19507
- var lastLine = (fd, size) => {
19508
- for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
19509
- const buffer = Buffer.alloc(span);
19510
- fs12.readSync(fd, buffer, 0, span, size - span);
19511
- const end = buffer.lastIndexOf(10);
19512
- if (end < 0) {
19513
- if (span === size)
19594
+ }
19595
+ if (char === `
19596
+ `) {
19597
+ newline();
19598
+ index += 1;
19599
+ continue;
19600
+ }
19601
+ if (SPACE.test(char)) {
19602
+ if (collapse)
19603
+ pendingSpace = true;
19604
+ else
19605
+ out += char;
19606
+ index += 1;
19607
+ continue;
19608
+ }
19609
+ const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
19610
+ if (lineOpener) {
19611
+ const end = text.indexOf(`
19612
+ `, index);
19613
+ const comment = text.slice(index, end < 0 ? text.length : end);
19614
+ if (DIRECTIVE.test(comment.slice(lineOpener.length)))
19615
+ emitVerbatim(comment);
19616
+ else
19617
+ pendingSpace = true;
19618
+ index = end < 0 ? text.length : end;
19619
+ continue;
19620
+ }
19621
+ const block = syntax.block.find(([open]) => text.startsWith(open, index));
19622
+ if (block) {
19623
+ const end = text.indexOf(block[1], index + block[0].length);
19624
+ if (end < 0)
19514
19625
  return null;
19626
+ const comment = text.slice(index, end + block[1].length);
19627
+ if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
19628
+ emitVerbatim(comment);
19629
+ else
19630
+ pendingSpace = true;
19631
+ if (comment.includes(`
19632
+ `))
19633
+ newline();
19634
+ index = end + block[1].length;
19515
19635
  continue;
19516
19636
  }
19517
- let stop = end;
19518
- let text = "";
19519
- while (stop >= 0) {
19520
- const start = buffer.lastIndexOf(10, stop - 1);
19521
- text = buffer.toString("utf8", start + 1, stop).trim();
19522
- if (text) {
19523
- if (start >= 0 || span === size)
19524
- return text;
19525
- break;
19526
- }
19527
- if (start < 0) {
19528
- if (span === size)
19529
- return null;
19530
- break;
19531
- }
19532
- stop = start;
19637
+ if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
19638
+ if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
19639
+ return null;
19640
+ continue;
19533
19641
  }
19534
- if (span === size)
19535
- return text || null;
19536
- }
19537
- return null;
19538
- };
19539
- var tighten = (fd) => {
19540
- try {
19541
- if ((fs12.fstatSync(fd).mode & 63) !== 0)
19542
- fs12.fchmodSync(fd, 384);
19543
- } catch {}
19544
- };
19545
- function appendLine(file, build, options) {
19546
- const write = () => {
19547
- const fd = fs12.openSync(file, "a+", 384);
19548
- try {
19549
- if (process.platform !== "win32")
19550
- tighten(fd);
19551
- const size = fs12.fstatSync(fd).size;
19552
- let fence = "";
19553
- if (size > 0) {
19554
- const last = Buffer.alloc(1);
19555
- fs12.readSync(fd, last, 0, 1, size - 1);
19556
- if (last[0] !== 10)
19557
- fence = `
19558
- `;
19559
- }
19560
- const line = build(lastLine(fd, size));
19561
- if (!line.ok)
19562
- return line;
19563
- const bytes = Buffer.from(fence + line.value);
19564
- const written = fs12.writeSync(fd, bytes);
19565
- if (written !== bytes.length)
19566
- return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
19567
- return { ok: true, value: undefined };
19568
- } finally {
19569
- fs12.closeSync(fd);
19642
+ if (syntax.quotes.includes(char)) {
19643
+ if (!scanString(char, true, Boolean(syntax.multiline)))
19644
+ return null;
19645
+ continue;
19570
19646
  }
19571
- };
19572
- try {
19573
- fs12.mkdirSync(path14.dirname(file), { recursive: true });
19574
- if (!options.forceLock && !onNetworkFs(path14.dirname(file)))
19575
- return write();
19576
- const locked = withLedgerLock(file, write);
19577
- return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
19578
- } catch (error) {
19579
- return err("unavailable", `cannot append to ${file}: ${error.message}`);
19580
- }
19581
- }
19582
- var sealed = (row, id, at, previous) => {
19583
- const { prevHash: _prev, rowHash: _row, ...fields } = row;
19584
- const prevHash = previous === null ? null : sha2562(previous);
19585
- const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
19586
- const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
19587
- return { full, line: `${JSON.stringify(full)}
19588
- ` };
19589
- };
19590
- function appendRaw(cwd, row, options) {
19591
- const file = ledgerPath(cwd);
19592
- if (!file.ok)
19593
- return file;
19594
- const id = newId2();
19595
- const at = (options.now ?? new Date).toISOString();
19596
- let full = null;
19597
- const written = appendLine(file.value, (previous) => {
19598
- const next = sealed(row, id, at, previous);
19599
- const bytes = Buffer.byteLength(next.line);
19600
- if (bytes > MAX_LINE_BYTES)
19601
- 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>`);
19602
- full = next.full;
19603
- return { ok: true, value: next.line };
19604
- }, options);
19605
- return written.ok ? { ok: true, value: full } : written;
19606
- }
19607
- function observedRowBytes(row) {
19608
- const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
19609
- return Buffer.byteLength(sample.line);
19610
- }
19611
- function appendObserved(cwd, row, options = {}) {
19612
- return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
19613
- }
19614
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19615
- var str2 = (value) => typeof value === "string" ? value : null;
19616
- var normalizeActor = (value) => {
19617
- const actor = isRecord3(value) ? value : {};
19618
- return {
19619
- host: str2(actor.host) ?? "unknown",
19620
- session: str2(actor.session),
19621
- agentId: str2(actor.agentId)
19622
- };
19623
- };
19624
- function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
19625
- if (by.type !== target.type)
19626
- return `a ${by.type} row cannot supersede a ${target.type} row`;
19627
- if (!by.actor.session || by.actor.session !== target.actor.session)
19628
- return "only the session that wrote a row can supersede it";
19629
- if (!onBranch(target))
19630
- return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
19631
- if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
19632
- return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
19633
- if (by.type === "verdict" && by.self === true && target.self !== true)
19634
- return "a self verdict cannot replace an independent verdict";
19635
- return null;
19636
- }
19637
- function readLedger(cwd, options = {}) {
19638
- const file = ledgerPath(cwd);
19639
- if (!file.ok)
19640
- return file;
19641
- let text = "";
19642
- let stat = null;
19643
- try {
19644
- stat = fs12.statSync(file.value);
19645
- text = fs12.readFileSync(file.value, "utf8");
19646
- } catch (error) {
19647
- if (error.code === "ENOENT")
19648
- return {
19649
- ok: true,
19650
- value: {
19651
- path: file.value,
19652
- rows: [],
19653
- skipped: 0,
19654
- integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
19655
- }
19656
- };
19657
- return err("unavailable", `cannot read ${file.value}: ${error.message}`);
19658
- }
19659
- const rows = [];
19660
- let skipped = 0;
19661
- const integrity = { chained: 0, legacy: 0, unverified: [] };
19662
- const earlier = new Set([null]);
19663
- let chainStarted = false;
19664
- const ids = new Set;
19665
- for (const raw of text.split(`
19666
- `)) {
19667
- const trimmed = raw.trim();
19668
- if (!trimmed)
19647
+ if (syntax.literal?.includes(char)) {
19648
+ if (!scanString(char, false, false))
19649
+ return null;
19669
19650
  continue;
19670
- const linked = (prevHash) => earlier.has(prevHash);
19671
- if (options.integrity)
19672
- earlier.add(sha2562(trimmed));
19673
- let parsed;
19674
- try {
19675
- parsed = JSON.parse(trimmed);
19676
- } catch {
19677
- skipped += 1;
19651
+ }
19652
+ if (syntax.rawBacktick && char === "`") {
19653
+ if (!scanString("`", false, true))
19654
+ return null;
19678
19655
  continue;
19679
19656
  }
19680
- if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
19681
- skipped += 1;
19657
+ if (syntax.js && char === "`") {
19658
+ emitVerbatim(char);
19659
+ frames.push({ kind: "template" });
19660
+ index += 1;
19682
19661
  continue;
19683
19662
  }
19684
- let rowIntegrity = options.integrity ? "verified" : undefined;
19685
- let reason = null;
19686
- if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
19687
- chainStarted = true;
19688
- const { rowHash, ...body } = parsed;
19689
- const prevHash = str2(parsed.prevHash);
19690
- if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
19691
- reason = "hash_mismatch";
19692
- else if (!linked(prevHash))
19693
- reason = "broken_link";
19694
- } else if (chainStarted)
19695
- reason = "unsigned";
19696
- else
19697
- rowIntegrity = "legacy";
19698
- if (options.integrity && !reason && ids.has(parsed.id))
19699
- reason = "duplicate_id";
19700
- if (options.integrity)
19701
- ids.add(parsed.id);
19702
- if (reason)
19703
- rowIntegrity = "unverified";
19704
- const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
19705
- const row = {
19706
- ...parsed,
19707
- v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
19708
- id: parsed.id,
19709
- at: parsed.at,
19710
- type: parsed.type,
19711
- actor: normalizeActor(parsed.actor),
19712
- branch: str2(parsed.branch),
19713
- head: str2(parsed.head),
19714
- base: str2(parsed.base),
19715
- baseSha: str2(parsed.baseSha),
19716
- patchId: str2(parsed.patchId),
19717
- diffHash: str2(parsed.diffHash),
19718
- tree: str2(parsed.tree),
19719
- dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
19720
- ...pr === undefined ? {} : { pr },
19721
- ...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
19722
- seq: rows.length + 1,
19723
- superseded: false,
19724
- supersedeIgnored: false,
19725
- ...rowIntegrity ? { integrity: rowIntegrity } : {},
19726
- ...reason ? { integrityReason: reason } : {}
19727
- };
19728
- rows.push(row);
19729
- if (!options.integrity)
19663
+ if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
19664
+ return null;
19665
+ if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
19666
+ const lineEnd = text.indexOf(`
19667
+ `, index);
19668
+ if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
19669
+ return null;
19670
+ }
19671
+ if (syntax.js && char === "/" && regexAllowed()) {
19672
+ let end = index + 1;
19673
+ let inClass = false;
19674
+ while (end < text.length && (inClass || text[end] !== "/")) {
19675
+ if (text[end] === `
19676
+ `)
19677
+ return null;
19678
+ if (text[end] === "\\")
19679
+ end += 1;
19680
+ else if (text[end] === "[")
19681
+ inClass = true;
19682
+ else if (text[end] === "]")
19683
+ inClass = false;
19684
+ end += 1;
19685
+ }
19686
+ if (end >= text.length)
19687
+ return null;
19688
+ emitVerbatim(text.slice(index, end + 1));
19689
+ index = end + 1;
19730
19690
  continue;
19731
- if (rowIntegrity === "verified")
19732
- integrity.chained += 1;
19733
- else if (rowIntegrity === "legacy")
19734
- integrity.legacy += 1;
19735
- else if (reason)
19736
- integrity.unverified.push({
19737
- seq: row.seq,
19738
- id: row.id,
19739
- type: row.type,
19740
- branch: row.branch,
19741
- reason
19742
- });
19691
+ }
19692
+ if (frames.length > 1 && frame.kind === "code") {
19693
+ if (char === "{")
19694
+ frame.braces += 1;
19695
+ else if (char === "}") {
19696
+ if (frame.braces === 0) {
19697
+ emitVerbatim(char);
19698
+ frames.pop();
19699
+ index += 1;
19700
+ continue;
19701
+ }
19702
+ frame.braces -= 1;
19703
+ }
19704
+ }
19705
+ emitCode(char);
19706
+ index += 1;
19743
19707
  }
19744
- const scopes = new Map;
19745
- const scopeOf = (branch) => {
19746
- let scope = scopes.get(branch);
19747
- if (!scope)
19748
- scopes.set(branch, scope = branchScope(cwd, branch));
19749
- return scope;
19750
- };
19751
- const byId = new Map;
19752
- for (const row of rows) {
19753
- const target = row.supersedes ? byId.get(row.supersedes) : undefined;
19754
- if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
19755
- target.superseded = true;
19756
- else if (row.supersedes)
19757
- row.supersedeIgnored = true;
19758
- if (!byId.has(row.id))
19759
- byId.set(row.id, row);
19708
+ if (frames.length !== 1)
19709
+ return null;
19710
+ newline();
19711
+ return out;
19712
+ }
19713
+ function unbalanced(body) {
19714
+ let depth = 0;
19715
+ for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
19716
+ if (part === "{")
19717
+ depth += 1;
19718
+ else if (part === "}")
19719
+ depth = Math.max(0, depth - 1);
19760
19720
  }
19761
- if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
19762
- tamperCache.set(file.value, {
19763
- size: stat.size,
19764
- stamp: fileStamp(stat),
19765
- breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
19766
- });
19767
- return {
19768
- ok: true,
19769
- value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
19770
- };
19721
+ return depth > 0;
19771
19722
  }
19772
- var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
19773
- var tamperCache = new Map;
19774
- var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
19775
- function ledgerTampering(cwd) {
19776
- const file = ledgerPath(cwd);
19777
- if (!file.ok)
19778
- return [];
19723
+ function hasOpaqueLineBreak(text) {
19724
+ return /\r(?!\n)|[\u2028\u2029]/u.test(text);
19725
+ }
19726
+ var decoder = new TextDecoder("utf-8", { fatal: true });
19727
+ function textOf(bytes) {
19728
+ if (bytes.includes(0))
19729
+ return null;
19779
19730
  try {
19780
- const stat = fs12.statSync(file.value);
19781
- const cached = tamperCache.get(file.value);
19782
- if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
19783
- return cached.breaks;
19731
+ return decoder.decode(bytes);
19784
19732
  } catch {
19785
- return [];
19733
+ return null;
19786
19734
  }
19787
- const read = readLedger(cwd, { integrity: true });
19788
- return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
19789
- }
19790
- function tamperedUnblock(cwd) {
19791
- const file = ledgerPath(cwd);
19792
- const where = file.ok ? file.value : "the ledger file";
19793
- 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`;
19794
19735
  }
19795
- 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)}`;
19796
- function verdictBasis(verdict, current) {
19797
- if (verdict.head && verdict.head === current.head)
19798
- return "fresh";
19799
- if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
19800
- return "carried";
19801
- return "stale";
19736
+ function cosmeticChange(file, before, after) {
19737
+ const left = textOf(before);
19738
+ const right = textOf(after);
19739
+ if (left === null || right === null)
19740
+ return false;
19741
+ if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
19742
+ return false;
19743
+ const a = substanceOf(file, left);
19744
+ return a !== null && a === substanceOf(file, right);
19802
19745
  }
19803
- var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
19804
- function trailerSessions(cwd, base, head) {
19805
- if (!base || !head || !safeRef(base) || !safeRef(head))
19806
- return [];
19807
- const out = git4(cwd, [
19808
- "log",
19809
- "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
19810
- `${base}..${head}`
19811
- ]);
19812
- return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
19746
+
19747
+ // packages/workit-core/src/core/policy/lane.ts
19748
+ var LANE_THRESHOLDS = {
19749
+ selfMaxLines: 100,
19750
+ selfMaxFiles: 5,
19751
+ fullMinLines: 500
19752
+ };
19753
+ var RISK_TOKENS = new Set([
19754
+ "auth",
19755
+ "authn",
19756
+ "authz",
19757
+ "oauth",
19758
+ "security",
19759
+ "permission",
19760
+ "permissions",
19761
+ "secret",
19762
+ "secrets",
19763
+ "crypto",
19764
+ "gate",
19765
+ "gates",
19766
+ "policy",
19767
+ "policies",
19768
+ "hook",
19769
+ "hooks",
19770
+ "husky",
19771
+ "lefthook",
19772
+ "release",
19773
+ "releases",
19774
+ "migration",
19775
+ "migrations",
19776
+ "migrate",
19777
+ "workflows"
19778
+ ]);
19779
+ var RISK_FILES = new Set([
19780
+ ".gitlab-ci.yml",
19781
+ "Jenkinsfile",
19782
+ "azure-pipelines.yml",
19783
+ ".pre-commit-config.yaml",
19784
+ ".workit/risk-paths"
19785
+ ]);
19786
+ var RISK_NAMES = new Set([".gitattributes"]);
19787
+ var RISK_PATHS_FILE = ".workit/risk-paths";
19788
+ var LOCKFILES = new Set([
19789
+ "bun.lock",
19790
+ "bun.lockb",
19791
+ "package-lock.json",
19792
+ "npm-shrinkwrap.json",
19793
+ "yarn.lock",
19794
+ "pnpm-lock.yaml",
19795
+ "Cargo.lock",
19796
+ "go.sum",
19797
+ "poetry.lock",
19798
+ "uv.lock",
19799
+ "Pipfile.lock",
19800
+ "Gemfile.lock",
19801
+ "composer.lock"
19802
+ ]);
19803
+ var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
19804
+ var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
19805
+ var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
19806
+ var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
19807
+ var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
19808
+ var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
19809
+ function kindOf(file) {
19810
+ if (file.binary)
19811
+ return "behavior";
19812
+ if (file.generated)
19813
+ return "generated";
19814
+ if (LOCKFILES.has(basename(file.path)))
19815
+ return "lockfile";
19816
+ if (isDoc(file.path))
19817
+ return "doc";
19818
+ if (file.cosmetic)
19819
+ return "cosmetic";
19820
+ return TEST_PATH.test(file.path) ? "test" : "behavior";
19813
19821
  }
19814
- var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
19815
- function branchScope(cwd, branch) {
19816
- const renamedAt = new Map;
19817
- let since = Number.NEGATIVE_INFINITY;
19818
- if (safeRef(branch)) {
19819
- const log = git4(cwd, [
19820
- "log",
19821
- "-g",
19822
- "--date=unix",
19823
- "--format=%gd%x09%gs",
19824
- `refs/heads/${branch}`,
19825
- "--"
19826
- ]);
19827
- for (const line of (log ?? "").split(`
19828
- `)) {
19829
- const [selector = "", subject = ""] = line.split("\t");
19830
- const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
19831
- if (!Number.isFinite(seconds))
19832
- continue;
19833
- since = seconds * 1000;
19834
- const match = RENAMED.exec(subject);
19835
- if (!match || match[1] === branch)
19836
- continue;
19837
- const until = (seconds + 1) * 1000;
19838
- renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
19822
+ var globCache = new Map;
19823
+ function globMatch(pattern, file) {
19824
+ let regex = globCache.get(pattern);
19825
+ if (!regex) {
19826
+ let source = "";
19827
+ for (let index = 0;index < pattern.length; index += 1) {
19828
+ const char = pattern[index];
19829
+ if (char === "*" && pattern[index + 1] === "*") {
19830
+ source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
19831
+ index += pattern[index + 2] === "/" ? 2 : 1;
19832
+ } else if (char === "*")
19833
+ source += "[^/]*";
19834
+ else if (char === "?")
19835
+ source += "[^/]";
19836
+ else
19837
+ source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
19839
19838
  }
19839
+ regex = new RegExp(`^${source}$`, "u");
19840
+ globCache.set(pattern, regex);
19840
19841
  }
19841
- return (row) => {
19842
- if (row.branch === branch)
19843
- return true;
19844
- const until = row.branch === null ? undefined : renamedAt.get(row.branch);
19845
- if (until === undefined)
19846
- return false;
19847
- const at = Date.parse(row.at);
19848
- return at >= since && at <= until;
19849
- };
19850
- }
19851
- var renameAwareBranch = (scopeOf, branch) => {
19852
- return (target) => {
19853
- const own = typeof branch === "string" ? branch : null;
19854
- const theirs = typeof target.branch === "string" ? target.branch : null;
19855
- if (own === theirs)
19856
- return true;
19857
- if (own === null || theirs === null || typeof target.at !== "string")
19858
- return false;
19859
- return scopeOf(own)({ branch: theirs, at: target.at });
19860
- };
19861
- };
19862
- function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
19863
- const sessions = new Set;
19864
- for (const row of rows) {
19865
- if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
19866
- continue;
19867
- const session = str2(row.session) ?? row.actor.session;
19868
- if (session)
19869
- sessions.add(session);
19870
- }
19871
- for (const session of trailerSessions(cwd, range.base, range.head))
19872
- sessions.add(session);
19873
- return sessions;
19842
+ return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
19874
19843
  }
19875
- var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
19876
- var authoredBy = (authors, session) => {
19877
- if (authors.has(session))
19844
+ function isRiskPath(file, extra = []) {
19845
+ if (extra.some((pattern) => globMatch(pattern, file)))
19878
19846
  return true;
19879
- for (const author of authors)
19880
- if (derivesFrom(author, session) || derivesFrom(session, author))
19881
- return true;
19882
- return false;
19883
- };
19884
- var TRUST_REASONS = new Set([
19885
- "unsigned",
19886
- "hash_mismatch",
19887
- "broken_link",
19888
- "duplicate_id",
19889
- "unissued_session",
19890
- "unbound_session"
19891
- ]);
19892
- var SHELL_VOUCHED_HOSTS = new Set([
19893
- "cli",
19894
- "opencode",
19895
- "pi",
19896
- "codex_cli",
19897
- "codex_desktop"
19898
- ]);
19899
- function sessionTrust(cwd, row) {
19900
- const session = row.actor.session;
19901
- if (!session || row.self === true)
19902
- return null;
19903
- const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
19904
- const host = row.actor.host;
19905
- const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
19906
- if (!issuedOnly)
19907
- return null;
19908
- const binding = issuedBinding(cwd, session);
19909
- if (binding === null)
19910
- return "unissued_session";
19911
- return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
19912
- }
19913
- function trustReasons(cwd, row) {
19914
- if (row.integrity === "unverified")
19915
- return [row.integrityReason ?? "unsigned"];
19916
- if (row.integrity !== "verified")
19917
- return ["unsigned"];
19918
- const session = sessionTrust(cwd, row);
19919
- return session ? [session] : [];
19847
+ if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
19848
+ return true;
19849
+ if (TEST_PATH.test(file) || isDoc(file))
19850
+ return false;
19851
+ 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)));
19920
19852
  }
19921
- function withIntegrity(cwd, rows) {
19922
- if (rows.every((row) => row.integrity !== undefined))
19923
- return rows;
19924
- const read = readLedger(cwd, { integrity: true });
19925
- const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
19926
- return rows.map((row) => {
19927
- if (row.integrity !== undefined)
19928
- return row;
19929
- const fresh = bySeq.get(row.seq);
19930
- return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
19853
+ var parseRiskPaths = (text) => text.split(`
19854
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
19855
+ function computeLane(input) {
19856
+ const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
19857
+ const risky = new Set(riskFiles);
19858
+ const kinds = input.files.map((file) => ({
19859
+ file,
19860
+ kind: risky.has(file.path) ? "behavior" : kindOf(file)
19861
+ }));
19862
+ const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
19863
+ const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
19864
+ const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
19865
+ const result = (lane, reasons) => ({
19866
+ lane,
19867
+ reasons,
19868
+ lines,
19869
+ files: behavior.length,
19870
+ riskFiles
19931
19871
  });
19872
+ const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
19873
+ if (input.riskTier === "high")
19874
+ return result("full", ["the task judgment records high risk"]);
19875
+ if (riskFiles.length)
19876
+ return result("full", [
19877
+ `touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
19878
+ ]);
19879
+ if (!behavior.length)
19880
+ return result("none", [
19881
+ input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
19882
+ ]);
19883
+ if (lines >= fullMinLines)
19884
+ return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
19885
+ if (lines > selfMaxLines)
19886
+ return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
19887
+ if (behavior.length > selfMaxFiles)
19888
+ return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
19889
+ if (opaque.length)
19890
+ return result("focused", [
19891
+ `a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
19892
+ ]);
19893
+ return result("self", [
19894
+ `small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
19895
+ ]);
19932
19896
  }
19933
- var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
19934
- var independenceReasons = (row, authors) => {
19935
- const reasons = [];
19936
- if (!row.actor.session)
19937
- reasons.push("no_session");
19938
- else if (authoredBy(authors, row.actor.session))
19939
- reasons.push("author_session");
19940
- if (row.self === true && !reasons.includes("no_session"))
19941
- reasons.push("self");
19942
- return reasons;
19943
- };
19944
- function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
19945
- let effective = null;
19946
- for (const row of rows) {
19947
- if (!effective) {
19948
- effective = row;
19949
- continue;
19950
- }
19951
- if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
19952
- continue;
19953
- const rowIndependent = reasonsOf(row).length === 0;
19954
- const effIndependent = reasonsOf(effective).length === 0;
19955
- if (effIndependent && !rowIndependent && isCurrent(effective))
19956
- continue;
19957
- if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
19958
- continue;
19959
- effective = row;
19897
+ function untrackedStat(cwd, file) {
19898
+ try {
19899
+ const bytes = readFileSync4(path14.join(cwd, file));
19900
+ if (bytes.includes(0))
19901
+ return { path: file, added: 0, deleted: 0, binary: true };
19902
+ const text = bytes.toString("utf8");
19903
+ if (hasOpaqueLineBreak(text))
19904
+ return { path: file, added: 0, deleted: 0, binary: true };
19905
+ const lines = text.split(`
19906
+ `).length - (text.endsWith(`
19907
+ `) ? 1 : 0);
19908
+ return { path: file, added: lines, deleted: 0 };
19909
+ } catch {
19910
+ return { path: file, added: 0, deleted: 0, binary: true };
19960
19911
  }
19961
- return effective;
19962
19912
  }
19963
- function restackCarries(rows, branch, from, to) {
19964
- if (!from || !to)
19965
- return false;
19966
- const edges = new Map;
19967
- for (const row of rows)
19968
- if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
19969
- edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
19970
- const seen = new Set([from]);
19971
- const queue = [from];
19972
- while (queue.length) {
19973
- const next = queue.shift();
19974
- if (next === to)
19975
- return true;
19976
- for (const target of edges.get(next) ?? [])
19977
- if (!seen.has(target)) {
19978
- seen.add(target);
19979
- queue.push(target);
19913
+ var REGULAR = new Set(["100644", "100755"]);
19914
+ var ABSENT = /^0+$/u;
19915
+ function cosmeticFiles(cwd, fork, head) {
19916
+ const raw = diffRaw(cwd, fork, head);
19917
+ if (!raw)
19918
+ return null;
19919
+ const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
19920
+ const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
19921
+ if (!blobs)
19922
+ return null;
19923
+ const out = new Set;
19924
+ const opaque = new Set;
19925
+ for (const entry of candidates) {
19926
+ const before = blobs.get(entry.oldBlob);
19927
+ let after = blobs.get(entry.newBlob);
19928
+ if (!after && head === null && ABSENT.test(entry.newBlob))
19929
+ try {
19930
+ after = readFileSync4(path14.join(cwd, entry.path));
19931
+ } catch {
19932
+ after = undefined;
19980
19933
  }
19934
+ if (before && after && cosmeticChange(entry.path, before, after))
19935
+ out.add(entry.path);
19936
+ else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
19937
+ opaque.add(entry.path);
19981
19938
  }
19982
- return false;
19939
+ return { cosmetic: out, opaque };
19983
19940
  }
19984
- function checkVerdicts(cwd, branch, ledgerRows) {
19985
- const rows = withIntegrity(cwd, ledgerRows);
19986
- const head = branchHead(cwd, branch);
19987
- const fallbackBase = defaultBase(cwd);
19988
- const scope = branchScope(cwd, branch);
19989
- const keyCache = new Map;
19990
- const keysAt = (base, at) => {
19991
- if (!base || !at)
19992
- return { patchId: null, diffHash: null };
19993
- const cacheKey = `${base}\x00${at}`;
19994
- let key = keyCache.get(cacheKey);
19995
- if (!key) {
19996
- key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
19997
- keyCache.set(cacheKey, key);
19998
- }
19999
- return key;
20000
- };
20001
- const keyFor = (base) => keysAt(base, head);
20002
- const basisOf = (row) => {
20003
- const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
20004
- return basis === "stale" && restackCarries(rows, branch, row.head, head) ? "carried" : basis;
20005
- };
20006
- const sameCode = (failed, row) => {
20007
- if (failed.head && failed.head === row.head)
20008
- return true;
20009
- const base = failed.base ?? fallbackBase;
20010
- const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
20011
- const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
20012
- return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
20013
- };
20014
- const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
20015
- const failureAuthorCache = new Map;
20016
- const failureAuthors = (row) => {
20017
- let set = failureAuthorCache.get(row.seq);
20018
- if (!set) {
20019
- set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
20020
- failureAuthorCache.set(row.seq, set);
20021
- }
20022
- return set;
20023
- };
20024
- const reasonCache = new Map;
20025
- const reasonsOf = (row) => {
20026
- let reasons = reasonCache.get(row);
20027
- if (!reasons) {
20028
- const failing = FAILING.has(String(row.result));
20029
- reasons = [
20030
- ...trustReasons(cwd, row),
20031
- ...independenceReasons(row, failing ? failureAuthors(row) : authors)
20032
- ];
20033
- reasonCache.set(row, reasons);
20034
- }
20035
- return reasons;
20036
- };
20037
- const retired = new Set;
20038
- for (const row of rows)
20039
- if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
20040
- retired.add(row.supersedes);
20041
- const byKind = new Map;
20042
- for (const row of rows)
20043
- if (isLiveVerdict(row, retired) && scope(row)) {
20044
- const kind = typeof row.kind === "string" ? row.kind : "review";
20045
- byKind.set(kind, [...byKind.get(kind) ?? [], row]);
20046
- }
20047
- const verdicts = [];
20048
- const warnings = [];
20049
- for (const [kind, list] of byKind) {
20050
- const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
20051
- if (!row)
20052
- continue;
20053
- const basis = basisOf(row);
20054
- const current = basis !== "stale";
20055
- const independence = reasonsOf(row);
20056
- const reasons = [
20057
- ...current ? [] : ["stale"],
20058
- ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
20059
- ...independence
20060
- ];
20061
- verdicts.push({
20062
- kind,
20063
- basis,
20064
- current,
20065
- independent: independence.length === 0,
20066
- accepted: reasons.length === 0,
20067
- reasons,
20068
- verdict: row
20069
- });
20070
- if (reasons.length === 0) {
20071
- for (const later of list)
20072
- if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
20073
- 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`);
20074
- }
20075
- }
20076
- for (const row of rows)
20077
- if (row.integrity === "unverified" && scope(row))
20078
- 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)`);
20079
- const tampered = ledgerTampering(cwd);
20080
- if (tampered.length) {
20081
- warnings.push(tamperedWarning(cwd, tampered));
20082
- for (const entry of verdicts) {
20083
- entry.accepted = false;
20084
- entry.reasons = [...entry.reasons, "ledger_tampered"];
20085
- }
20086
- }
20087
- verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
20088
- const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
20089
- const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
20090
- const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
20091
- const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
20092
- accepted: false,
20093
- verdict: failing?.verdict ?? null,
20094
- reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
20095
- };
20096
- 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))));
20097
- const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
20098
- return {
20099
- branch,
20100
- head,
20101
- review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
20102
- selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
20103
- base: newestCurrent?.verdict.base ?? fallbackBase,
20104
- patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
20105
- current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
20106
- accepted,
20107
- verdicts,
20108
- authors: [...authors].toSorted(),
20109
- warnings
19941
+ function generatedAt(cwd, fork, paths) {
19942
+ const files = treeFilesNamed(cwd, fork, ".gitattributes");
19943
+ const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
19944
+ if (!files || !blobs)
19945
+ return new Set;
19946
+ const texts = new Map;
19947
+ for (const [file, id] of files) {
19948
+ const bytes = blobs.get(id);
19949
+ if (bytes)
19950
+ texts.set(file, bytes.toString("utf8"));
19951
+ }
19952
+ return generatedByAttributes(texts, paths);
19953
+ }
19954
+ var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
19955
+ function laneFiles(cwd, fork, head, attributesAt = fork) {
19956
+ const stats = diffNumstat(cwd, fork, head);
19957
+ const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
19958
+ if (!stats || !cosmetic)
19959
+ return null;
19960
+ const files = [
19961
+ ...stats.map((entry) => ({
19962
+ ...entry,
19963
+ ...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
19964
+ cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
19965
+ })),
19966
+ ...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
19967
+ ];
19968
+ const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
19969
+ return files.map((entry) => ({
19970
+ ...entry,
19971
+ generated: generated.has(entry.path)
19972
+ }));
19973
+ }
19974
+ function driftFiles(cwd, range) {
19975
+ if (range.fromFork === range.toFork)
19976
+ return laneFiles(cwd, range.from, range.to, range.toFork);
19977
+ const drifted = ownChangeDrift(cwd, range);
19978
+ const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
19979
+ if (!drifted || !stats)
19980
+ return null;
19981
+ const byPath = new Map(stats.map((entry) => [entry.path, entry]));
19982
+ const generated = generatedAt(cwd, range.toFork, drifted);
19983
+ return drifted.map((file) => ({
19984
+ path: file,
19985
+ added: byPath.get(file)?.added ?? 0,
19986
+ deleted: byPath.get(file)?.deleted ?? 0,
19987
+ ...byPath.get(file)?.binary ? { binary: true } : {},
19988
+ cosmetic: false,
19989
+ generated: generated.has(file)
19990
+ }));
19991
+ }
19992
+ function branchLane(cwd, options) {
19993
+ const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
19994
+ const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
19995
+ const head = options.workingTree ? null : options.head;
19996
+ const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
19997
+ const files = fork ? laneFiles(cwd, fork, head) : null;
19998
+ const riskTier = options.judgment?.riskTier ?? null;
19999
+ if (!fork || !files) {
20000
+ const high = riskTier === "high";
20001
+ return {
20002
+ lane: high ? "full" : "focused",
20003
+ reasons: [
20004
+ `the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
20005
+ ...high ? ["the task judgment records high risk"] : []
20006
+ ],
20007
+ lines: 0,
20008
+ files: 0,
20009
+ riskFiles: [],
20010
+ base,
20011
+ head: options.head
20012
+ };
20013
+ }
20014
+ const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
20015
+ return {
20016
+ ...computeLane({
20017
+ files,
20018
+ riskTier,
20019
+ riskPaths: [...repoRisk, ...options.riskPaths ?? []]
20020
+ }),
20021
+ base,
20022
+ head: options.head
20110
20023
  };
20111
20024
  }
20112
- var OBSERVED_ONLY = new Set([
20113
- "check",
20114
- "merge.unverified",
20115
- "policy.judged",
20116
- ...PR_ROW_TYPES
20117
- ]);
20118
-
20119
- // packages/workit-core/src/core/policy/lane.ts
20120
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
20121
- import path16 from "node:path";
20025
+ var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
20122
20026
 
20123
- // packages/workit-core/src/core/pr-create.ts
20124
- import fs13 from "node:fs";
20125
- import path15 from "node:path";
20126
- import { spawnSync as spawnSync10 } from "node:child_process";
20127
- // packages/workit-core/src/forge/pr-body.ts
20128
- function parseGhIssue(value) {
20129
- const m = /issues\/(\d+)/.exec(value);
20130
- return m ? m[1] : String(value).trim().replace(/^#/, "");
20027
+ // packages/workit-core/src/core/policy/carry.ts
20028
+ var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
20029
+ function verdictDrift(cwd, input) {
20030
+ const since = input.from;
20031
+ const stale = (reason, files = null, focused = false) => ({
20032
+ carries: false,
20033
+ since,
20034
+ files,
20035
+ focused,
20036
+ reason
20037
+ });
20038
+ const fromFork = mergeBase(cwd, input.fromBase, input.from);
20039
+ const toFork = mergeBase(cwd, input.toBase, input.to);
20040
+ const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
20041
+ if (!fromFork || !toFork || !files)
20042
+ return stale(`the change since ${since.slice(0, 12)} cannot be read`);
20043
+ const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
20044
+ const lane = computeLane({ files, riskPaths });
20045
+ const paths = files.map((file) => file.path);
20046
+ if (lane.lane === "none")
20047
+ return { carries: true, rule: "lane-none", since, files: paths };
20048
+ if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
20049
+ return { carries: true, rule: "tests-only", since, files: paths };
20050
+ return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
20131
20051
  }
20132
20052
 
20133
- // packages/workit-core/src/core/pr-create.ts
20134
- function whichOnPath(tool) {
20135
- const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
20136
- for (const dir of (process.env.PATH ?? "").split(path15.delimiter)) {
20137
- if (!dir)
20138
- continue;
20139
- for (const name of names) {
20140
- const candidate = path15.join(dir, name);
20141
- try {
20142
- fs13.accessSync(candidate, fs13.constants.X_OK);
20143
- return candidate;
20144
- } catch {}
20145
- }
20146
- }
20147
- return null;
20148
- }
20149
- var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
20150
- var pushRemote = (cwd) => {
20151
- const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
20053
+ // packages/workit-core/src/ledger.ts
20054
+ var LEDGER_VERSION = 1;
20055
+ var MAX_LINE_BYTES = 4096;
20056
+ var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
20057
+ var FAILING = new Set(["failed", "blocked"]);
20058
+ var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
20059
+ var PR_ROW_TYPES = new Set([
20060
+ "pr.created",
20061
+ "pr.status",
20062
+ "pr.merged",
20063
+ "pr.linked"
20064
+ ]);
20065
+ var err = (code, error, unblock) => ({
20066
+ ok: false,
20067
+ code,
20068
+ error,
20069
+ ...unblock ? { unblock } : {}
20070
+ });
20071
+ var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
20072
+ const run = spawnSync9("git", args, {
20152
20073
  cwd,
20153
- encoding: "utf8"
20074
+ encoding: "utf8",
20075
+ timeout: timeoutMs,
20076
+ killSignal: "SIGKILL",
20077
+ windowsHide: true,
20078
+ maxBuffer: 256 * 1024 * 1024,
20079
+ env: {
20080
+ ...process.env,
20081
+ GIT_TERMINAL_PROMPT: "0",
20082
+ GIT_OPTIONAL_LOCKS: "0",
20083
+ LC_ALL: "C",
20084
+ LANGUAGE: "C"
20085
+ }
20154
20086
  });
20155
- if (result.status !== 0)
20156
- return null;
20157
- const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
20158
- return urls.length === 1 ? urls[0] : null;
20087
+ return {
20088
+ ok: run.status === 0,
20089
+ stdout: run.stdout ?? "",
20090
+ stderr: run.error ? String(run.error.message) : run.stderr ?? ""
20091
+ };
20159
20092
  };
20160
-
20161
- // packages/workit-core/src/forge/redact.ts
20162
- 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_.-]*`;
20163
- var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
20164
- var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
20165
- var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
20166
-
20167
- // packages/workit-core/src/git/ops.ts
20168
- var COMMIT_TIMEOUT_MS = 10 * 60000;
20169
-
20170
- // packages/workit-core/src/core/policy/attributes.ts
20171
- function patternRegex(dir, pattern) {
20172
- if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
20093
+ var git4 = (cwd, args) => {
20094
+ const run = gitRun2(cwd, args);
20095
+ const value = run.ok ? run.stdout.trim() : "";
20096
+ return value ? value : null;
20097
+ };
20098
+ var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
20099
+ function resolveCommit(cwd, ref) {
20100
+ if (!safeRef2(ref))
20173
20101
  return null;
20174
- const anchored = pattern.includes("/");
20175
- const body = pattern.replace(/^\//u, "");
20176
- let source = "";
20177
- for (let index = 0;index < body.length; index += 1) {
20178
- const char = body[index];
20179
- if (char === "*" && body[index + 1] === "*") {
20180
- const slash = body[index + 2] === "/";
20181
- source += slash ? "(?:.*/)?" : ".*";
20182
- index += slash ? 2 : 1;
20183
- } else if (char === "*")
20184
- source += "[^/]*";
20185
- else if (char === "?")
20186
- source += "[^/]";
20187
- else if (char === "[") {
20188
- const end = body.indexOf("]", index + 2);
20189
- if (end < 0)
20190
- return null;
20191
- const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
20192
- source += `[${set}]`;
20193
- index = end;
20194
- } else if (char === "\\" && index + 1 < body.length) {
20195
- source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
20196
- index += 1;
20197
- } else
20198
- source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
20199
- }
20200
- const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
20201
- try {
20202
- return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
20203
- } catch {
20102
+ return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
20103
+ }
20104
+ function branchHead(cwd, branch) {
20105
+ if (!safeRef2(branch))
20204
20106
  return null;
20205
- }
20107
+ return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
20206
20108
  }
20207
- function rulesOf(dir, text) {
20208
- const out = [];
20209
- for (const raw of text.split(`
20109
+ function diffHash(cwd, base, head) {
20110
+ if (!safeRef2(base) || !safeRef2(head))
20111
+ return null;
20112
+ const diff = gitRun2(cwd, [
20113
+ "diff",
20114
+ "-U3",
20115
+ "--diff-algorithm=myers",
20116
+ "--indent-heuristic",
20117
+ "--no-color",
20118
+ "--no-ext-diff",
20119
+ "--no-textconv",
20120
+ "--no-renames",
20121
+ "--full-index",
20122
+ "--binary",
20123
+ "--src-prefix=a/",
20124
+ "--dst-prefix=b/",
20125
+ `${base}...${head}`,
20126
+ "--"
20127
+ ], GIT_TIMEOUTS.worktree);
20128
+ if (!diff.ok || !diff.stdout)
20129
+ return null;
20130
+ const hash = createHash9("sha256");
20131
+ for (const line of diff.stdout.split(`
20210
20132
  `)) {
20211
- const line = raw.trim();
20212
- if (!line || line.startsWith("#") || line.startsWith("[attr]"))
20133
+ if (line.startsWith("index "))
20213
20134
  continue;
20214
- const [pattern, ...attributes] = line.split(/\s+/u);
20215
- let generated = null;
20216
- for (const attribute of attributes) {
20217
- if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
20218
- generated = true;
20219
- else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
20220
- generated = false;
20221
- }
20222
- if (generated === null)
20223
- continue;
20224
- const regex = patternRegex(dir, pattern);
20225
- if (regex)
20226
- out.push({ regex, generated });
20135
+ hash.update(line.startsWith("@@ ") ? "@@" : line);
20136
+ hash.update(`
20137
+ `);
20227
20138
  }
20228
- return out;
20139
+ return `sha256:${hash.digest("hex")}`;
20229
20140
  }
20230
- function generatedByAttributes(files, paths) {
20231
- const rules = [...files].map(([file, text]) => {
20232
- const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
20233
- return {
20234
- depth: dir ? dir.split("/").length : 0,
20235
- rules: rulesOf(dir, text)
20236
- };
20237
- }).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
20238
- const out = new Set;
20239
- for (const file of paths) {
20240
- const last = rules.findLast((rule) => rule.regex.test(file));
20241
- if (last?.generated)
20242
- out.add(file);
20243
- }
20244
- return out;
20141
+ function storeRoot(cwd) {
20142
+ const location = resolveStore(path15.resolve(cwd));
20143
+ if (location instanceof Error)
20144
+ 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");
20145
+ return { ok: true, value: { root: location.dir, shared: location.shared } };
20245
20146
  }
20246
-
20247
- // packages/workit-core/src/core/policy/substance.ts
20248
- var SLASH_BLOCK = [["/*", "*/"]];
20249
- var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
20250
- var SYNTAXES = Object.fromEntries([
20251
- ...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
20252
- ...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
20253
- ext,
20254
- { ...C_LIKE, triple: true }
20255
- ]),
20256
- ["go", { ...C_LIKE, rawBacktick: true }],
20257
- ...["css", "scss", "less"].map((ext) => [
20258
- ext,
20259
- { line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
20260
- ]),
20261
- ...["py", "pyi"].map((ext) => [
20262
- ext,
20263
- { line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
20264
- ]),
20265
- ["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
20266
- ...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
20267
- ext,
20268
- { line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
20269
- ])
20147
+ function ledgerPath(cwd) {
20148
+ const root = storeRoot(cwd);
20149
+ return root.ok ? { ok: true, value: path15.join(root.value.root, "ledger", "ledger.jsonl") } : root;
20150
+ }
20151
+ var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
20152
+ var NETWORK_FS_TYPES = new Set([
20153
+ 26985,
20154
+ 20859,
20155
+ 4266872130,
20156
+ 4283649346,
20157
+ 1702057286,
20158
+ 1397113167,
20159
+ 12805120,
20160
+ 16914839,
20161
+ 1196443219,
20162
+ 198183888
20270
20163
  ]);
20271
- 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;
20272
- var extensionOf = (file) => {
20273
- const name = file.slice(file.lastIndexOf("/") + 1);
20274
- const dot = name.lastIndexOf(".");
20275
- return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
20164
+ var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
20165
+ var onNetworkFs = (dir) => {
20166
+ if (process.platform !== "linux")
20167
+ return false;
20168
+ try {
20169
+ return isNetworkFsType(fs12.statfsSync(dir).type);
20170
+ } catch {
20171
+ return false;
20172
+ }
20276
20173
  };
20277
- var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
20278
- var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
20279
- var SPACE = /[ \t\r\f\v]/u;
20280
- var WORD = /[\w$]/u;
20281
- var NO_SPACE_AFTER = "([{,;";
20282
- var NO_SPACE_BEFORE = ")]},;";
20283
- var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
20284
- var REGEX_KEYWORDS = new Set([
20285
- "return",
20286
- "typeof",
20287
- "instanceof",
20288
- "case",
20289
- "do",
20290
- "else",
20291
- "in",
20292
- "of",
20293
- "new",
20294
- "delete",
20295
- "void",
20296
- "throw",
20297
- "yield",
20298
- "await"
20299
- ]);
20300
- function substanceOf(file, text) {
20301
- const syntax = syntaxOf(file);
20302
- if (!syntax)
20303
- return plainSubstance(text);
20304
- const markup = syntax.block.some(([open]) => open === "<!--");
20305
- if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
20306
- return plainSubstance(text);
20307
- return lex(syntax, text);
20308
- }
20309
- function plainSubstance(text) {
20310
- return text.split(`
20311
- `).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
20312
- `);
20313
- }
20314
- function lex(syntax, text) {
20315
- const collapse = !syntax.indented;
20316
- let out = "";
20317
- let lineStart = 0;
20318
- let kept = 0;
20319
- let pendingSpace = false;
20320
- const frames = [{ kind: "code", braces: 0 }];
20321
- let index = 0;
20322
- if (text.startsWith("#!")) {
20323
- const end = text.indexOf(`
20324
- `);
20325
- out = end < 0 ? text : text.slice(0, end);
20326
- kept = out.length;
20327
- index = end < 0 ? text.length : end;
20174
+ var LOCK_WAIT_MS = 5000;
20175
+ var LOCK_STALE_MS = 30000;
20176
+ var LOCK_OWNER = "owner";
20177
+ var lockToken = (lock) => {
20178
+ try {
20179
+ return fs12.readFileSync(path15.join(lock, LOCK_OWNER), "utf8");
20180
+ } catch {
20181
+ return null;
20328
20182
  }
20329
- const emitCode = (chunk) => {
20330
- if (collapse && pendingSpace && out.length > lineStart) {
20331
- const before = out[out.length - 1];
20332
- if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
20333
- out += " ";
20183
+ };
20184
+ var lockAgeMs = (lock) => {
20185
+ for (const target of [path15.join(lock, LOCK_OWNER), lock]) {
20186
+ try {
20187
+ return Date.now() - fs12.statSync(target).mtimeMs;
20188
+ } catch {}
20189
+ }
20190
+ return null;
20191
+ };
20192
+ var ledgerLock = {
20193
+ acquire(lock) {
20194
+ try {
20195
+ fs12.mkdirSync(lock);
20196
+ } catch (error) {
20197
+ if (error.code === "EEXIST")
20198
+ return null;
20199
+ throw error;
20334
20200
  }
20335
- pendingSpace = false;
20336
- out += chunk;
20337
- };
20338
- const emitVerbatim = (chunk) => {
20339
- emitCode(chunk);
20340
- kept = out.length;
20341
- };
20342
- const newline = () => {
20343
- if (!collapse) {
20344
- let end = out.length;
20345
- while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
20346
- end -= 1;
20347
- out = out.slice(0, end);
20201
+ const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
20202
+ fs12.writeFileSync(path15.join(lock, LOCK_OWNER), token);
20203
+ return token;
20204
+ },
20205
+ release(lock, token) {
20206
+ if (lockToken(lock) !== token)
20207
+ return false;
20208
+ const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
20209
+ try {
20210
+ fs12.renameSync(lock, tomb);
20211
+ } catch {
20212
+ return false;
20348
20213
  }
20349
- pendingSpace = false;
20350
- if (out.length > lineStart) {
20351
- out += `
20352
- `;
20353
- lineStart = out.length;
20214
+ if (lockToken(tomb) !== token) {
20215
+ try {
20216
+ fs12.renameSync(tomb, lock);
20217
+ } catch {}
20218
+ return false;
20354
20219
  }
20355
- };
20356
- const regexAllowed = () => {
20357
- let end = out.length;
20358
- while (end > 0 && /\s/u.test(out[end - 1]))
20359
- end -= 1;
20360
- if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
20220
+ fs12.rmSync(tomb, { recursive: true, force: true });
20221
+ return true;
20222
+ },
20223
+ token: lockToken,
20224
+ reap(lock, observed) {
20225
+ const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
20226
+ try {
20227
+ fs12.renameSync(lock, tomb);
20228
+ } catch {
20229
+ return false;
20230
+ }
20231
+ if (lockToken(tomb) === observed) {
20232
+ fs12.rmSync(tomb, { recursive: true, force: true });
20361
20233
  return true;
20362
- let start = end;
20363
- while (start > 0 && WORD.test(out[start - 1]))
20364
- start -= 1;
20365
- return REGEX_KEYWORDS.has(out.slice(start, end));
20366
- };
20367
- const scanString = (quote, escapes, multiline) => {
20368
- const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
20369
- const fstring = syntax.fstring === true && /f/iu.test(prefix);
20370
- let end = index + quote.length;
20371
- while (end < text.length) {
20372
- const char = text[end];
20373
- if (escapes && char === "\\") {
20374
- end += 2;
20375
- continue;
20376
- }
20377
- if (text.startsWith(quote, end)) {
20378
- if (fstring && unbalanced(text.slice(index + quote.length, end)))
20379
- return false;
20380
- emitVerbatim(text.slice(index, end + quote.length));
20381
- index = end + quote.length;
20382
- return true;
20383
- }
20384
- if (char === `
20385
- ` && !multiline)
20386
- return false;
20387
- end += 1;
20234
+ }
20235
+ try {
20236
+ fs12.renameSync(tomb, lock);
20237
+ } catch {
20238
+ fs12.rmSync(tomb, { recursive: true, force: true });
20388
20239
  }
20389
20240
  return false;
20390
- };
20391
- while (index < text.length) {
20392
- const frame = frames[frames.length - 1];
20393
- const char = text[index];
20394
- if (frame.kind === "template") {
20395
- if (char === "\\") {
20396
- emitVerbatim(text.slice(index, index + 2));
20397
- index += 2;
20398
- } else if (char === "`") {
20399
- emitVerbatim(char);
20400
- frames.pop();
20401
- index += 1;
20402
- } else if (text.startsWith("${", index)) {
20403
- emitVerbatim("${");
20404
- frames.push({ kind: "code", braces: 0 });
20405
- index += 2;
20406
- } else {
20407
- out += char;
20408
- kept = out.length;
20409
- index += 1;
20410
- }
20411
- continue;
20412
- }
20413
- if (char === `
20414
- `) {
20415
- newline();
20416
- index += 1;
20417
- continue;
20418
- }
20419
- if (SPACE.test(char)) {
20420
- if (collapse)
20421
- pendingSpace = true;
20422
- else
20423
- out += char;
20424
- index += 1;
20425
- continue;
20426
- }
20427
- const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
20428
- if (lineOpener) {
20429
- const end = text.indexOf(`
20430
- `, index);
20431
- const comment = text.slice(index, end < 0 ? text.length : end);
20432
- if (DIRECTIVE.test(comment.slice(lineOpener.length)))
20433
- emitVerbatim(comment);
20434
- else
20435
- pendingSpace = true;
20436
- index = end < 0 ? text.length : end;
20437
- continue;
20438
- }
20439
- const block = syntax.block.find(([open]) => text.startsWith(open, index));
20440
- if (block) {
20441
- const end = text.indexOf(block[1], index + block[0].length);
20442
- if (end < 0)
20443
- return null;
20444
- const comment = text.slice(index, end + block[1].length);
20445
- if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
20446
- emitVerbatim(comment);
20447
- else
20448
- pendingSpace = true;
20449
- if (comment.includes(`
20450
- `))
20451
- newline();
20452
- index = end + block[1].length;
20453
- continue;
20454
- }
20455
- if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
20456
- if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
20457
- return null;
20458
- continue;
20459
- }
20460
- if (syntax.quotes.includes(char)) {
20461
- if (!scanString(char, true, Boolean(syntax.multiline)))
20462
- return null;
20463
- continue;
20464
- }
20465
- if (syntax.literal?.includes(char)) {
20466
- if (!scanString(char, false, false))
20467
- return null;
20468
- continue;
20469
- }
20470
- if (syntax.rawBacktick && char === "`") {
20471
- if (!scanString("`", false, true))
20472
- return null;
20473
- continue;
20474
- }
20475
- if (syntax.js && char === "`") {
20476
- emitVerbatim(char);
20477
- frames.push({ kind: "template" });
20478
- index += 1;
20241
+ }
20242
+ };
20243
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
20244
+ const lock = `${file}.lock`;
20245
+ const deadline = Date.now() + waitMs;
20246
+ let token = null;
20247
+ for (;; ) {
20248
+ token = ledgerLock.acquire(lock);
20249
+ if (token)
20250
+ break;
20251
+ const observed = lockToken(lock);
20252
+ const age = lockAgeMs(lock);
20253
+ if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
20479
20254
  continue;
20480
- }
20481
- if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
20255
+ if (Date.now() >= deadline)
20482
20256
  return null;
20483
- if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
20484
- const lineEnd = text.indexOf(`
20485
- `, index);
20486
- if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
20257
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
20258
+ }
20259
+ try {
20260
+ return fn();
20261
+ } finally {
20262
+ ledgerLock.release(lock, token);
20263
+ }
20264
+ }
20265
+ var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
20266
+ var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
20267
+ ${body}`);
20268
+ var TAIL_BYTES = 64 * 1024;
20269
+ var lastLine = (fd, size) => {
20270
+ for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
20271
+ const buffer = Buffer.alloc(span);
20272
+ fs12.readSync(fd, buffer, 0, span, size - span);
20273
+ const end = buffer.lastIndexOf(10);
20274
+ if (end < 0) {
20275
+ if (span === size)
20487
20276
  return null;
20277
+ continue;
20488
20278
  }
20489
- if (syntax.js && char === "/" && regexAllowed()) {
20490
- let end = index + 1;
20491
- let inClass = false;
20492
- while (end < text.length && (inClass || text[end] !== "/")) {
20493
- if (text[end] === `
20494
- `)
20279
+ let stop = end;
20280
+ let text = "";
20281
+ while (stop >= 0) {
20282
+ const start = buffer.lastIndexOf(10, stop - 1);
20283
+ text = buffer.toString("utf8", start + 1, stop).trim();
20284
+ if (text) {
20285
+ if (start >= 0 || span === size)
20286
+ return text;
20287
+ break;
20288
+ }
20289
+ if (start < 0) {
20290
+ if (span === size)
20495
20291
  return null;
20496
- if (text[end] === "\\")
20497
- end += 1;
20498
- else if (text[end] === "[")
20499
- inClass = true;
20500
- else if (text[end] === "]")
20501
- inClass = false;
20502
- end += 1;
20292
+ break;
20503
20293
  }
20504
- if (end >= text.length)
20505
- return null;
20506
- emitVerbatim(text.slice(index, end + 1));
20507
- index = end + 1;
20508
- continue;
20294
+ stop = start;
20509
20295
  }
20510
- if (frames.length > 1 && frame.kind === "code") {
20511
- if (char === "{")
20512
- frame.braces += 1;
20513
- else if (char === "}") {
20514
- if (frame.braces === 0) {
20515
- emitVerbatim(char);
20516
- frames.pop();
20517
- index += 1;
20518
- continue;
20519
- }
20520
- frame.braces -= 1;
20296
+ if (span === size)
20297
+ return text || null;
20298
+ }
20299
+ return null;
20300
+ };
20301
+ var tighten = (fd) => {
20302
+ try {
20303
+ if ((fs12.fstatSync(fd).mode & 63) !== 0)
20304
+ fs12.fchmodSync(fd, 384);
20305
+ } catch {}
20306
+ };
20307
+ function appendLine(file, build, options) {
20308
+ const write = () => {
20309
+ const fd = fs12.openSync(file, "a+", 384);
20310
+ try {
20311
+ if (process.platform !== "win32")
20312
+ tighten(fd);
20313
+ const size = fs12.fstatSync(fd).size;
20314
+ let fence = "";
20315
+ if (size > 0) {
20316
+ const last = Buffer.alloc(1);
20317
+ fs12.readSync(fd, last, 0, 1, size - 1);
20318
+ if (last[0] !== 10)
20319
+ fence = `
20320
+ `;
20521
20321
  }
20322
+ const line = build(lastLine(fd, size));
20323
+ if (!line.ok)
20324
+ return line;
20325
+ const bytes = Buffer.from(fence + line.value);
20326
+ const written = fs12.writeSync(fd, bytes);
20327
+ if (written !== bytes.length)
20328
+ return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
20329
+ return { ok: true, value: undefined };
20330
+ } finally {
20331
+ fs12.closeSync(fd);
20522
20332
  }
20523
- emitCode(char);
20524
- index += 1;
20333
+ };
20334
+ try {
20335
+ fs12.mkdirSync(path15.dirname(file), { recursive: true });
20336
+ if (!options.forceLock && !onNetworkFs(path15.dirname(file)))
20337
+ return write();
20338
+ const locked = withLedgerLock(file, write);
20339
+ return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
20340
+ } catch (error) {
20341
+ return err("unavailable", `cannot append to ${file}: ${error.message}`);
20525
20342
  }
20526
- if (frames.length !== 1)
20527
- return null;
20528
- newline();
20529
- return out;
20530
20343
  }
20531
- function unbalanced(body) {
20532
- let depth = 0;
20533
- for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
20534
- if (part === "{")
20535
- depth += 1;
20536
- else if (part === "}")
20537
- depth = Math.max(0, depth - 1);
20538
- }
20539
- return depth > 0;
20344
+ var sealed = (row, id, at, previous) => {
20345
+ const { prevHash: _prev, rowHash: _row, ...fields } = row;
20346
+ const prevHash = previous === null ? null : sha2562(previous);
20347
+ const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
20348
+ const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
20349
+ return { full, line: `${JSON.stringify(full)}
20350
+ ` };
20351
+ };
20352
+ function appendRaw(cwd, row, options) {
20353
+ const file = ledgerPath(cwd);
20354
+ if (!file.ok)
20355
+ return file;
20356
+ const id = newId2();
20357
+ const at = (options.now ?? new Date).toISOString();
20358
+ let full = null;
20359
+ const written = appendLine(file.value, (previous) => {
20360
+ const next = sealed(row, id, at, previous);
20361
+ const bytes = Buffer.byteLength(next.line);
20362
+ if (bytes > MAX_LINE_BYTES)
20363
+ 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>`);
20364
+ full = next.full;
20365
+ return { ok: true, value: next.line };
20366
+ }, options);
20367
+ return written.ok ? { ok: true, value: full } : written;
20540
20368
  }
20541
- function hasOpaqueLineBreak(text) {
20542
- return /\r(?!\n)|[\u2028\u2029]/u.test(text);
20369
+ function observedRowBytes(row) {
20370
+ const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
20371
+ return Buffer.byteLength(sample.line);
20543
20372
  }
20544
- var decoder = new TextDecoder("utf-8", { fatal: true });
20545
- function textOf(bytes) {
20546
- if (bytes.includes(0))
20547
- return null;
20548
- try {
20549
- return decoder.decode(bytes);
20550
- } catch {
20551
- return null;
20552
- }
20553
- }
20554
- function cosmeticChange(file, before, after) {
20555
- const left = textOf(before);
20556
- const right = textOf(after);
20557
- if (left === null || right === null)
20558
- return false;
20559
- if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
20560
- return false;
20561
- const a = substanceOf(file, left);
20562
- return a !== null && a === substanceOf(file, right);
20373
+ function appendObserved(cwd, row, options = {}) {
20374
+ return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
20563
20375
  }
20564
-
20565
- // packages/workit-core/src/core/policy/lane.ts
20566
- var LANE_THRESHOLDS = {
20567
- selfMaxLines: 100,
20568
- selfMaxFiles: 5,
20569
- fullMinLines: 500
20376
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20377
+ var str2 = (value) => typeof value === "string" ? value : null;
20378
+ var normalizeActor = (value) => {
20379
+ const actor = isRecord3(value) ? value : {};
20380
+ return {
20381
+ host: str2(actor.host) ?? "unknown",
20382
+ session: str2(actor.session),
20383
+ agentId: str2(actor.agentId)
20384
+ };
20570
20385
  };
20571
- var RISK_TOKENS = new Set([
20572
- "auth",
20573
- "authn",
20574
- "authz",
20575
- "oauth",
20576
- "security",
20577
- "permission",
20578
- "permissions",
20579
- "secret",
20580
- "secrets",
20581
- "crypto",
20582
- "gate",
20583
- "gates",
20584
- "policy",
20585
- "policies",
20586
- "hook",
20587
- "hooks",
20588
- "husky",
20589
- "lefthook",
20590
- "release",
20591
- "releases",
20592
- "migration",
20593
- "migrations",
20594
- "migrate",
20595
- "workflows"
20596
- ]);
20597
- var RISK_FILES = new Set([
20598
- ".gitlab-ci.yml",
20599
- "Jenkinsfile",
20600
- "azure-pipelines.yml",
20601
- ".pre-commit-config.yaml",
20602
- ".workit/risk-paths"
20603
- ]);
20604
- var RISK_NAMES = new Set([".gitattributes"]);
20605
- var RISK_PATHS_FILE = ".workit/risk-paths";
20606
- var LOCKFILES = new Set([
20607
- "bun.lock",
20608
- "bun.lockb",
20609
- "package-lock.json",
20610
- "npm-shrinkwrap.json",
20611
- "yarn.lock",
20612
- "pnpm-lock.yaml",
20613
- "Cargo.lock",
20614
- "go.sum",
20615
- "poetry.lock",
20616
- "uv.lock",
20617
- "Pipfile.lock",
20618
- "Gemfile.lock",
20619
- "composer.lock"
20620
- ]);
20621
- var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
20622
- var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
20623
- var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
20624
- var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
20625
- var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
20626
- var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
20627
- function kindOf(file) {
20628
- if (file.binary)
20629
- return "behavior";
20630
- if (file.generated)
20631
- return "generated";
20632
- if (LOCKFILES.has(basename(file.path)))
20633
- return "lockfile";
20634
- if (isDoc(file.path))
20635
- return "doc";
20636
- if (file.cosmetic)
20637
- return "cosmetic";
20638
- return TEST_PATH.test(file.path) ? "test" : "behavior";
20386
+ function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
20387
+ if (by.type !== target.type)
20388
+ return `a ${by.type} row cannot supersede a ${target.type} row`;
20389
+ if (!by.actor.session || by.actor.session !== target.actor.session)
20390
+ return "only the session that wrote a row can supersede it";
20391
+ if (!onBranch(target))
20392
+ return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
20393
+ if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
20394
+ return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
20395
+ if (by.type === "verdict" && by.self === true && target.self !== true)
20396
+ return "a self verdict cannot replace an independent verdict";
20397
+ return null;
20639
20398
  }
20640
- var globCache = new Map;
20641
- function globMatch(pattern, file) {
20642
- let regex = globCache.get(pattern);
20643
- if (!regex) {
20644
- let source = "";
20645
- for (let index = 0;index < pattern.length; index += 1) {
20646
- const char = pattern[index];
20647
- if (char === "*" && pattern[index + 1] === "*") {
20648
- source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
20649
- index += pattern[index + 2] === "/" ? 2 : 1;
20650
- } else if (char === "*")
20651
- source += "[^/]*";
20652
- else if (char === "?")
20653
- source += "[^/]";
20654
- else
20655
- source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
20399
+ function readLedger(cwd, options = {}) {
20400
+ const file = ledgerPath(cwd);
20401
+ if (!file.ok)
20402
+ return file;
20403
+ let text = "";
20404
+ let stat = null;
20405
+ try {
20406
+ stat = fs12.statSync(file.value);
20407
+ text = fs12.readFileSync(file.value, "utf8");
20408
+ } catch (error) {
20409
+ if (error.code === "ENOENT")
20410
+ return {
20411
+ ok: true,
20412
+ value: {
20413
+ path: file.value,
20414
+ rows: [],
20415
+ skipped: 0,
20416
+ integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
20417
+ }
20418
+ };
20419
+ return err("unavailable", `cannot read ${file.value}: ${error.message}`);
20420
+ }
20421
+ const rows = [];
20422
+ let skipped = 0;
20423
+ const integrity = { chained: 0, legacy: 0, unverified: [] };
20424
+ const earlier = new Set([null]);
20425
+ let chainStarted = false;
20426
+ const ids = new Set;
20427
+ for (const raw of text.split(`
20428
+ `)) {
20429
+ const trimmed = raw.trim();
20430
+ if (!trimmed)
20431
+ continue;
20432
+ const linked = (prevHash) => earlier.has(prevHash);
20433
+ if (options.integrity)
20434
+ earlier.add(sha2562(trimmed));
20435
+ let parsed;
20436
+ try {
20437
+ parsed = JSON.parse(trimmed);
20438
+ } catch {
20439
+ skipped += 1;
20440
+ continue;
20656
20441
  }
20657
- regex = new RegExp(`^${source}$`, "u");
20658
- globCache.set(pattern, regex);
20442
+ if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
20443
+ skipped += 1;
20444
+ continue;
20445
+ }
20446
+ let rowIntegrity = options.integrity ? "verified" : undefined;
20447
+ let reason = null;
20448
+ if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
20449
+ chainStarted = true;
20450
+ const { rowHash, ...body } = parsed;
20451
+ const prevHash = str2(parsed.prevHash);
20452
+ if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
20453
+ reason = "hash_mismatch";
20454
+ else if (!linked(prevHash))
20455
+ reason = "broken_link";
20456
+ } else if (chainStarted)
20457
+ reason = "unsigned";
20458
+ else
20459
+ rowIntegrity = "legacy";
20460
+ if (options.integrity && !reason && ids.has(parsed.id))
20461
+ reason = "duplicate_id";
20462
+ if (options.integrity)
20463
+ ids.add(parsed.id);
20464
+ if (reason)
20465
+ rowIntegrity = "unverified";
20466
+ const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
20467
+ const row = {
20468
+ ...parsed,
20469
+ v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
20470
+ id: parsed.id,
20471
+ at: parsed.at,
20472
+ type: parsed.type,
20473
+ actor: normalizeActor(parsed.actor),
20474
+ branch: str2(parsed.branch),
20475
+ head: str2(parsed.head),
20476
+ base: str2(parsed.base),
20477
+ baseSha: str2(parsed.baseSha),
20478
+ patchId: str2(parsed.patchId),
20479
+ diffHash: str2(parsed.diffHash),
20480
+ tree: str2(parsed.tree),
20481
+ dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
20482
+ ...pr === undefined ? {} : { pr },
20483
+ ...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
20484
+ seq: rows.length + 1,
20485
+ superseded: false,
20486
+ supersedeIgnored: false,
20487
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
20488
+ ...reason ? { integrityReason: reason } : {}
20489
+ };
20490
+ rows.push(row);
20491
+ if (!options.integrity)
20492
+ continue;
20493
+ if (rowIntegrity === "verified")
20494
+ integrity.chained += 1;
20495
+ else if (rowIntegrity === "legacy")
20496
+ integrity.legacy += 1;
20497
+ else if (reason)
20498
+ integrity.unverified.push({
20499
+ seq: row.seq,
20500
+ id: row.id,
20501
+ type: row.type,
20502
+ branch: row.branch,
20503
+ reason
20504
+ });
20659
20505
  }
20660
- return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
20661
- }
20662
- function isRiskPath(file, extra = []) {
20663
- if (extra.some((pattern) => globMatch(pattern, file)))
20664
- return true;
20665
- if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
20666
- return true;
20667
- if (TEST_PATH.test(file) || isDoc(file))
20668
- return false;
20669
- 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)));
20670
- }
20671
- var parseRiskPaths = (text) => text.split(`
20672
- `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
20673
- function computeLane(input) {
20674
- const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
20675
- const risky = new Set(riskFiles);
20676
- const kinds = input.files.map((file) => ({
20677
- file,
20678
- kind: risky.has(file.path) ? "behavior" : kindOf(file)
20679
- }));
20680
- const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
20681
- const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
20682
- const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
20683
- const result = (lane, reasons) => ({
20684
- lane,
20685
- reasons,
20686
- lines,
20687
- files: behavior.length,
20688
- riskFiles
20689
- });
20690
- const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
20691
- if (input.riskTier === "high")
20692
- return result("full", ["the task judgment records high risk"]);
20693
- if (riskFiles.length)
20694
- return result("full", [
20695
- `touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
20696
- ]);
20697
- if (!behavior.length)
20698
- return result("none", [
20699
- input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
20700
- ]);
20701
- if (lines >= fullMinLines)
20702
- return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
20703
- if (lines > selfMaxLines)
20704
- return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
20705
- if (behavior.length > selfMaxFiles)
20706
- return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
20707
- if (opaque.length)
20708
- return result("focused", [
20709
- `a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
20710
- ]);
20711
- return result("self", [
20712
- `small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
20713
- ]);
20506
+ const scopes = new Map;
20507
+ const scopeOf = (branch) => {
20508
+ let scope = scopes.get(branch);
20509
+ if (!scope)
20510
+ scopes.set(branch, scope = branchScope(cwd, branch));
20511
+ return scope;
20512
+ };
20513
+ const byId = new Map;
20514
+ for (const row of rows) {
20515
+ const target = row.supersedes ? byId.get(row.supersedes) : undefined;
20516
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
20517
+ target.superseded = true;
20518
+ else if (row.supersedes)
20519
+ row.supersedeIgnored = true;
20520
+ if (!byId.has(row.id))
20521
+ byId.set(row.id, row);
20522
+ }
20523
+ if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
20524
+ tamperCache.set(file.value, {
20525
+ size: stat.size,
20526
+ stamp: fileStamp(stat),
20527
+ breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
20528
+ });
20529
+ return {
20530
+ ok: true,
20531
+ value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
20532
+ };
20714
20533
  }
20715
- function untrackedStat(cwd, file) {
20534
+ var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
20535
+ var tamperCache = new Map;
20536
+ var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
20537
+ function ledgerTampering(cwd) {
20538
+ const file = ledgerPath(cwd);
20539
+ if (!file.ok)
20540
+ return [];
20716
20541
  try {
20717
- const bytes = readFileSync4(path16.join(cwd, file));
20718
- if (bytes.includes(0))
20719
- return { path: file, added: 0, deleted: 0, binary: true };
20720
- const text = bytes.toString("utf8");
20721
- if (hasOpaqueLineBreak(text))
20722
- return { path: file, added: 0, deleted: 0, binary: true };
20723
- const lines = text.split(`
20724
- `).length - (text.endsWith(`
20725
- `) ? 1 : 0);
20726
- return { path: file, added: lines, deleted: 0 };
20542
+ const stat = fs12.statSync(file.value);
20543
+ const cached = tamperCache.get(file.value);
20544
+ if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
20545
+ return cached.breaks;
20727
20546
  } catch {
20728
- return { path: file, added: 0, deleted: 0, binary: true };
20547
+ return [];
20729
20548
  }
20549
+ const read = readLedger(cwd, { integrity: true });
20550
+ return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
20730
20551
  }
20731
- var REGULAR = new Set(["100644", "100755"]);
20732
- var ABSENT = /^0+$/u;
20733
- function cosmeticFiles(cwd, fork, head) {
20734
- const raw = diffRaw(cwd, fork, head);
20735
- if (!raw)
20736
- return null;
20737
- const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
20738
- const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
20739
- if (!blobs)
20740
- return null;
20741
- const out = new Set;
20742
- const opaque = new Set;
20743
- for (const entry of candidates) {
20744
- const before = blobs.get(entry.oldBlob);
20745
- let after = blobs.get(entry.newBlob);
20746
- if (!after && head === null && ABSENT.test(entry.newBlob))
20747
- try {
20748
- after = readFileSync4(path16.join(cwd, entry.path));
20749
- } catch {
20750
- after = undefined;
20751
- }
20752
- if (before && after && cosmeticChange(entry.path, before, after))
20753
- out.add(entry.path);
20754
- else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
20755
- opaque.add(entry.path);
20552
+ function tamperedUnblock(cwd) {
20553
+ const file = ledgerPath(cwd);
20554
+ const where = file.ok ? file.value : "the ledger file";
20555
+ 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`;
20556
+ }
20557
+ 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)}`;
20558
+ function verdictBasis(verdict, current) {
20559
+ if (verdict.head && verdict.head === current.head)
20560
+ return "fresh";
20561
+ if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
20562
+ return "carried";
20563
+ return "stale";
20564
+ }
20565
+ var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
20566
+ function trailerSessions(cwd, base, head) {
20567
+ if (!base || !head || !safeRef2(base) || !safeRef2(head))
20568
+ return [];
20569
+ const out = git4(cwd, [
20570
+ "log",
20571
+ "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
20572
+ `${base}..${head}`
20573
+ ]);
20574
+ return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
20575
+ }
20576
+ var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
20577
+ function branchScope(cwd, branch) {
20578
+ const renamedAt = new Map;
20579
+ let since = Number.NEGATIVE_INFINITY;
20580
+ if (safeRef2(branch)) {
20581
+ const log = git4(cwd, [
20582
+ "log",
20583
+ "-g",
20584
+ "--date=unix",
20585
+ "--format=%gd%x09%gs",
20586
+ `refs/heads/${branch}`,
20587
+ "--"
20588
+ ]);
20589
+ for (const line of (log ?? "").split(`
20590
+ `)) {
20591
+ const [selector = "", subject = ""] = line.split("\t");
20592
+ const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
20593
+ if (!Number.isFinite(seconds))
20594
+ continue;
20595
+ since = seconds * 1000;
20596
+ const match = RENAMED.exec(subject);
20597
+ if (!match || match[1] === branch)
20598
+ continue;
20599
+ const until = (seconds + 1) * 1000;
20600
+ renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
20601
+ }
20756
20602
  }
20757
- return { cosmetic: out, opaque };
20603
+ return (row) => {
20604
+ if (row.branch === branch)
20605
+ return true;
20606
+ const until = row.branch === null ? undefined : renamedAt.get(row.branch);
20607
+ if (until === undefined)
20608
+ return false;
20609
+ const at = Date.parse(row.at);
20610
+ return at >= since && at <= until;
20611
+ };
20758
20612
  }
20759
- function generatedAt(cwd, fork, paths) {
20760
- const files = treeFilesNamed(cwd, fork, ".gitattributes");
20761
- const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
20762
- if (!files || !blobs)
20763
- return new Set;
20764
- const texts = new Map;
20765
- for (const [file, id] of files) {
20766
- const bytes = blobs.get(id);
20767
- if (bytes)
20768
- texts.set(file, bytes.toString("utf8"));
20613
+ var renameAwareBranch = (scopeOf, branch) => {
20614
+ return (target) => {
20615
+ const own = typeof branch === "string" ? branch : null;
20616
+ const theirs = typeof target.branch === "string" ? target.branch : null;
20617
+ if (own === theirs)
20618
+ return true;
20619
+ if (own === null || theirs === null || typeof target.at !== "string")
20620
+ return false;
20621
+ return scopeOf(own)({ branch: theirs, at: target.at });
20622
+ };
20623
+ };
20624
+ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
20625
+ const sessions = new Set;
20626
+ for (const row of rows) {
20627
+ if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
20628
+ continue;
20629
+ const session = str2(row.session) ?? row.actor.session;
20630
+ if (session)
20631
+ sessions.add(session);
20769
20632
  }
20770
- return generatedByAttributes(texts, paths);
20633
+ for (const session of trailerSessions(cwd, range.base, range.head))
20634
+ sessions.add(session);
20635
+ return sessions;
20771
20636
  }
20772
- var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
20773
- function laneFiles(cwd, fork, head) {
20774
- const stats = diffNumstat(cwd, fork, head);
20775
- const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
20776
- if (!stats || !cosmetic)
20637
+ var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
20638
+ var authoredBy = (authors, session) => {
20639
+ if (authors.has(session))
20640
+ return true;
20641
+ for (const author of authors)
20642
+ if (derivesFrom(author, session) || derivesFrom(session, author))
20643
+ return true;
20644
+ return false;
20645
+ };
20646
+ var TRUST_REASONS = new Set([
20647
+ "unsigned",
20648
+ "hash_mismatch",
20649
+ "broken_link",
20650
+ "duplicate_id",
20651
+ "unissued_session",
20652
+ "unbound_session"
20653
+ ]);
20654
+ var SHELL_VOUCHED_HOSTS = new Set([
20655
+ "cli",
20656
+ "opencode",
20657
+ "pi",
20658
+ "codex_cli",
20659
+ "codex_desktop"
20660
+ ]);
20661
+ function sessionTrust(cwd, row) {
20662
+ const session = row.actor.session;
20663
+ if (!session || row.self === true)
20777
20664
  return null;
20778
- const files = [
20779
- ...stats.map((entry) => ({
20780
- ...entry,
20781
- ...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
20782
- cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
20783
- })),
20784
- ...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
20785
- ];
20786
- const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
20787
- return files.map((entry) => ({
20788
- ...entry,
20789
- generated: generated.has(entry.path)
20790
- }));
20665
+ const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
20666
+ const host = row.actor.host;
20667
+ const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
20668
+ if (!issuedOnly)
20669
+ return null;
20670
+ const binding = issuedBinding(cwd, session);
20671
+ if (binding === null)
20672
+ return "unissued_session";
20673
+ return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
20674
+ }
20675
+ function trustReasons(cwd, row) {
20676
+ if (row.integrity === "unverified")
20677
+ return [row.integrityReason ?? "unsigned"];
20678
+ if (row.integrity !== "verified")
20679
+ return ["unsigned"];
20680
+ const session = sessionTrust(cwd, row);
20681
+ return session ? [session] : [];
20682
+ }
20683
+ function withIntegrity(cwd, rows) {
20684
+ if (rows.every((row) => row.integrity !== undefined))
20685
+ return rows;
20686
+ const read = readLedger(cwd, { integrity: true });
20687
+ const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
20688
+ return rows.map((row) => {
20689
+ if (row.integrity !== undefined)
20690
+ return row;
20691
+ const fresh = bySeq.get(row.seq);
20692
+ return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
20693
+ });
20694
+ }
20695
+ var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
20696
+ var independenceReasons = (row, authors) => {
20697
+ const reasons = [];
20698
+ if (!row.actor.session)
20699
+ reasons.push("no_session");
20700
+ else if (authoredBy(authors, row.actor.session))
20701
+ reasons.push("author_session");
20702
+ if (row.self === true && !reasons.includes("no_session"))
20703
+ reasons.push("self");
20704
+ return reasons;
20705
+ };
20706
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
20707
+ let effective = null;
20708
+ for (const row of rows) {
20709
+ if (!effective) {
20710
+ effective = row;
20711
+ continue;
20712
+ }
20713
+ if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
20714
+ continue;
20715
+ const rowIndependent = reasonsOf(row).length === 0;
20716
+ const effIndependent = reasonsOf(effective).length === 0;
20717
+ if (effIndependent && !rowIndependent && isCurrent(effective))
20718
+ continue;
20719
+ if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
20720
+ continue;
20721
+ effective = row;
20722
+ }
20723
+ return effective;
20724
+ }
20725
+ var restackExact = new WeakMap;
20726
+ function restackCarries(exactIn, rows, branch, from, to) {
20727
+ if (!from || !to)
20728
+ return false;
20729
+ const exact = (row, fromHead, head) => {
20730
+ if (exactIn === null)
20731
+ return true;
20732
+ const cwd = exactIn;
20733
+ let known = restackExact.get(row);
20734
+ if (known === undefined) {
20735
+ known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
20736
+ from: fromHead,
20737
+ to: head,
20738
+ fromBase: row.fromBase,
20739
+ toBase: row.toBase
20740
+ });
20741
+ restackExact.set(row, known);
20742
+ }
20743
+ return known;
20744
+ };
20745
+ const edges = new Map;
20746
+ for (const row of rows)
20747
+ 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))
20748
+ edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
20749
+ const seen = new Set([from]);
20750
+ const queue = [from];
20751
+ while (queue.length) {
20752
+ const next = queue.shift();
20753
+ if (next === to)
20754
+ return true;
20755
+ for (const target of edges.get(next) ?? [])
20756
+ if (!seen.has(target)) {
20757
+ seen.add(target);
20758
+ queue.push(target);
20759
+ }
20760
+ }
20761
+ return false;
20791
20762
  }
20792
- function branchLane(cwd, options) {
20793
- const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
20794
- const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
20795
- const head = options.workingTree ? null : options.head;
20796
- const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
20797
- const files = fork ? laneFiles(cwd, fork, head) : null;
20798
- const riskTier = options.judgment?.riskTier ?? null;
20799
- if (!fork || !files) {
20800
- const high = riskTier === "high";
20801
- return {
20802
- lane: high ? "full" : "focused",
20803
- reasons: [
20804
- `the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
20805
- ...high ? ["the task judgment records high risk"] : []
20806
- ],
20807
- lines: 0,
20808
- files: 0,
20809
- riskFiles: [],
20810
- base,
20811
- head: options.head
20812
- };
20763
+ function checkVerdicts(cwd, branch, ledgerRows) {
20764
+ const rows = withIntegrity(cwd, ledgerRows);
20765
+ const head = branchHead(cwd, branch);
20766
+ const fallbackBase = defaultBase(cwd);
20767
+ const scope = branchScope(cwd, branch);
20768
+ const keyCache = new Map;
20769
+ const keysAt = (base, at) => {
20770
+ if (!base || !at)
20771
+ return { patchId: null, diffHash: null };
20772
+ const cacheKey = `${base}\x00${at}`;
20773
+ let key = keyCache.get(cacheKey);
20774
+ if (!key) {
20775
+ key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
20776
+ keyCache.set(cacheKey, key);
20777
+ }
20778
+ return key;
20779
+ };
20780
+ const keyFor = (base) => keysAt(base, head);
20781
+ const driftCache = new Map;
20782
+ const driftOf = (row) => {
20783
+ if (driftCache.has(row))
20784
+ return driftCache.get(row) ?? null;
20785
+ let drift = null;
20786
+ const toBase = row.base ?? fallbackBase;
20787
+ const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
20788
+ const fromBase = row.baseSha ?? toBaseSha;
20789
+ if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
20790
+ drift = verdictDrift(cwd, {
20791
+ from: row.head,
20792
+ to: head,
20793
+ fromBase,
20794
+ toBase: toBaseSha,
20795
+ kind: typeof row.kind === "string" ? row.kind : "review"
20796
+ });
20797
+ driftCache.set(row, drift);
20798
+ return drift;
20799
+ };
20800
+ const exactCache = new Map;
20801
+ const exactCarry = (row) => {
20802
+ if (FAILING.has(String(row.result)))
20803
+ return true;
20804
+ let exact = exactCache.get(row);
20805
+ if (exact === undefined) {
20806
+ const toBase = row.base ?? fallbackBase;
20807
+ const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
20808
+ const fromBase = row.baseSha ?? toBaseSha;
20809
+ exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
20810
+ exactCache.set(row, exact);
20811
+ }
20812
+ return exact;
20813
+ };
20814
+ const keyBasis = (row) => {
20815
+ const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
20816
+ return basis === "carried" && !exactCarry(row) ? "stale" : basis;
20817
+ };
20818
+ const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
20819
+ const carryOf = (row) => {
20820
+ if (keyBasis(row) !== "stale")
20821
+ return null;
20822
+ if (restacked(row))
20823
+ return null;
20824
+ const drift = driftOf(row);
20825
+ return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
20826
+ };
20827
+ const basisOf = (row) => {
20828
+ const basis = keyBasis(row);
20829
+ if (basis !== "stale")
20830
+ return basis;
20831
+ return restacked(row) || carryOf(row) ? "carried" : basis;
20832
+ };
20833
+ const sameCode = (failed, row) => {
20834
+ if (failed.head && failed.head === row.head)
20835
+ return true;
20836
+ const base = failed.base ?? fallbackBase;
20837
+ const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
20838
+ const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
20839
+ return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
20840
+ };
20841
+ const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
20842
+ const failureAuthorCache = new Map;
20843
+ const failureAuthors = (row) => {
20844
+ let set = failureAuthorCache.get(row.seq);
20845
+ if (!set) {
20846
+ set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
20847
+ failureAuthorCache.set(row.seq, set);
20848
+ }
20849
+ return set;
20850
+ };
20851
+ const reasonCache = new Map;
20852
+ const reasonsOf = (row) => {
20853
+ let reasons = reasonCache.get(row);
20854
+ if (!reasons) {
20855
+ const failing = FAILING.has(String(row.result));
20856
+ reasons = [
20857
+ ...trustReasons(cwd, row),
20858
+ ...independenceReasons(row, failing ? failureAuthors(row) : authors)
20859
+ ];
20860
+ reasonCache.set(row, reasons);
20861
+ }
20862
+ return reasons;
20863
+ };
20864
+ const retired = new Set;
20865
+ for (const row of rows)
20866
+ if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
20867
+ retired.add(row.supersedes);
20868
+ const byKind = new Map;
20869
+ for (const row of rows)
20870
+ if (isLiveVerdict(row, retired) && scope(row)) {
20871
+ const kind = typeof row.kind === "string" ? row.kind : "review";
20872
+ byKind.set(kind, [...byKind.get(kind) ?? [], row]);
20873
+ }
20874
+ const verdicts = [];
20875
+ const warnings = [];
20876
+ for (const [kind, list] of byKind) {
20877
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
20878
+ if (!row)
20879
+ continue;
20880
+ const basis = basisOf(row);
20881
+ const current = basis !== "stale";
20882
+ const independence = reasonsOf(row);
20883
+ const reasons = [
20884
+ ...current ? [] : ["stale"],
20885
+ ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
20886
+ ...independence
20887
+ ];
20888
+ verdicts.push({
20889
+ kind,
20890
+ basis,
20891
+ current,
20892
+ independent: independence.length === 0,
20893
+ accepted: reasons.length === 0,
20894
+ reasons,
20895
+ carry: basis === "carried" ? carryOf(row) : null,
20896
+ verdict: row
20897
+ });
20898
+ if (reasons.length === 0) {
20899
+ for (const later of list)
20900
+ if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
20901
+ 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`);
20902
+ }
20813
20903
  }
20814
- const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
20904
+ for (const row of rows)
20905
+ if (row.integrity === "unverified" && scope(row))
20906
+ 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)`);
20907
+ const tampered = ledgerTampering(cwd);
20908
+ if (tampered.length) {
20909
+ warnings.push(tamperedWarning(cwd, tampered));
20910
+ for (const entry of verdicts) {
20911
+ entry.accepted = false;
20912
+ entry.reasons = [...entry.reasons, "ledger_tampered"];
20913
+ }
20914
+ }
20915
+ verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
20916
+ const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
20917
+ const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
20918
+ const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
20919
+ const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
20920
+ accepted: false,
20921
+ verdict: failing?.verdict ?? null,
20922
+ reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
20923
+ };
20924
+ 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))));
20925
+ const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
20926
+ const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
20927
+ const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
20928
+ const drift = lastPass ? driftOf(lastPass.verdict) : null;
20929
+ const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
20815
20930
  return {
20816
- ...computeLane({
20817
- files,
20818
- riskTier,
20819
- riskPaths: [...repoRisk, ...options.riskPaths ?? []]
20820
- }),
20821
- base,
20822
- head: options.head
20931
+ branch,
20932
+ head,
20933
+ review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
20934
+ selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
20935
+ base: newestCurrent?.verdict.base ?? fallbackBase,
20936
+ patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
20937
+ current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
20938
+ carry: carryEntry?.carry ?? null,
20939
+ reverify,
20940
+ accepted,
20941
+ verdicts,
20942
+ authors: [...authors].toSorted(),
20943
+ warnings
20823
20944
  };
20824
20945
  }
20825
- var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
20946
+ var OBSERVED_ONLY = new Set([
20947
+ "check",
20948
+ "merge.unverified",
20949
+ "policy.judged",
20950
+ ...PR_ROW_TYPES
20951
+ ]);
20826
20952
 
20827
20953
  // packages/workit-core/src/core/policy/derive.ts
20828
20954
  var RULES = {
@@ -20977,7 +21103,7 @@ var productDecision = (task, ledger) => {
20977
21103
  };
20978
21104
  };
20979
21105
  var planRecorded = (task, root) => {
20980
- const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path17.resolve(root, ref.path)));
21106
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path16.resolve(root, ref.path)));
20981
21107
  if (cited)
20982
21108
  return {
20983
21109
  met: true,
@@ -21044,14 +21170,14 @@ function derivedRuleStatus(task, requirement, root, ledger) {
21044
21170
 
21045
21171
  // packages/workit-core/src/core/task-evaluation.ts
21046
21172
  var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
21047
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path18.sep}`);
21048
- var relative = (root, candidate) => path18.relative(root, candidate).split(path18.sep).join("/") || ".";
21173
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path17.sep}`);
21174
+ var relative = (root, candidate) => path17.relative(root, candidate).split(path17.sep).join("/") || ".";
21049
21175
  var canonicalPath = (value) => {
21050
21176
  if (typeof value !== "string" || !value)
21051
21177
  return null;
21052
21178
  if (value.includes("\\") || value.split("/").includes(".."))
21053
21179
  return null;
21054
- const normalized = path18.posix.normalize(value);
21180
+ const normalized = path17.posix.normalize(value);
21055
21181
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
21056
21182
  return null;
21057
21183
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -21087,7 +21213,7 @@ var compareCodeUnits2 = (left, right) => {
21087
21213
  return left.length - right.length;
21088
21214
  };
21089
21215
  var gitPaths = (root) => {
21090
- const result = spawnSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
21216
+ const result = spawnSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
21091
21217
  cwd: root,
21092
21218
  encoding: "buffer"
21093
21219
  });
@@ -21099,21 +21225,21 @@ var gitPaths = (root) => {
21099
21225
  git: !/not a git repository/i.test(stderr)
21100
21226
  };
21101
21227
  }
21102
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")) : [];
21103
- const stagedDeleted = spawnSync11("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
21228
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")) : [];
21229
+ const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
21104
21230
  if (stagedDeleted.status !== 0) {
21105
21231
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
21106
21232
  if (!/not a git repository/i.test(stderr))
21107
21233
  return { paths, uncertain: true, git: true };
21108
21234
  } else if (stagedDeleted.stdout) {
21109
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")));
21235
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")));
21110
21236
  }
21111
21237
  return { paths: [...new Set(paths)], uncertain: false, git: true };
21112
21238
  };
21113
21239
  var walk = (root, directory, output) => {
21114
21240
  let names;
21115
21241
  try {
21116
- names = fs14.readdirSync(directory);
21242
+ names = fs13.readdirSync(directory);
21117
21243
  } catch {
21118
21244
  return true;
21119
21245
  }
@@ -21121,10 +21247,10 @@ var walk = (root, directory, output) => {
21121
21247
  for (const name of names) {
21122
21248
  if (name === ".git" || name === ".workit")
21123
21249
  continue;
21124
- const target = path18.join(directory, name);
21250
+ const target = path17.join(directory, name);
21125
21251
  let stat;
21126
21252
  try {
21127
- stat = fs14.lstatSync(target);
21253
+ stat = fs13.lstatSync(target);
21128
21254
  } catch {
21129
21255
  uncertain = true;
21130
21256
  continue;
@@ -21139,14 +21265,14 @@ var walk = (root, directory, output) => {
21139
21265
  var scopeRoots = (root, scope) => {
21140
21266
  const roots = [];
21141
21267
  for (const item of scope.paths.length ? scope.paths : ["."]) {
21142
- const target = path18.resolve(root, item);
21268
+ const target = path17.resolve(root, item);
21143
21269
  if (!inside(root, target))
21144
21270
  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` }] });
21145
21271
  let ancestor = target;
21146
- while (!fs14.existsSync(ancestor) && ancestor !== root)
21147
- ancestor = path18.dirname(ancestor);
21272
+ while (!fs13.existsSync(ancestor) && ancestor !== root)
21273
+ ancestor = path17.dirname(ancestor);
21148
21274
  try {
21149
- if (!inside(root, fs14.realpathSync(ancestor)))
21275
+ if (!inside(root, fs13.realpathSync(ancestor)))
21150
21276
  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` }] });
21151
21277
  } catch {
21152
21278
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -21158,7 +21284,7 @@ var scopeRoots = (root, scope) => {
21158
21284
  function captureCandidate(root, scope, environment = []) {
21159
21285
  let checkout;
21160
21286
  try {
21161
- checkout = fs14.realpathSync(root);
21287
+ checkout = fs13.realpathSync(root);
21162
21288
  } catch {
21163
21289
  return failure2("invalid_input", "candidate root does not exist");
21164
21290
  }
@@ -21176,11 +21302,11 @@ function captureCandidate(root, scope, environment = []) {
21176
21302
  if (item === ".git" || item === ".workit")
21177
21303
  continue;
21178
21304
  names.add(item);
21179
- if (!fs14.existsSync(target))
21305
+ if (!fs13.existsSync(target))
21180
21306
  continue;
21181
21307
  let stat;
21182
21308
  try {
21183
- stat = fs14.lstatSync(target);
21309
+ stat = fs13.lstatSync(target);
21184
21310
  } catch {
21185
21311
  uncertain = true;
21186
21312
  continue;
@@ -21196,17 +21322,17 @@ function captureCandidate(root, scope, environment = []) {
21196
21322
  for (const item of [...names].toSorted()) {
21197
21323
  if (!scopeMatches(item, normalizedScope))
21198
21324
  continue;
21199
- const target = path18.join(checkout, item);
21325
+ const target = path17.join(checkout, item);
21200
21326
  try {
21201
- const stat = fs14.lstatSync(target);
21327
+ const stat = fs13.lstatSync(target);
21202
21328
  if (stat.isSymbolicLink()) {
21203
- const link = fs14.readlinkSync(target);
21329
+ const link = fs13.readlinkSync(target);
21204
21330
  files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
21205
21331
  } else if (stat.isFile()) {
21206
21332
  files.push({
21207
21333
  path: item,
21208
21334
  kind: "file",
21209
- digest: digestBytes(fs14.readFileSync(target)),
21335
+ digest: digestBytes(fs13.readFileSync(target)),
21210
21336
  executable: (stat.mode & 73) !== 0
21211
21337
  });
21212
21338
  } else if (!stat.isDirectory()) {
@@ -21231,7 +21357,7 @@ function captureCandidate(root, scope, environment = []) {
21231
21357
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
21232
21358
  if (values.some(({ value }) => value === null))
21233
21359
  completeness = "uncertain";
21234
- const headResult = spawnSync11("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
21360
+ const headResult = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
21235
21361
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
21236
21362
  completeness = "uncertain";
21237
21363
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -21371,7 +21497,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
21371
21497
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21372
21498
  let root;
21373
21499
  try {
21374
- root = fs14.realpathSync(checkoutRoot);
21500
+ root = fs13.realpathSync(checkoutRoot);
21375
21501
  } catch {
21376
21502
  return failure2("permission_denied", "checkout root is unavailable");
21377
21503
  }
@@ -21380,23 +21506,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21380
21506
  continue;
21381
21507
  if (!reference.digest)
21382
21508
  return failure2("invalid_input", "document references require a byte digest");
21383
- const target = path18.resolve(root, reference.path);
21384
- if (target !== root && !target.startsWith(`${root}${path18.sep}`))
21509
+ const target = path17.resolve(root, reference.path);
21510
+ if (target !== root && !target.startsWith(`${root}${path17.sep}`))
21385
21511
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
21386
21512
  fields: [
21387
21513
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
21388
21514
  ]
21389
21515
  });
21390
21516
  try {
21391
- const below = path18.relative(root, target);
21517
+ const below = path17.relative(root, target);
21392
21518
  let current = root;
21393
- for (const segment of below.split(path18.sep)) {
21519
+ for (const segment of below.split(path17.sep)) {
21394
21520
  if (!segment)
21395
21521
  continue;
21396
- current = path18.join(current, segment);
21397
- const stat = fs14.lstatSync(current);
21398
- const real = fs14.realpathSync(current);
21399
- if (real !== root && !real.startsWith(`${root}${path18.sep}`))
21522
+ current = path17.join(current, segment);
21523
+ const stat = fs13.lstatSync(current);
21524
+ const real = fs13.realpathSync(current);
21525
+ if (real !== root && !real.startsWith(`${root}${path17.sep}`))
21400
21526
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
21401
21527
  fields: [
21402
21528
  {
@@ -21412,7 +21538,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21412
21538
  if (current !== target && !stat.isDirectory())
21413
21539
  return failure2("invalid_input", "document reference ancestor is not a directory");
21414
21540
  }
21415
- if (digestBytes(fs14.readFileSync(target)) !== reference.digest)
21541
+ if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
21416
21542
  return failure2("permission_denied", "decision document bytes have changed");
21417
21543
  } catch {
21418
21544
  return failure2("invalid_input", "decision document is unavailable");
@@ -22255,7 +22381,7 @@ var judgeInputSummary = (raw) => {
22255
22381
 
22256
22382
  // packages/workit-core/src/autonomy.ts
22257
22383
  import os4 from "node:os";
22258
- import path19 from "node:path";
22384
+ import path18 from "node:path";
22259
22385
  var accountHome = () => {
22260
22386
  try {
22261
22387
  return os4.userInfo().homedir;
@@ -22264,10 +22390,10 @@ var accountHome = () => {
22264
22390
  }
22265
22391
  };
22266
22392
  var grantsHome = { resolve: accountHome };
22267
- var grantsDir = () => path19.join(grantsHome.resolve(), ".config", "workit");
22393
+ var grantsDir = () => path18.join(grantsHome.resolve(), ".config", "workit");
22268
22394
  var grantsOverride = () => {
22269
- const configured = path19.resolve(resolveConfigDir());
22270
- 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`;
22395
+ const configured = path18.resolve(resolveConfigDir());
22396
+ 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`;
22271
22397
  };
22272
22398
  var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
22273
22399
  var DEFAULT_GRANTS = Object.freeze({
@@ -24076,9 +24202,9 @@ class WorkitCore {
24076
24202
  }
24077
24203
  }
24078
24204
  // packages/workit-core/src/core/repo-context.ts
24079
- import { spawnSync as spawnSync12 } from "node:child_process";
24205
+ import { spawnSync as spawnSync11 } from "node:child_process";
24080
24206
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
24081
- import path20 from "node:path";
24207
+ import path19 from "node:path";
24082
24208
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
24083
24209
  try {
24084
24210
  gitRevisionParts(value);
@@ -24105,7 +24231,7 @@ var invalidRange = (cwd) => ({
24105
24231
  cwd
24106
24232
  });
24107
24233
  var runGit = (cwd, args) => {
24108
- const result = spawnSync12("git", args, { cwd, encoding: "utf8" });
24234
+ const result = spawnSync11("git", args, { cwd, encoding: "utf8" });
24109
24235
  return {
24110
24236
  stdout: (result.stdout ?? "").trimEnd(),
24111
24237
  stderr: (result.stderr ?? "").trimEnd(),
@@ -24212,7 +24338,7 @@ var findPrTemplate = (cwd) => {
24212
24338
  ]) {
24213
24339
  const parts = rel.split("/");
24214
24340
  const base = parts[parts.length - 1];
24215
- const dir = parts.length > 1 ? path20.join(cwd, ...parts.slice(0, -1)) : cwd;
24341
+ const dir = parts.length > 1 ? path19.join(cwd, ...parts.slice(0, -1)) : cwd;
24216
24342
  const actual = probeCaseInsensitive(dir, base);
24217
24343
  if (actual)
24218
24344
  return [...parts.slice(0, -1), actual].join("/");
@@ -24243,7 +24369,7 @@ var diffStatForRange = (cwd, range) => {
24243
24369
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
24244
24370
  };
24245
24371
  var packageScripts = (cwd, keys) => {
24246
- const pkgPath = path20.join(cwd, "package.json");
24372
+ const pkgPath = path19.join(cwd, "package.json");
24247
24373
  if (!existsSync7(pkgPath))
24248
24374
  return [];
24249
24375
  try {
@@ -24269,9 +24395,9 @@ var documentationFiles = (cwd) => {
24269
24395
  if (entry.isDirectory()) {
24270
24396
  if (excluded.has(entry.name))
24271
24397
  continue;
24272
- walk(path20.join(dir, entry.name), depth + 1);
24398
+ walk(path19.join(dir, entry.name), depth + 1);
24273
24399
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
24274
- matches.push("./" + path20.relative(cwd, path20.join(dir, entry.name)).replaceAll("\\", "/"));
24400
+ matches.push("./" + path19.relative(cwd, path19.join(dir, entry.name)).replaceAll("\\", "/"));
24275
24401
  }
24276
24402
  }
24277
24403
  };
@@ -24287,7 +24413,7 @@ var readTrimmed2 = (file, maxLines) => {
24287
24413
  `);
24288
24414
  };
24289
24415
  function prReadyContext(root, range) {
24290
- const cwd = path20.resolve(repoRoot(root));
24416
+ const cwd = path19.resolve(repoRoot(root));
24291
24417
  if (range !== undefined && !isResolvableContextRange(cwd, range))
24292
24418
  return invalidRange(cwd);
24293
24419
  let stdout = "";
@@ -24345,7 +24471,7 @@ ${status.stderr}` : "");
24345
24471
  stdout += `template_path: ${template}
24346
24472
 
24347
24473
  `;
24348
- stdout += readTrimmed2(path20.join(cwd, template), 220);
24474
+ stdout += readTrimmed2(path19.join(cwd, template), 220);
24349
24475
  } else {
24350
24476
  stdout += `template_path: none
24351
24477
 
@@ -24355,7 +24481,7 @@ ${status.stderr}` : "");
24355
24481
  stdout += `
24356
24482
  `;
24357
24483
  stdout += printSection("Recent Validation Signals");
24358
- if (existsSync7(path20.join(cwd, "package.json"))) {
24484
+ if (existsSync7(path19.join(cwd, "package.json"))) {
24359
24485
  stdout += `package.json detected. Common scripts:
24360
24486
  `;
24361
24487
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -24364,7 +24490,7 @@ ${status.stderr}` : "");
24364
24490
  `) + `
24365
24491
  `;
24366
24492
  }
24367
- if (existsSync7(path20.join(cwd, "Cargo.toml")) || existsSync7(path20.join(cwd, "src-tauri/Cargo.toml"))) {
24493
+ if (existsSync7(path19.join(cwd, "Cargo.toml")) || existsSync7(path19.join(cwd, "src-tauri/Cargo.toml"))) {
24368
24494
  stdout += `Rust project detected.
24369
24495
  `;
24370
24496
  }
@@ -24397,7 +24523,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
24397
24523
  - Apply with the host-native editor, preserving the repository changelog conventions.
24398
24524
  - If Unreleased already has duplicate category headings, normalize_only first.`;
24399
24525
  function changelogContext(root, range) {
24400
- const cwd = path20.resolve(repoRoot(root));
24526
+ const cwd = path19.resolve(repoRoot(root));
24401
24527
  if (range !== undefined && !isResolvableContextRange(cwd, range))
24402
24528
  return invalidRange(cwd);
24403
24529
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -24415,7 +24541,7 @@ function changelogContext(root, range) {
24415
24541
  stdout += CHANGELOG_RULES + `
24416
24542
  `;
24417
24543
  stdout += printSection("Existing CHANGELOG.md");
24418
- const changelogPath = path20.join(cwd, "CHANGELOG.md");
24544
+ const changelogPath = path19.join(cwd, "CHANGELOG.md");
24419
24545
  if (existsSync7(changelogPath)) {
24420
24546
  stdout += readTrimmed2(changelogPath, 260) + `
24421
24547
  `;
@@ -24435,7 +24561,7 @@ function changelogContext(root, range) {
24435
24561
  return { stdout, stderr: "", exitCode: 0, cwd };
24436
24562
  }
24437
24563
  function docsRefreshContext(root, range) {
24438
- const cwd = path20.resolve(repoRoot(root));
24564
+ const cwd = path19.resolve(repoRoot(root));
24439
24565
  if (range !== undefined && !isResolvableContextRange(cwd, range))
24440
24566
  return invalidRange(cwd);
24441
24567
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -24457,7 +24583,7 @@ function docsRefreshContext(root, range) {
24457
24583
  `) + `
24458
24584
  `;
24459
24585
  stdout += printSection("README Preview");
24460
- const readme = path20.join(cwd, "README.md");
24586
+ const readme = path19.join(cwd, "README.md");
24461
24587
  if (existsSync7(readme)) {
24462
24588
  stdout += readTrimmed2(readme, 220) + `
24463
24589
  `;
@@ -24466,7 +24592,7 @@ function docsRefreshContext(root, range) {
24466
24592
  `;
24467
24593
  }
24468
24594
  stdout += printSection("Package Scripts");
24469
- const pkgPath = path20.join(cwd, "package.json");
24595
+ const pkgPath = path19.join(cwd, "package.json");
24470
24596
  if (existsSync7(pkgPath)) {
24471
24597
  try {
24472
24598
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
@@ -24480,7 +24606,7 @@ function docsRefreshContext(root, range) {
24480
24606
  return { stdout, stderr: "", exitCode: 0, cwd };
24481
24607
  }
24482
24608
  function releaseNotesContext(root, rangeOrTag) {
24483
- const cwd = path20.resolve(repoRoot(root));
24609
+ const cwd = path19.resolve(repoRoot(root));
24484
24610
  if (!rangeOrTag) {
24485
24611
  return { stdout: "", stderr: `ERROR: release tag or range required
24486
24612
  `, exitCode: 1, cwd };
@@ -24518,7 +24644,7 @@ function releaseNotesContext(root, rangeOrTag) {
24518
24644
  `;
24519
24645
  stdout += printSection("Existing Release Files");
24520
24646
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
24521
- if (existsSync7(path20.join(cwd, rel)))
24647
+ if (existsSync7(path19.join(cwd, rel)))
24522
24648
  stdout += rel + `
24523
24649
  `;
24524
24650
  }
@@ -24547,6 +24673,46 @@ function releaseNotesContext(root, rangeOrTag) {
24547
24673
 
24548
24674
  // packages/workit-core/src/core/tracker-issues.ts
24549
24675
  import { spawnSync as spawnSync13 } from "node:child_process";
24676
+
24677
+ // packages/workit-core/src/core/pr-create.ts
24678
+ import fs14 from "node:fs";
24679
+ import path20 from "node:path";
24680
+ import { spawnSync as spawnSync12 } from "node:child_process";
24681
+ // packages/workit-core/src/forge/pr-body.ts
24682
+ function parseGhIssue(value) {
24683
+ const m = /issues\/(\d+)/.exec(value);
24684
+ return m ? m[1] : String(value).trim().replace(/^#/, "");
24685
+ }
24686
+
24687
+ // packages/workit-core/src/core/pr-create.ts
24688
+ function whichOnPath(tool) {
24689
+ const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
24690
+ for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
24691
+ if (!dir)
24692
+ continue;
24693
+ for (const name of names) {
24694
+ const candidate = path20.join(dir, name);
24695
+ try {
24696
+ fs14.accessSync(candidate, fs14.constants.X_OK);
24697
+ return candidate;
24698
+ } catch {}
24699
+ }
24700
+ }
24701
+ return null;
24702
+ }
24703
+ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
24704
+ var pushRemote = (cwd) => {
24705
+ const result = spawnSync12("git", ["remote", "get-url", "--push", "--all", "origin"], {
24706
+ cwd,
24707
+ encoding: "utf8"
24708
+ });
24709
+ if (result.status !== 0)
24710
+ return null;
24711
+ const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
24712
+ return urls.length === 1 ? urls[0] : null;
24713
+ };
24714
+
24715
+ // packages/workit-core/src/core/tracker-issues.ts
24550
24716
  var repoPathFromRemote = (remote) => {
24551
24717
  let rest = (remote || "").trim().replace(/\/+$/, "");
24552
24718
  if (!rest)
@@ -27810,6 +27976,25 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
27810
27976
  var signals = new Map;
27811
27977
  var cache = new Map;
27812
27978
  var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
27979
+ // packages/workit-core/src/hooks/delivery-verbs.ts
27980
+ var WORKIT_ALLOWED_VERBS = [
27981
+ "git",
27982
+ "pr",
27983
+ "ci",
27984
+ "stack",
27985
+ "release",
27986
+ "task",
27987
+ "ledger",
27988
+ "evidence",
27989
+ "finding",
27990
+ "decision",
27991
+ "worker",
27992
+ "verify-delivery",
27993
+ "policy",
27994
+ "doctor",
27995
+ "help"
27996
+ ];
27997
+ var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
27813
27998
  // packages/workit-core/src/core/heavy.ts
27814
27999
  var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
27815
28000
  var sub = (...names) => new Set(names);
@@ -28018,6 +28203,10 @@ var WRITE_TOOLS = new Set([
28018
28203
  // packages/workit-core/src/hooks/standing.ts
28019
28204
  var sinceStart = new Map;
28020
28205
  // packages/workit-core/src/hooks/cwd-normalize.ts
28206
+ var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
28207
+ var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
28208
+ var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
28209
+ var VERBS = new Set(WORKIT_ALLOWED_VERBS);
28021
28210
  var FILTERS = new Set(["tail", "head", "grep"]);
28022
28211
 
28023
28212
  // packages/workit-core/src/hooks/worktree-confine.ts
@@ -28186,6 +28375,92 @@ var codexCapabilities = (host, availability = {}) => capabilitiesFor(codexDescri
28186
28375
  if (false)
28187
28376
  ;
28188
28377
 
28378
+ // packages/workit-core/src/core/role-models.ts
28379
+ var MODEL_ROLES = [
28380
+ "decider",
28381
+ "implementer",
28382
+ "verifier",
28383
+ "reviewer",
28384
+ "explore",
28385
+ "babysit"
28386
+ ];
28387
+ var MODEL_TIERS = ["mundane", "standard", "hard"];
28388
+ var HOST_KEYS = new Set([
28389
+ "claude_code",
28390
+ "opencode",
28391
+ "codex",
28392
+ "codex_cli",
28393
+ "codex_desktop",
28394
+ "cursor",
28395
+ "pi",
28396
+ "default"
28397
+ ]);
28398
+ var BUILT_IN_CLAUDE_CODE = {
28399
+ decider: { model: "fable", effort: "high" },
28400
+ "implementer.hard": { model: "opus" },
28401
+ "implementer.standard": { model: "sonnet" },
28402
+ "implementer.mundane": { model: "haiku" },
28403
+ verifier: { model: "sonnet" },
28404
+ reviewer: { model: "opus" },
28405
+ explore: { model: "haiku" },
28406
+ babysit: { model: "haiku" }
28407
+ };
28408
+ var isObject7 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
28409
+ var name = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
28410
+ var leaf = (value) => {
28411
+ const text = name(value);
28412
+ if (text)
28413
+ return { model: text, effort: null };
28414
+ if (!isObject7(value))
28415
+ return null;
28416
+ const model = name(value.model);
28417
+ return model ? { model, effort: name(value.effort) } : null;
28418
+ };
28419
+ var isFlat = (value) => typeof value === "string" || isObject7(value) && Object.hasOwn(value, "model");
28420
+ var forHost = (value, host) => {
28421
+ if (isFlat(value))
28422
+ return leaf(value);
28423
+ if (!isObject7(value))
28424
+ return null;
28425
+ const family = host ? hostFamily(host) : null;
28426
+ for (const key of [family, host, "default"])
28427
+ if (key && Object.hasOwn(value, key)) {
28428
+ const found = leaf(value[key]);
28429
+ if (found)
28430
+ return found;
28431
+ }
28432
+ return null;
28433
+ };
28434
+ var rolesOf = (models) => isObject7(models) && isObject7(models.roles) ? models.roles : null;
28435
+ var resolveRoleModel = (cwd, selector, host, dir) => {
28436
+ const { role, tier } = selector;
28437
+ for (const source of modelSources(cwd, dir)) {
28438
+ const where = source.global ? "the global default" : `workspace ${source.name}`;
28439
+ const roles = rolesOf(source.models);
28440
+ const configured = roles && Object.hasOwn(roles, role) ? roles[role] : undefined;
28441
+ const fromRoles = role === "implementer" ? tier && isObject7(configured) && Object.hasOwn(configured, tier) ? forHost(configured[tier], host) : null : forHost(configured, host);
28442
+ if (fromRoles)
28443
+ return { ...fromRoles, source: `models.roles in ${where}` };
28444
+ if (role === "implementer" && tier && isObject7(source.tierModels)) {
28445
+ const fromTiers = Object.hasOwn(source.tierModels, tier) ? forHost(source.tierModels[tier], host) : null;
28446
+ if (fromTiers)
28447
+ return { model: fromTiers.model, effort: null, source: `fanout.tierModels in ${where}` };
28448
+ }
28449
+ }
28450
+ if (host && hostFamily(host) === "claude_code") {
28451
+ const builtIn = BUILT_IN_CLAUDE_CODE[tier ? `${role}.${tier}` : role];
28452
+ if (builtIn)
28453
+ return { model: builtIn.model, effort: builtIn.effort ?? null, source: "built-in" };
28454
+ }
28455
+ return null;
28456
+ };
28457
+ var uniformImplementerModel = (cwd, host, dir) => {
28458
+ const tiers = MODEL_TIERS.map((tier) => resolveRoleModel(cwd, { role: "implementer", tier }, host, dir));
28459
+ const [first] = tiers;
28460
+ return first && tiers.every((t) => t?.model === first.model && t.effort === first.effort) ? first : null;
28461
+ };
28462
+ var ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
28463
+
28189
28464
  // packages/workit-codex/scripts/agents.ts
28190
28465
  import {
28191
28466
  existsSync as existsSync10,
@@ -28199,13 +28474,26 @@ import {
28199
28474
  } from "node:fs";
28200
28475
  import os5 from "node:os";
28201
28476
  import path25 from "node:path";
28477
+ var CODEX_AGENT_ROLES = ["verifier", "reviewer", "implementer"];
28202
28478
  var CODEX_AGENT_MARKER = "# Generated by Workit";
28479
+ var codexAgentName = (role) => `workit-${role}`;
28480
+ var codexAgentFile = (role) => `${codexAgentName(role)}.toml`;
28481
+ var tomlString = (value) => JSON.stringify(value);
28203
28482
  var codexHomeOfInstall = (pluginRoot) => {
28204
28483
  const parts = path25.resolve(pluginRoot).split(path25.sep);
28205
28484
  const cache = parts.length - 4;
28206
28485
  return cache >= 2 && parts[cache] === "cache" && parts[cache - 1] === "plugins" ? parts.slice(0, cache - 1).join(path25.sep) || path25.sep : null;
28207
28486
  };
28208
28487
  var codexHomeFor = (pluginRoot, env) => codexHomeOfInstall(pluginRoot) ?? (env.CODEX_HOME || path25.join(env.HOME || os5.homedir(), ".codex"));
28488
+ var withRoleModel = (content, file, models) => {
28489
+ const role = CODEX_AGENT_ROLES.find((candidate) => codexAgentFile(candidate) === file);
28490
+ const chosen = role ? models[role] : undefined;
28491
+ if (!chosen)
28492
+ return content;
28493
+ return content.replace(/^(description = .*)$/m, (line) => `${line}
28494
+ model = ${tomlString(chosen.model)}${chosen.effort ? `
28495
+ model_reasoning_effort = ${tomlString(chosen.effort)}` : ""}`);
28496
+ };
28209
28497
  var MARKER_VERSION = /^# Generated by Workit (\d+\.\d+\.\d+)\b/;
28210
28498
  var agentMarkerVersion = (text) => MARKER_VERSION.exec(text)?.[1] ?? null;
28211
28499
  var stampCodexAgent = (content, version) => version && content.startsWith(CODEX_AGENT_MARKER) && !agentMarkerVersion(content) ? `${CODEX_AGENT_MARKER} ${version}${content.slice(CODEX_AGENT_MARKER.length)}` : content;
@@ -28232,7 +28520,7 @@ var lstatOrNull = (file) => {
28232
28520
  return null;
28233
28521
  }
28234
28522
  };
28235
- var installCodexAgents = (pluginRoot, codexHome) => {
28523
+ var installCodexAgents = (pluginRoot, codexHome, models = {}) => {
28236
28524
  const source = path25.join(pluginRoot, "agents");
28237
28525
  if (!existsSync10(source))
28238
28526
  return [];
@@ -28240,7 +28528,7 @@ var installCodexAgents = (pluginRoot, codexHome) => {
28240
28528
  const target = path25.join(codexHome, "agents");
28241
28529
  const results = [];
28242
28530
  for (const file of readdirSync5(source).filter((name) => name.endsWith(".toml")).toSorted()) {
28243
- const content = stampCodexAgent(readFileSync8(path25.join(source, file), "utf8"), version);
28531
+ const content = withRoleModel(stampCodexAgent(readFileSync8(path25.join(source, file), "utf8"), version), file, models);
28244
28532
  const dest = path25.join(target, file);
28245
28533
  const stat = lstatOrNull(dest);
28246
28534
  if (stat?.isSymbolicLink()) {
@@ -28340,12 +28628,24 @@ var codexContextProvider = (host = detectCodexSurface(process.env), root) => ({
28340
28628
  }
28341
28629
  });
28342
28630
  var pluginRoot = () => path26.resolve(path26.dirname(fileURLToPath3(import.meta.url)), "..");
28343
- var syncCodexAgents = (root) => {
28631
+ var codexRoleModels = (cwd) => {
28632
+ const out = {};
28633
+ for (const role of ["verifier", "reviewer"]) {
28634
+ const found = resolveRoleModel(cwd, { role }, "codex");
28635
+ if (found)
28636
+ out[role] = { model: found.model, effort: found.effort };
28637
+ }
28638
+ const implementer = uniformImplementerModel(cwd, "codex");
28639
+ if (implementer)
28640
+ out.implementer = { model: implementer.model, effort: implementer.effort };
28641
+ return out;
28642
+ };
28643
+ var syncCodexAgents = (root, cwd) => {
28344
28644
  const codexHome = codexHomeOfInstall(root);
28345
28645
  if (!codexHome)
28346
28646
  return;
28347
28647
  try {
28348
- installCodexAgents(root, codexHome);
28648
+ installCodexAgents(root, codexHome, codexRoleModels(cwd ?? resolveCodexWorkspaceRoot(root, process.env) ?? process.cwd()));
28349
28649
  } catch {}
28350
28650
  };
28351
28651
  if (__require.main == __require.module) {
@@ -28366,6 +28666,7 @@ if (__require.main == __require.module) {
28366
28666
  export {
28367
28667
  codexContextProvider,
28368
28668
  codexQualification,
28669
+ codexRoleModels,
28369
28670
  resolveCodexWorkspaceRoot,
28370
28671
  syncCodexAgents
28371
28672
  };