@attalabs/vinaya 0.26.0 → 0.28.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.
Files changed (50) hide show
  1. package/README.md +6 -3
  2. package/aeg-root/enforcement.md +5 -5
  3. package/aeg-root/milestone-model.md +2 -0
  4. package/aeg-root/process.md +1 -1
  5. package/aeg-root/roles/developer.md +23 -17
  6. package/aeg-root/roles/planner.md +4 -0
  7. package/aeg-root/roles/principal.md +8 -2
  8. package/aeg-root/roles/reviewer.md +9 -5
  9. package/aeg-root/roles/security.md +9 -5
  10. package/dist/checks/bin/check-body-bare-digits.js +1038 -257
  11. package/dist/checks/bin/check-branch-topology.js +963 -199
  12. package/dist/checks/bin/check-brief-shape.js +1276 -203
  13. package/dist/checks/bin/check-changeset-coverage.js +1677 -281
  14. package/dist/checks/bin/check-closes-n.js +967 -203
  15. package/dist/checks/bin/check-coherence.js +1127 -284
  16. package/dist/checks/bin/check-dead-branch-push.js +910 -193
  17. package/dist/checks/bin/check-dispatch-readiness.js +1189 -295
  18. package/dist/checks/bin/check-doc-coverage-push.js +1673 -277
  19. package/dist/checks/bin/check-doc-coverage.js +1675 -279
  20. package/dist/checks/bin/check-doctrine-no-procedures.js +1020 -257
  21. package/dist/checks/bin/check-doctrine-portability.js +1672 -276
  22. package/dist/checks/bin/check-evidence-fresh.js +2526 -322
  23. package/dist/checks/bin/check-exec-bits.js +1670 -274
  24. package/dist/checks/bin/check-first-push-dispatch.js +1075 -265
  25. package/dist/checks/bin/check-issue-assignment.js +965 -201
  26. package/dist/checks/bin/check-issue-milestone-attach.js +5734 -0
  27. package/dist/checks/bin/check-issue-objectives-numbering.js +5736 -0
  28. package/dist/checks/bin/check-issue-parts-coverage.js +5736 -0
  29. package/dist/checks/bin/check-issue-surface-globs.js +6910 -0
  30. package/dist/checks/bin/check-issue-title-grammar.js +5736 -0
  31. package/dist/checks/bin/check-issue-tranche-label.js +5736 -0
  32. package/dist/checks/bin/check-main-branch-refusal.js +910 -193
  33. package/dist/checks/bin/check-no-disk-state.js +910 -193
  34. package/dist/checks/bin/check-pr-premise-reassert.js +1020 -257
  35. package/dist/checks/bin/check-pr-report-density.js +910 -193
  36. package/dist/checks/bin/check-quoted-command.js +1651 -274
  37. package/dist/checks/bin/check-reader-resolvable-prose.js +1655 -278
  38. package/dist/checks/bin/check-registry-gates.js +997 -193
  39. package/dist/checks/bin/check-retired-vocabulary.js +1649 -272
  40. package/dist/checks/bin/check-review-gate.js +1163 -316
  41. package/dist/checks/bin/check-single-plan-pr.js +910 -193
  42. package/dist/checks/bin/check-surface-scope.js +973 -201
  43. package/dist/checks/bin/check-test-plan.js +939 -206
  44. package/dist/checks/bin/check-token-collection-wired.js +910 -193
  45. package/dist/checks/bin/check-token-report.js +910 -193
  46. package/dist/checks/bin/check-workspace-escape.js +1668 -272
  47. package/dist/index.js +10602 -5352
  48. package/dist/lib/pre-push-changed-files.js +57 -0
  49. package/dist/lib/pre-push-select-tests.js +1473 -0
  50. package/package.json +4 -2
@@ -4,7 +4,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/checks/bin/check-workspace-escape.ts
6
6
  import { readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
7
- import { join as join3 } from "node:path";
7
+ import { join as join5 } from "node:path";
8
8
  // ../../packages/aeg-core/src/gate-audience.ts
9
9
  var GATE_AUDIENCE = {
10
10
  "check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
@@ -304,7 +304,7 @@ function issueListByLabelArgs(owner, repo, label) {
304
304
  "--state",
305
305
  "all",
306
306
  "--json",
307
- "number,title,body,state,labels,milestone",
307
+ "number,title,body,state,labels,milestone,stateReason",
308
308
  "--limit",
309
309
  "200"
310
310
  ];
@@ -650,14 +650,19 @@ function resolveTaskIssueRef(title, labels) {
650
650
  var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
651
651
  var NEXT_HEADING = /^#{1,6}\s+\S/m;
652
652
  var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
653
- function intentGoalForSlug(description, slug) {
654
- const text = stripCode(description, { inlineSpans: "keep" });
653
+ function intentsSection(text) {
655
654
  const start = text.match(INTENTS_HEADING);
656
655
  if (!start || start.index === undefined)
657
- return "";
656
+ return null;
658
657
  const rest = text.slice(start.index + start[0].length);
659
658
  const next = rest.match(NEXT_HEADING);
660
- const section = rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
659
+ return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
660
+ }
661
+ function intentGoalForSlug(description, slug) {
662
+ const text = stripCode(description, { inlineSpans: "keep" });
663
+ const section = intentsSection(text);
664
+ if (section === null)
665
+ return "";
661
666
  for (const line of section.split(`
662
667
  `)) {
663
668
  const trimmed = line.trim();
@@ -1949,6 +1954,9 @@ function parseInlineFieldList(section) {
1949
1954
  // ../../packages/aeg-core/src/verdict-extraction.ts
1950
1955
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1951
1956
  var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1957
+ var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
1958
+ var BRIEF_HASH_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Brief hash:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1959
+ var POLICY_DIGEST_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Policy digest:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1952
1960
  function firstFiveLines(comment) {
1953
1961
  return comment.split(`
1954
1962
  `).slice(0, 5).join(`
@@ -1962,6 +1970,35 @@ function extractObjectivesVersion(comment) {
1962
1970
  const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
1963
1971
  return m ? m[1].toLowerCase() : null;
1964
1972
  }
1973
+ function firstSevenLines(comment) {
1974
+ return comment.split(`
1975
+ `).slice(0, 7).join(`
1976
+ `);
1977
+ }
1978
+ function extractRulingOrdinal(comment) {
1979
+ const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
1980
+ return m ? Number.parseInt(m[1], 10) : null;
1981
+ }
1982
+ function firstElevenLines(comment) {
1983
+ return comment.split(`
1984
+ `).slice(0, 11).join(`
1985
+ `);
1986
+ }
1987
+ function extractBriefHash(comment) {
1988
+ const m = firstElevenLines(comment).match(BRIEF_HASH_PATTERN);
1989
+ return m ? m[1].toLowerCase() : null;
1990
+ }
1991
+ function extractPolicyDigest(comment) {
1992
+ const m = firstElevenLines(comment).match(POLICY_DIGEST_PATTERN);
1993
+ return m ? m[1].toLowerCase() : null;
1994
+ }
1995
+ var FINDING_SEVERITY_LINE = /^\d+\.\s+\[([A-Z][A-Z]*)\]\s+(.+?)\s+—/gm;
1996
+ function extractFindingSeverities(comment) {
1997
+ return [...comment.matchAll(FINDING_SEVERITY_LINE)].map((m) => ({
1998
+ severity: m[1],
1999
+ location: m[2]
2000
+ }));
2001
+ }
1965
2002
  function extractVerdict(comments, valuePattern, missingLabel) {
1966
2003
  const candidates = comments.filter((c) => valuePattern.test(c));
1967
2004
  if (candidates.length === 0) {
@@ -1969,6 +2006,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1969
2006
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1970
2007
  headSha: null,
1971
2008
  objectivesVersion: null,
2009
+ rulingOrdinal: null,
2010
+ briefHash: null,
2011
+ policyDigest: null,
2012
+ findingSeverities: [],
1972
2013
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1973
2014
  };
1974
2015
  }
@@ -1979,6 +2020,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1979
2020
  value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
1980
2021
  headSha: null,
1981
2022
  objectivesVersion: null,
2023
+ rulingOrdinal: null,
2024
+ briefHash: null,
2025
+ policyDigest: null,
2026
+ findingSeverities: [],
1982
2027
  danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
1983
2028
  };
1984
2029
  }
@@ -1986,6 +2031,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1986
2031
  value: m[1].toUpperCase().replace(/[_-]/g, " "),
1987
2032
  headSha: extractHeadSha(latest),
1988
2033
  objectivesVersion: extractObjectivesVersion(latest),
2034
+ rulingOrdinal: extractRulingOrdinal(latest),
2035
+ briefHash: extractBriefHash(latest),
2036
+ policyDigest: extractPolicyDigest(latest),
2037
+ findingSeverities: extractFindingSeverities(latest),
1989
2038
  danglingNote: null
1990
2039
  };
1991
2040
  }
@@ -2321,6 +2370,53 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
2321
2370
  }
2322
2371
  return out;
2323
2372
  }
2373
+ // ../../packages/aeg-core/src/review-policy.ts
2374
+ var CODE_REVIEW_SEVERITY_ORDER = ["BLOCKER", "MAJOR", "MINOR"];
2375
+ var SECURITY_SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
2376
+ var DEFAULT_MAX_ROUNDS = 3;
2377
+ var DEFAULT_REVIEW_POLICY = {
2378
+ codeReviewThreshold: "BLOCKER",
2379
+ securityThreshold: "HIGH",
2380
+ maxRounds: DEFAULT_MAX_ROUNDS
2381
+ };
2382
+ var FILE_SHAPED_LOCATION = /\.[a-zA-Z0-9]{1,10}(:\d+)?\s*$/;
2383
+ var PROSE_LOCATION_PATTERNS = [/\bpr\s*body\b/i, /\bcomment\b/i];
2384
+ var ROLE_FILE_LOCATION = /(^|\/)aeg-root\/roles\//i;
2385
+ function isProseLocation(location) {
2386
+ if (ROLE_FILE_LOCATION.test(location))
2387
+ return true;
2388
+ if (FILE_SHAPED_LOCATION.test(location))
2389
+ return false;
2390
+ return PROSE_LOCATION_PATTERNS.some((pattern) => pattern.test(location));
2391
+ }
2392
+ var PROSE_CAP_SEVERITY = "MINOR";
2393
+ function blockingSeverities(scale, threshold) {
2394
+ const idx = scale.indexOf(threshold);
2395
+ if (idx === -1) {
2396
+ throw new Error(`blockingSeverities: threshold "${threshold}" is not one of ${scale.join(" > ")}`);
2397
+ }
2398
+ return scale.slice(0, idx + 1);
2399
+ }
2400
+ function evaluateReviewFindings(findings, scale, threshold) {
2401
+ const blocking = new Set(blockingSeverities(scale, threshold));
2402
+ const blockingFindings = findings.filter((f) => {
2403
+ if (!scale.includes(f.severity)) {
2404
+ throw new Error(`evaluateReviewFindings: severity "${f.severity}" is not one of ${scale.join(" > ")}`);
2405
+ }
2406
+ const effectiveSeverity = f.location !== undefined && isProseLocation(f.location) ? PROSE_CAP_SEVERITY : f.severity;
2407
+ return blocking.has(effectiveSeverity);
2408
+ });
2409
+ return { outcome: blockingFindings.length > 0 ? "blocked" : "clean", blockingFindings };
2410
+ }
2411
+ function evaluateCodeReview(findings, policy) {
2412
+ return evaluateReviewFindings(findings, CODE_REVIEW_SEVERITY_ORDER, policy.codeReviewThreshold);
2413
+ }
2414
+ function evaluateSecurityReview(findings, policy) {
2415
+ return evaluateReviewFindings(findings, SECURITY_SEVERITY_ORDER, policy.securityThreshold);
2416
+ }
2417
+ function isKnownSeverity(scale, value) {
2418
+ return scale.includes(value);
2419
+ }
2324
2420
  // ../../packages/aeg-core/src/pr-tier.ts
2325
2421
  var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
2326
2422
  function readTierFromPrBody(prBody) {
@@ -2347,6 +2443,9 @@ import { createHash as createHash2 } from "node:crypto";
2347
2443
  var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
2348
2444
  var NEXT_HEADING_RE = /^##[ \t]/m;
2349
2445
  var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
2446
+ function maskedForHeadingSearch(body) {
2447
+ return maskDetailsBlocks(maskCode(body));
2448
+ }
2350
2449
  function hasBacktickedPath(text) {
2351
2450
  let i = 0;
2352
2451
  while (i < text.length) {
@@ -2369,16 +2468,22 @@ function stripObjectiveBackticks(text) {
2369
2468
  function wordCount(text) {
2370
2469
  return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
2371
2470
  }
2372
- function objectivesSectionText(body) {
2373
- const heading = HEADING_RE.exec(body);
2471
+ function objectivesSectionBounds(body) {
2472
+ const masked = maskedForHeadingSearch(body);
2473
+ const heading = HEADING_RE.exec(masked);
2374
2474
  if (!heading)
2375
2475
  return null;
2376
- const afterHeading = body.slice(heading.index + heading[0].length);
2476
+ const start = heading.index + heading[0].length;
2477
+ const afterHeading = masked.slice(start);
2377
2478
  const next = NEXT_HEADING_RE.exec(afterHeading);
2378
- return next ? afterHeading.slice(0, next.index) : afterHeading;
2479
+ return { start, end: next ? start + next.index : body.length };
2480
+ }
2481
+ function objectivesSectionText(body) {
2482
+ const bounds = objectivesSectionBounds(body);
2483
+ return bounds === null ? null : body.slice(bounds.start, bounds.end);
2379
2484
  }
2380
2485
  function hasObjectivesHeading(body) {
2381
- return HEADING_RE.test(body);
2486
+ return HEADING_RE.test(maskedForHeadingSearch(body));
2382
2487
  }
2383
2488
  function objectivesOf(body) {
2384
2489
  const section = objectivesSectionText(body);
@@ -2438,6 +2543,15 @@ function isIssueNotFoundError(err) {
2438
2543
  `);
2439
2544
  return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
2440
2545
  }
2546
+ function resolveObjectivesSource(prBody, issue, cutoverIssue) {
2547
+ if (issue !== null && issue < cutoverIssue)
2548
+ return { kind: "none" };
2549
+ if (issue !== null)
2550
+ return { kind: "issue", issue };
2551
+ if (hasObjectivesHeading(prBody))
2552
+ return { kind: "body" };
2553
+ return { kind: "none" };
2554
+ }
2441
2555
 
2442
2556
  // ../../packages/aeg-core/src/premise-check.ts
2443
2557
  import { createHash as createHash3 } from "node:crypto";
@@ -2673,7 +2787,7 @@ function checkForField(prBody) {
2673
2787
  ]
2674
2788
  };
2675
2789
  }
2676
- function checkClosesN(prBody) {
2790
+ function checkClosesNPresence(prBody) {
2677
2791
  const closesPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s{0,8}:?\s{0,8}#\d+/i;
2678
2792
  if (closesPattern.test(stripCode(prBody))) {
2679
2793
  return { status: "pass", errors: [] };
@@ -2705,10 +2819,22 @@ var COMMIT_TYPES = [
2705
2819
  "Test"
2706
2820
  ];
2707
2821
  var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
2822
+ function checkForgeTitle(title) {
2823
+ const taskStyle = /^\[[a-z0-9._-]+\] \S+ — \S/;
2824
+ if (COMMIT_TYPE_STYLE.test(title) || taskStyle.test(title))
2825
+ return { status: "pass", errors: [] };
2826
+ return {
2827
+ status: "fail",
2828
+ errors: [
2829
+ `brief-validation title: "${title}" matches neither title grammar — expected \`Type: description\` / \`Type(scope): description\` (commitlint types + Plan) or \`[tranche] id — description\` (task form).`
2830
+ ]
2831
+ };
2832
+ }
2708
2833
  var BRIEF_SHAPE_MARKERS = [checkSurfaceMap, checkDocUpdateList, checkStopConditions, checkAutonomyClause];
2709
2834
  var TASK_BRANCH_PATTERN = /^task\/[^/]+\/[^/]+$/;
2835
+ var TASK_ISSUE_BRANCH_PATTERN = /^task\/issue-\d+$/;
2710
2836
  function isTaskBranch(branch) {
2711
- return TASK_BRANCH_PATTERN.test(branch);
2837
+ return TASK_BRANCH_PATTERN.test(branch) || TASK_ISSUE_BRANCH_PATTERN.test(branch);
2712
2838
  }
2713
2839
  function isBriefShaped(prBody) {
2714
2840
  const stripped = stripCode(prBody);
@@ -2830,8 +2956,9 @@ function packagesNamedIn(text) {
2830
2956
  }
2831
2957
  function hasTestPathForConsumer(text, consumerDir) {
2832
2958
  const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2833
- const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2834
- return re.test(text);
2959
+ const filePathRe = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2960
+ const testDirRe = new RegExp(`${escaped}\\/(?:[\\w-]+\\/)*(?:tests|specs)(?:\\/|\\b)`);
2961
+ return filePathRe.test(text) || testDirRe.test(text);
2835
2962
  }
2836
2963
  function checkConsumerTests(prBody, consumersOf) {
2837
2964
  const section4 = extractNumberedSection(prBody, 4);
@@ -2935,21 +3062,44 @@ function checkBriefSections(prBody, readTier, options = {}) {
2935
3062
  checkConsumerTests(prBody, consumersOf),
2936
3063
  checkDefeatCases(prBody),
2937
3064
  ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2938
- ...requireClosesN ? [checkClosesN(prBody)] : []
3065
+ ...requireClosesN ? [checkClosesNPresence(prBody)] : []
2939
3066
  ];
2940
3067
  return { errors: results.flatMap((r) => r.errors) };
2941
3068
  }
2942
- var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
2943
- function contentAfterTwoLines(body) {
2944
- const first = body.indexOf(`
2945
- `);
2946
- if (first === -1)
2947
- return "";
2948
- const second = body.indexOf(`
2949
- `, first + 1);
2950
- if (second === -1)
2951
- return "";
2952
- return body.slice(second + 1);
3069
+ function contentAfterNLines(body, n) {
3070
+ let idx = -1;
3071
+ for (let i = 0;i < n; i++) {
3072
+ idx = body.indexOf(`
3073
+ `, idx + 1);
3074
+ if (idx === -1)
3075
+ return "";
3076
+ }
3077
+ return body.slice(idx + 1);
3078
+ }
3079
+ var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
3080
+ function parseBriefMarkerVersion(firstLine) {
3081
+ const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
3082
+ if (!m)
3083
+ return null;
3084
+ const version = Number.parseInt(m[1], 10);
3085
+ return Number.isInteger(version) && version >= 1 ? version : null;
3086
+ }
3087
+ function frozenBriefContent(body, version) {
3088
+ return contentAfterNLines(body, version === 1 ? 2 : 3);
3089
+ }
3090
+ function resolveNewestFrozenBrief(comments, allowlist) {
3091
+ let best = null;
3092
+ for (const c of comments) {
3093
+ if (!isPrincipal(c.author, allowlist))
3094
+ continue;
3095
+ const version = parseBriefMarkerVersion(c.body.split(`
3096
+ `)[0] ?? "");
3097
+ if (version === null)
3098
+ continue;
3099
+ if (best === null || version > best.version)
3100
+ best = { ...c, version };
3101
+ }
3102
+ return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
2953
3103
  }
2954
3104
  // ../../packages/aeg-core/src/doctrine-portability.ts
2955
3105
  var DEFAULT_SHIPS_PREFIX = "aeg-root/";
@@ -3202,6 +3352,28 @@ function topLevelSectionText(body, headingName) {
3202
3352
  const next = /^##[ \t]/m.exec(afterHeading);
3203
3353
  return next ? afterHeading.slice(0, next.index) : afterHeading;
3204
3354
  }
3355
+ function partHasBacktickedPath(text) {
3356
+ let i = 0;
3357
+ while (i < text.length) {
3358
+ const start = text.indexOf("`", i);
3359
+ if (start === -1)
3360
+ return false;
3361
+ const end = text.indexOf("`", start + 1);
3362
+ if (end === -1)
3363
+ return false;
3364
+ if (text.slice(start + 1, end).includes("/"))
3365
+ return true;
3366
+ i = end + 1;
3367
+ }
3368
+ return false;
3369
+ }
3370
+ var PART_MIN_WORDS_OUTSIDE_BACKTICKS = 3;
3371
+ function stripPartBackticks(text) {
3372
+ return text.replace(/`[^`\n]*`/g, " ");
3373
+ }
3374
+ function partWordCount(text) {
3375
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
3376
+ }
3205
3377
  function looksLikeFilePath(entry2) {
3206
3378
  const stripped = entry2.replace(/\/\*\*?$/, "");
3207
3379
  const lastSegment = stripped.split("/").pop() ?? stripped;
@@ -3242,6 +3414,18 @@ function globCoversPath(glob, path) {
3242
3414
  const p = path.replace(/\/+$/, "");
3243
3415
  return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
3244
3416
  }
3417
+ function checkSurfaceGlobsResolve(body, resolvesToFile) {
3418
+ const surface = parseIssueSurface(body);
3419
+ if (!surface.ok)
3420
+ return { status: "pass", errors: [] };
3421
+ const errors = [];
3422
+ for (const glob of surface.value.in) {
3423
+ if (!resolvesToFile(glob)) {
3424
+ errors.push(`issue-validation Surface: \`${glob}\` in \`## Surface\`'s \`in:\` list matches no tracked file — a Surface that cannot resolve cannot render a brief's file list.`);
3425
+ }
3426
+ }
3427
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3428
+ }
3245
3429
  function checkSurfaceScope(changedFiles, outGlobs) {
3246
3430
  if (outGlobs.length === 0)
3247
3431
  return { ok: true };
@@ -3253,9 +3437,77 @@ function checkSurfaceScope(changedFiles, outGlobs) {
3253
3437
  }
3254
3438
  return violations.length > 0 ? { ok: false, violations } : { ok: true };
3255
3439
  }
3440
+ var ISSUE_PART_LINE_RE = /^Part\s+(\d+)\s*\(([^)]*)\)\s*[-—–]\s*(.*)$/i;
3441
+ function parseIssueParts(body) {
3442
+ const section = topLevelSectionText(body, "Parts");
3443
+ if (section === null)
3444
+ return { ok: false, errors: ["no `## Parts` heading found in the body."] };
3445
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
3446
+ const parts = [];
3447
+ const errors = [];
3448
+ for (const line of lines) {
3449
+ const m = ISSUE_PART_LINE_RE.exec(line);
3450
+ if (!m) {
3451
+ errors.push(`"${line}" is not a well-formed Parts line — expected \`Part <n> (<refs>) — <outcome>\`.`);
3452
+ continue;
3453
+ }
3454
+ const n = Number.parseInt(m[1], 10);
3455
+ const refs = m[2];
3456
+ const text = m[3].trim();
3457
+ if (text.length === 0) {
3458
+ errors.push(`Part ${n} has no outcome text after the dash — every Part states one observable outcome.`);
3459
+ continue;
3460
+ }
3461
+ if (partHasBacktickedPath(text) && partWordCount(stripPartBackticks(text)) < PART_MIN_WORDS_OUTSIDE_BACKTICKS) {
3462
+ errors.push(`Part ${n} is little more than a file path — a Part names an outcome and symbols, never a bare path.`);
3463
+ continue;
3464
+ }
3465
+ const objectiveIds = [...refs.matchAll(/O(\d+)/g)].map((r) => Number.parseInt(r[1], 10));
3466
+ parts.push({ n, objectiveIds, text });
3467
+ }
3468
+ if (parts.length === 0) {
3469
+ errors.push("the `## Parts` section has no well-formed `Part <n> (<refs>) — <outcome>` lines.");
3470
+ }
3471
+ if (errors.length > 0)
3472
+ return { ok: false, errors };
3473
+ return { ok: true, value: parts };
3474
+ }
3475
+ function checkPartsCiteDefinedObjectives(body) {
3476
+ const parts = parseIssueParts(body);
3477
+ const objectives = objectivesOf(body);
3478
+ if (!parts.ok || !objectives.ok)
3479
+ return { status: "pass", errors: [] };
3480
+ const definedIds = new Set(objectives.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
3481
+ const errors = [];
3482
+ for (const part of parts.value) {
3483
+ for (const objectiveId of part.objectiveIds) {
3484
+ if (!definedIds.has(objectiveId)) {
3485
+ errors.push(`issue-validation Parts: Part ${part.n} cites O${objectiveId}, which the Issue's own \`## Objectives\` section does not define.`);
3486
+ }
3487
+ }
3488
+ }
3489
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3490
+ }
3256
3491
  function isTaskIssueLabelSet(labels) {
3257
3492
  return hasLabel("tranche", labels);
3258
3493
  }
3494
+ function checkTrancheLabelPresence(_body, _labels) {
3495
+ return { status: "pass", errors: [] };
3496
+ }
3497
+ function checkMilestoneAttach(labels, currentMilestoneTitle, resolvedMilestoneTitle) {
3498
+ if (!isTaskIssueLabelSet(labels))
3499
+ return { status: "pass", errors: [] };
3500
+ if (resolvedMilestoneTitle === null)
3501
+ return { status: "pass", errors: [] };
3502
+ if (currentMilestoneTitle === resolvedMilestoneTitle)
3503
+ return { status: "pass", errors: [] };
3504
+ return {
3505
+ status: "fail",
3506
+ errors: [
3507
+ `issue-validation milestone attach: this task Issue's tranche label resolves to Milestone "${resolvedMilestoneTitle}", but its live Milestone is ${currentMilestoneTitle === null ? "unset" : `"${currentMilestoneTitle}"`} — attach it to "${resolvedMilestoneTitle}".`
3508
+ ]
3509
+ };
3510
+ }
3259
3511
  var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
3260
3512
  function isControlCodePoint(codePoint) {
3261
3513
  return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
@@ -3320,6 +3572,19 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
3320
3572
  var DOC_PATH_RE = /(?:(?:aeg-root|apps|packages|specs|docs|tools|\.claude|\.github)\/[\w./@-]*\.(?:md|mdx)|\.claude\/(?:skills|rules)\/[\w./-]+|\b(?:docs-index|decisions|projects|state-machine|enforcement|process|README|CLAUDE)\.md\b|\b[\w-]+-(?:spec|decisions|backlog)\.md\b)/i;
3321
3573
  var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
3322
3574
 
3575
+ // ../../packages/aeg-core/src/task-branch-identity.ts
3576
+ var ISSUE_BRANCH_PATTERN = /^task\/issue-(\d+)$/;
3577
+ var TRANCHE_BRANCH_PATTERN = /^task\/([^/]+)\/([^/]+)$/;
3578
+ function parseTaskBranchIdentity(branch) {
3579
+ const issueMatch = ISSUE_BRANCH_PATTERN.exec(branch);
3580
+ if (issueMatch)
3581
+ return { kind: "issue", issueNumber: Number(issueMatch[1]) };
3582
+ const trancheMatch = TRANCHE_BRANCH_PATTERN.exec(branch);
3583
+ if (trancheMatch)
3584
+ return { kind: "tranche", tranche: trancheMatch[1], taskId: trancheMatch[2] };
3585
+ return null;
3586
+ }
3587
+
3323
3588
  // ../../packages/aeg-core/src/coherence-checks.ts
3324
3589
  var COHERENCE_ENFORCED_FROM = "2026-07-01";
3325
3590
  function isGrandfathered(isoDate) {
@@ -3340,6 +3605,7 @@ function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
3340
3605
  if (handClosed)
3341
3606
  continue;
3342
3607
  failures.push({
3608
+ code: "closed-without-merge",
3343
3609
  issue: e.task.issue,
3344
3610
  tranche: e.trancheSlug,
3345
3611
  task: e.task.id,
@@ -3364,6 +3630,7 @@ function checkA3(entries) {
3364
3630
  continue;
3365
3631
  if (e.facts.prState === "merged" && e.facts.issueState !== "closed") {
3366
3632
  failures.push({
3633
+ code: "auto-close-misfire",
3367
3634
  issue: e.task.issue,
3368
3635
  tranche: e.trancheSlug,
3369
3636
  task: e.task.id,
@@ -3388,6 +3655,7 @@ function checkT1(entries) {
3388
3655
  continue;
3389
3656
  if (e.facts === undefined) {
3390
3657
  failures.push({
3658
+ code: "phantom-issue-ref",
3391
3659
  issue: e.task.issue,
3392
3660
  tranche: e.trancheSlug,
3393
3661
  task: e.task.id,
@@ -3406,6 +3674,7 @@ function checkT2(openIssuesBySlug, topologyIssuesBySlug, ciTrancheSlug) {
3406
3674
  for (const num of openNums) {
3407
3675
  if (!topologySet.has(num)) {
3408
3676
  failures.push({
3677
+ code: "orphan-task",
3409
3678
  issue: num,
3410
3679
  tranche: slug,
3411
3680
  reason: `Issue #${num} is open and labeled ${trancheLabel(slug)} but does not appear in the topology file`
@@ -3440,6 +3709,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3440
3709
  continue;
3441
3710
  if (forgeUnavailableSlugs?.has(e.trancheSlug)) {
3442
3711
  failures.push({
3712
+ code: "tbd-in-active-tranche",
3443
3713
  issue: null,
3444
3714
  tranche: e.trancheSlug,
3445
3715
  task: e.task.id,
@@ -3449,6 +3719,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3449
3719
  continue;
3450
3720
  }
3451
3721
  failures.push({
3722
+ code: "tbd-in-active-tranche",
3452
3723
  issue: null,
3453
3724
  tranche: e.trancheSlug,
3454
3725
  task: e.task.id,
@@ -3481,6 +3752,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3481
3752
  const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3482
3753
  if (sameTask || sameIssue) {
3483
3754
  failures.push({
3755
+ code: "d1-self-dependency",
3484
3756
  issue: e.task.issue,
3485
3757
  tranche: e.trancheSlug,
3486
3758
  task: e.task.id,
@@ -3492,6 +3764,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3492
3764
  const depClosed = depFacts?.issueState === "closed";
3493
3765
  if (!depClosed) {
3494
3766
  failures.push({
3767
+ code: "dispatched-on-unmet-deps",
3495
3768
  issue: e.task.issue,
3496
3769
  tranche: e.trancheSlug,
3497
3770
  task: e.task.id,
@@ -3517,11 +3790,13 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3517
3790
  const errors = [
3518
3791
  ...checkIssueRationale(issue.body).errors,
3519
3792
  ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3520
- ...checkIssueObjectives(issue.body, issue.number).errors
3793
+ ...checkIssueObjectives(issue.body, issue.number).errors,
3794
+ ...checkPartsCiteDefinedObjectives(issue.body).errors
3521
3795
  ];
3522
3796
  if (errors.length === 0)
3523
3797
  continue;
3524
3798
  failures.push({
3799
+ code: "missing-rationale-field",
3525
3800
  issue: issue.number,
3526
3801
  tranche: slug,
3527
3802
  reason: `Issue #${issue.number} fails the rationale gate: ${errors.join(" | ")}`,
@@ -3550,6 +3825,7 @@ function checkL1(files, entriesBySlug) {
3550
3825
  const allClosed = withFacts.every((e) => e.facts?.issueState === "closed");
3551
3826
  if (allClosed) {
3552
3827
  failures.push({
3828
+ code: "archive-recommended",
3553
3829
  tranche: f.slug,
3554
3830
  reason: "Active tranche has no open task-Issues — consider archiving to completed/"
3555
3831
  });
@@ -3581,27 +3857,38 @@ function extractClosesReferences(prBody) {
3581
3857
  }
3582
3858
  return referenced;
3583
3859
  }
3584
- function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3860
+ function checkClosesNTopology(branch, prBody, trancheFiles, taskIssueRefs) {
3585
3861
  const referenced = extractClosesReferences(prBody);
3586
3862
  if (taskIssueRefs) {
3587
3863
  for (const n of referenced) {
3588
- const ref = taskIssueRefs.get(n);
3589
- if (!ref)
3864
+ const ref2 = taskIssueRefs.get(n);
3865
+ if (!ref2)
3590
3866
  continue;
3591
- const expectedBranch = `task/${ref.trancheSlug}/${ref.taskId}`;
3867
+ const expectedBranch = `task/${ref2.trancheSlug}/${ref2.taskId}`;
3592
3868
  if (branch !== expectedBranch) {
3593
3869
  return {
3594
3870
  ok: false,
3595
- message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${ref.taskId} of tranche "${ref.trancheSlug}") but is not named "${expectedBranch}" — rename the branch and re-push, or if this work is intentionally outside AEG's dispatch flow, remove the Closes reference.`
3871
+ message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${ref2.taskId} of tranche "${ref2.trancheSlug}") but is not named "${expectedBranch}" — rename the branch and re-push, or if this work is intentionally outside AEG's dispatch flow, remove the Closes reference.`
3596
3872
  };
3597
3873
  }
3598
3874
  }
3599
3875
  }
3600
- const m = branch.match(/^task\/([^/]+)\/([^/]+)$/);
3601
- if (!m)
3876
+ const ref = parseTaskBranchIdentity(branch);
3877
+ if (!ref)
3602
3878
  return { ok: true };
3603
- const trancheSlug = m[1];
3604
- const taskId = m[2];
3879
+ if (ref.kind === "issue") {
3880
+ const expectedIssue2 = ref.issueNumber;
3881
+ if (!referenced.has(expectedIssue2)) {
3882
+ return {
3883
+ ok: false,
3884
+ expectedIssue: expectedIssue2,
3885
+ message: `closes-n: PR body does not contain \`Closes #${expectedIssue2}\` (required for branch "${branch}"). Add it to the PR body Summary section.`
3886
+ };
3887
+ }
3888
+ return { ok: true, expectedIssue: expectedIssue2 };
3889
+ }
3890
+ const trancheSlug = ref.tranche;
3891
+ const taskId = ref.taskId;
3605
3892
  const trancheFile = trancheFiles.find((f) => f.slug === trancheSlug);
3606
3893
  if (!trancheFile) {
3607
3894
  return {
@@ -3653,29 +3940,67 @@ function extractIssue(body) {
3653
3940
  const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3654
3941
  return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3655
3942
  }
3656
- // ../../packages/aeg-core/src/review-gate.ts
3657
- function isBoundToHead(extraction, headSha) {
3658
- if (!extraction.headSha)
3943
+ // ../../packages/aeg-core/src/review-input-manifest.ts
3944
+ import { createHash as createHash4 } from "node:crypto";
3945
+ function briefHash(brief) {
3946
+ return createHash4("sha256").update(`${brief}
3947
+ `).digest("hex");
3948
+ }
3949
+ function policyDigest(policy) {
3950
+ return createHash4("sha256").update(JSON.stringify({ codeReviewThreshold: policy.codeReviewThreshold, securityThreshold: policy.securityThreshold })).digest("hex");
3951
+ }
3952
+ function isBoundToHead(echoed, headSha) {
3953
+ if (!echoed.headSha)
3659
3954
  return false;
3660
- return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
3955
+ return headSha.toLowerCase().startsWith(echoed.headSha.toLowerCase());
3661
3956
  }
3662
- function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3663
- if (patchIdOf === undefined || !extraction.headSha)
3957
+ function isBoundByPatchIdentity(echoed, headSha, patchIdOf) {
3958
+ if (patchIdOf === undefined || !echoed.headSha)
3664
3959
  return false;
3665
- const judged = patchIdOf(extraction.headSha);
3960
+ const judged = patchIdOf(echoed.headSha);
3666
3961
  const current = patchIdOf(headSha);
3667
3962
  if (judged === null || current === null)
3668
3963
  return false;
3669
3964
  return judged === current;
3670
3965
  }
3671
- function isBoundToPatch(extraction, headSha, patchIdOf) {
3672
- return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3966
+ function isBoundToPatch(echoed, headSha, patchIdOf) {
3967
+ return isBoundToHead(echoed, headSha) || isBoundByPatchIdentity(echoed, headSha, patchIdOf);
3673
3968
  }
3674
- function isBoundToObjectives(extraction, currentVersion) {
3969
+ function isBoundToObjectives(echoed, currentVersion) {
3675
3970
  if (currentVersion === null)
3676
3971
  return true;
3677
- return extraction.objectivesVersion === currentVersion;
3972
+ return echoed.objectivesVersion === currentVersion;
3973
+ }
3974
+ function isBoundToRulings(echoed, currentOrdinal) {
3975
+ if (echoed.rulingOrdinal === null)
3976
+ return currentOrdinal === 0;
3977
+ return echoed.rulingOrdinal === currentOrdinal;
3678
3978
  }
3979
+ function isBoundToBriefHash(echoed, currentHash) {
3980
+ if (currentHash === null)
3981
+ return true;
3982
+ return echoed.briefHash === currentHash;
3983
+ }
3984
+ function isBoundToPolicy(echoed, currentDigest) {
3985
+ return echoed.policyDigest === currentDigest;
3986
+ }
3987
+ function compareManifest(echoed, current, patchIdOf) {
3988
+ const head = isBoundToPatch(echoed, current.headSha, patchIdOf);
3989
+ const briefHashBound = isBoundToBriefHash(echoed, current.briefHash);
3990
+ const objectivesVersion2 = isBoundToObjectives(echoed, current.objectivesVersion);
3991
+ const rulingOrdinal = isBoundToRulings(echoed, current.rulingOrdinal);
3992
+ const policyDigestBound = isBoundToPolicy(echoed, current.policyDigest);
3993
+ return {
3994
+ bound: head && briefHashBound && objectivesVersion2 && rulingOrdinal && policyDigestBound,
3995
+ head,
3996
+ briefHash: briefHashBound,
3997
+ objectivesVersion: objectivesVersion2,
3998
+ rulingOrdinal,
3999
+ policyDigest: policyDigestBound
4000
+ };
4001
+ }
4002
+
4003
+ // ../../packages/aeg-core/src/review-gate.ts
3679
4004
  function checkReviewGate(input) {
3680
4005
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3681
4006
  const waived = isWaiverLabelActorVerified({
@@ -3698,13 +4023,55 @@ function checkReviewGate(input) {
3698
4023
  const verifiedBodies = verified.map((c) => c.body);
3699
4024
  const codeReview = extractCodeReviewVerdict(verifiedBodies);
3700
4025
  const security = extractSecurityReviewVerdict(verifiedBodies);
3701
- const codeReviewClean = codeReview.value === "APPROVE";
3702
- const securityClean = security.value === "PASS";
3703
- const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3704
- const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3705
- const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3706
- const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3707
- if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
4026
+ const policy = input.policy ?? DEFAULT_REVIEW_POLICY;
4027
+ let codeReviewPolicyEvaluation;
4028
+ let securityPolicyEvaluation;
4029
+ try {
4030
+ codeReviewPolicyEvaluation = evaluateCodeReview(codeReview.findingSeverities, policy);
4031
+ securityPolicyEvaluation = evaluateSecurityReview(security.findingSeverities, policy);
4032
+ } catch (err) {
4033
+ return {
4034
+ verdict: "fail",
4035
+ reason: `a verdict comment carries a finding severity this repository's policy does not recognize: ${err instanceof Error ? err.message : String(err)}`,
4036
+ waived: false
4037
+ };
4038
+ }
4039
+ const codeReviewTextClean = codeReview.value === "APPROVE";
4040
+ const securityTextClean = security.value === "PASS";
4041
+ const codeReviewPolicyClean = codeReviewPolicyEvaluation.outcome === "clean";
4042
+ const securityPolicyClean = securityPolicyEvaluation.outcome === "clean";
4043
+ const codeReviewClean = codeReviewTextClean && codeReviewPolicyClean;
4044
+ const securityClean = securityTextClean && securityPolicyClean;
4045
+ const currentManifest = {
4046
+ headSha: input.headSha,
4047
+ briefHash: input.briefHash ?? null,
4048
+ objectivesVersion: input.objectivesVersion,
4049
+ rulingOrdinal: input.rulingOrdinal,
4050
+ policyDigest: policyDigest(policy)
4051
+ };
4052
+ const codeReviewEchoed = {
4053
+ headSha: codeReview.headSha,
4054
+ briefHash: codeReview.briefHash,
4055
+ objectivesVersion: codeReview.objectivesVersion,
4056
+ rulingOrdinal: codeReview.rulingOrdinal,
4057
+ policyDigest: codeReview.policyDigest
4058
+ };
4059
+ const securityEchoed = {
4060
+ headSha: security.headSha,
4061
+ briefHash: security.briefHash,
4062
+ objectivesVersion: security.objectivesVersion,
4063
+ rulingOrdinal: security.rulingOrdinal,
4064
+ policyDigest: security.policyDigest
4065
+ };
4066
+ const codeReviewBinding = compareManifest(codeReviewEchoed, currentManifest, input.patchIdOf);
4067
+ const securityBinding = compareManifest(securityEchoed, currentManifest, input.patchIdOf);
4068
+ const codeReviewBound = codeReviewBinding.head;
4069
+ const securityBound = securityBinding.head;
4070
+ const codeReviewObjectivesBound = codeReviewBinding.objectivesVersion;
4071
+ const securityObjectivesBound = securityBinding.objectivesVersion;
4072
+ const codeReviewRulingsBound = codeReviewBinding.rulingOrdinal;
4073
+ const securityRulingsBound = securityBinding.rulingOrdinal;
4074
+ if (codeReviewClean && codeReviewBinding.bound && securityClean && securityBinding.bound && mechanicalChecksClean) {
3708
4075
  return {
3709
4076
  verdict: "pass",
3710
4077
  reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
@@ -3712,19 +4079,35 @@ function checkReviewGate(input) {
3712
4079
  };
3713
4080
  }
3714
4081
  const problems = [];
3715
- if (!codeReviewClean) {
4082
+ if (!codeReviewTextClean) {
3716
4083
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
4084
+ } else if (!codeReviewPolicyClean) {
4085
+ problems.push(`code-reviewer verdict says APPROVE but carries a finding (${codeReviewPolicyEvaluation.blockingFindings.map((f) => f.severity).join(", ")}) at or above this repository's code-review policy threshold (${policy.codeReviewThreshold}) — a reviewer's own APPROVE never overrides policy`);
3717
4086
  } else if (!codeReviewBound) {
3718
4087
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3719
4088
  } else if (!codeReviewObjectivesBound) {
3720
4089
  problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3721
- }
3722
- if (!securityClean) {
4090
+ } else if (!codeReviewRulingsBound) {
4091
+ problems.push(`the newest code-review verdict was cast against ruling ordinal ${codeReview.rulingOrdinal ?? "none"}, a newer ruling (ruling ${input.rulingOrdinal}) is now posted on this PR`);
4092
+ } else if (!codeReviewBinding.briefHash) {
4093
+ problems.push(`the newest code-review verdict was cast against brief hash ${codeReview.briefHash ?? "none"}, the frozen brief's current hash is ${currentManifest.briefHash ?? "none"}`);
4094
+ } else if (!codeReviewBinding.policyDigest) {
4095
+ problems.push(`the newest code-review verdict was cast against review policy digest ${codeReview.policyDigest ?? "none"}, this repository's current policy digest is ${currentManifest.policyDigest}`);
4096
+ }
4097
+ if (!securityTextClean) {
3723
4098
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
4099
+ } else if (!securityPolicyClean) {
4100
+ problems.push(`security-review verdict says PASS but carries a finding (${securityPolicyEvaluation.blockingFindings.map((f) => f.severity).join(", ")}) at or above this repository's security policy threshold (${policy.securityThreshold}) — a reviewer's own PASS never overrides policy`);
3724
4101
  } else if (!securityBound) {
3725
4102
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3726
4103
  } else if (!securityObjectivesBound) {
3727
4104
  problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
4105
+ } else if (!securityRulingsBound) {
4106
+ problems.push(`the newest security-review verdict was cast against ruling ordinal ${security.rulingOrdinal ?? "none"}, a newer ruling (ruling ${input.rulingOrdinal}) is now posted on this PR`);
4107
+ } else if (!securityBinding.briefHash) {
4108
+ problems.push(`the newest security-review verdict was cast against brief hash ${security.briefHash ?? "none"}, the frozen brief's current hash is ${currentManifest.briefHash ?? "none"}`);
4109
+ } else if (!securityBinding.policyDigest) {
4110
+ problems.push(`the newest security-review verdict was cast against review policy digest ${security.policyDigest ?? "none"}, this repository's current policy digest is ${currentManifest.policyDigest}`);
3728
4111
  }
3729
4112
  if (!mechanicalChecksClean) {
3730
4113
  problems.push(reportedMechanicalChecks.length === 0 ? "no mechanical checks have reported for this head yet" : `mechanical check(s) not green: ${reportedMechanicalChecks.filter((c) => c.bucket !== "pass").map((c) => `${c.name} (${c.bucket})`).join(", ")}`);
@@ -3741,6 +4124,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
3741
4124
  function isChangesetsReleasePr(branch, author, expectedAuthor) {
3742
4125
  return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
3743
4126
  }
4127
+ // ../../packages/aeg-core/src/ruling-ordinal.ts
4128
+ var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
4129
+ function newestPrincipalRulingOrdinal(comments, allowlist) {
4130
+ let best = 0;
4131
+ for (const c of comments) {
4132
+ if (!isPrincipal(c.author, allowlist))
4133
+ continue;
4134
+ const firstLine = (c.body.split(`
4135
+ `)[0] ?? "").trim();
4136
+ const m = RULING_MARKER_ORDINAL.exec(firstLine);
4137
+ if (!m)
4138
+ continue;
4139
+ const k = Number.parseInt(m[1], 10);
4140
+ if (k > best)
4141
+ best = k;
4142
+ }
4143
+ return best;
4144
+ }
3744
4145
  // ../../packages/aeg-core/src/markdown-table.ts
3745
4146
  function splitRow(line) {
3746
4147
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -4398,42 +4799,45 @@ function checkDispatchReadiness(input) {
4398
4799
  const { trancheSlug, task } = input;
4399
4800
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
4400
4801
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
4401
- const blockers = [];
4802
+ const blockerDetails = [];
4803
+ const push = (blockerClass, message) => {
4804
+ blockerDetails.push({ class: blockerClass, message });
4805
+ };
4402
4806
  if (task.issue === null) {
4403
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
4807
+ push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
4404
4808
  } else if (input.issue === null) {
4405
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4809
+ push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4406
4810
  }
4407
4811
  if (input.issue !== null && !input.issueRationalePass) {
4408
- blockers.push(`dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
4812
+ push("rationale", `dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
4409
4813
  }
4410
4814
  for (const dep of input.dependsOn) {
4411
4815
  if (isSelfDependency(dep, task, input.issue)) {
4412
4816
  const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4413
- blockers.push(`dispatch-gate INTERNAL: parsed a self-dependency for task ${task.id} (${issueStr}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream. Re-run once the rationale is corrected or the fix ships.`);
4817
+ push("internal-self-dependency", `dispatch-gate INTERNAL: parsed a self-dependency for task ${task.id} (${issueStr}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream. Re-run once the rationale is corrected or the fix ships.`);
4414
4818
  continue;
4415
4819
  }
4416
4820
  if (dep.resolved === false) {
4417
- blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on "${dep.id}", which is UNRESOLVABLE — the resolver could not find a matching tranche/task/Issue for this edge (not a claim about merge status). Not dispatchable until the edge is corrected.`);
4821
+ push("depends-on-unresolvable", `dispatch-gate depends-on: ${taskLabel} depends on "${dep.id}", which is UNRESOLVABLE — the resolver could not find a matching tranche/task/Issue for this edge (not a claim about merge status). Not dispatchable until the edge is corrected.`);
4418
4822
  continue;
4419
4823
  }
4420
4824
  if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
4421
4825
  const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
4422
- blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
4826
+ push("depends-on-not-merged", `dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
4423
4827
  }
4424
4828
  }
4425
4829
  for (const c of input.conflictsWith) {
4426
4830
  if (c.openOrInFlight) {
4427
4831
  const issueStr = c.issue !== null ? ` (#${c.issue})` : "";
4428
- blockers.push(`dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
4832
+ push("conflicts-with", `dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
4429
4833
  }
4430
4834
  }
4431
4835
  for (const proj of input.priorTrancheArchival) {
4432
4836
  if (proj.priorTrancheSlug !== null && !proj.archived) {
4433
- blockers.push(`dispatch-gate prior-tranche-archival: project \`${proj.project}\`'s previous tranche \`${proj.priorTrancheSlug}\` is not archived — the Tranche Archivist must run before new work on this product.`);
4837
+ push("prior-tranche-archival", `dispatch-gate prior-tranche-archival: project \`${proj.project}\`'s previous tranche \`${proj.priorTrancheSlug}\` is not archived — the Tranche Archivist must run before new work on this product.`);
4434
4838
  }
4435
4839
  }
4436
- return { ready: blockers.length === 0, blockers };
4840
+ return { ready: blockerDetails.length === 0, blockers: blockerDetails.map((b) => b.message), blockerDetails };
4437
4841
  }
4438
4842
  // ../../packages/aeg-core/src/single-plan-pr.ts
4439
4843
  function trancheSlugFromTopologyPath(path) {
@@ -4535,6 +4939,91 @@ function checkBranchTopology(input) {
4535
4939
  reason: `Branch \`${branch}\` matches topology row \`${taskId}\` in ${topoPath}.`
4536
4940
  };
4537
4941
  }
4942
+ // ../../packages/aeg-core/src/task-tools.ts
4943
+ import { z } from "zod";
4944
+ var TASK_TOOL_ERROR_KINDS = [
4945
+ "validation",
4946
+ "authority",
4947
+ "precondition",
4948
+ "capability",
4949
+ "infrastructure",
4950
+ "cancellation",
4951
+ "timeout",
4952
+ "uncertain_effect"
4953
+ ];
4954
+ var TaskToolErrorSchema = z.object({
4955
+ kind: z.enum(TASK_TOOL_ERROR_KINDS),
4956
+ message: z.string().min(1),
4957
+ detail: z.string().optional()
4958
+ });
4959
+ var TaskToolRefSchema = z.union([
4960
+ z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
4961
+ z.object({ issue: z.number().int().positive() })
4962
+ ]);
4963
+ var MAX_PAGE_LIMIT = 100;
4964
+ var PageRequestSchema = z.object({
4965
+ cursor: z.string().optional(),
4966
+ limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
4967
+ });
4968
+ var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
4969
+ var ObservedSchema = z.object({
4970
+ observedAt: z.string(),
4971
+ freshness: FreshnessSchema
4972
+ });
4973
+ var TaskStatusInputSchema = z.object({
4974
+ task: TaskToolRefSchema.optional()
4975
+ }).merge(PageRequestSchema);
4976
+ var TaskStatusItemSchema = z.object({
4977
+ task: TaskToolRefSchema,
4978
+ issue: z.number().int().positive(),
4979
+ pr: z.number().int().positive().nullable(),
4980
+ state: z.string()
4981
+ }).merge(ObservedSchema);
4982
+ var TaskStatusResultSchema = z.object({
4983
+ items: z.array(TaskStatusItemSchema),
4984
+ nextCursor: z.string().nullable()
4985
+ });
4986
+ var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
4987
+ var EscalationInputsSchema = z.object({
4988
+ task: z.number().int().positive(),
4989
+ round: z.number().int().nonnegative(),
4990
+ head: z.string(),
4991
+ branch: z.string(),
4992
+ prNumber: z.number().int().positive()
4993
+ });
4994
+ var EscalationEvidenceSchema = z.object({
4995
+ round: z.number().int().nonnegative(),
4996
+ reviewer: z.string().nullable(),
4997
+ security: z.string().nullable()
4998
+ });
4999
+ var TaskEscalationReadInputSchema = z.object({
5000
+ task: TaskToolRefSchema
5001
+ }).merge(PageRequestSchema);
5002
+ var TaskEscalationPacketSchema = z.object({
5003
+ reason: z.string(),
5004
+ detail: z.string().nullable(),
5005
+ inputs: EscalationInputsSchema.nullable(),
5006
+ evidence: EscalationEvidenceSchema.nullable(),
5007
+ attemptedRecovery: z.string(),
5008
+ requestedAuthority: RequestedAuthoritySchema,
5009
+ permittedNextActions: z.array(z.string())
5010
+ }).merge(ObservedSchema);
5011
+ var TaskEscalationReadResultSchema = z.object({
5012
+ items: z.array(TaskEscalationPacketSchema),
5013
+ nextCursor: z.string().nullable()
5014
+ }).merge(ObservedSchema);
5015
+ var TaskStartInputSchema = z.object({
5016
+ tranche: z.string().min(1),
5017
+ id: z.string().min(1)
5018
+ });
5019
+ var TaskResumeInputSchema = z.object({
5020
+ task: TaskToolRefSchema
5021
+ });
5022
+ var TaskCancelInputSchema = z.object({
5023
+ task: TaskToolRefSchema,
5024
+ reason: z.string().min(1)
5025
+ });
5026
+ var NoResultSchema = z.never();
4538
5027
  // ../../packages/aeg-core/src/first-push-dispatch-gate.ts
4539
5028
  function parseTaskBranch(branch) {
4540
5029
  const m = /^task\/([^/]+)\/([^/]+)$/.exec(branch);
@@ -4618,7 +5107,6 @@ function decideIssueAssignment(input) {
4618
5107
  };
4619
5108
  }
4620
5109
  // ../../packages/aeg-core/src/test-plan-gate.ts
4621
- var TASK_BRANCH_PATTERN2 = /^task\/[^/]+\/[^/]+$/;
4622
5110
  function evaluateTestPlanGate(body, branch) {
4623
5111
  if (!body) {
4624
5112
  return {
@@ -4631,7 +5119,7 @@ function evaluateTestPlanGate(body, branch) {
4631
5119
  }
4632
5120
  const located = locateTestPlanSection(body);
4633
5121
  if (!located.found) {
4634
- if (TASK_BRANCH_PATTERN2.test(branch)) {
5122
+ if (parseTaskBranchIdentity(branch) !== null) {
4635
5123
  return {
4636
5124
  verdict: "fail",
4637
5125
  messages: [
@@ -4759,7 +5247,7 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4759
5247
  return findings;
4760
5248
  }
4761
5249
  // ../../packages/aeg-core/src/log/schema.ts
4762
- import { z } from "zod";
5250
+ import { z as z2 } from "zod";
4763
5251
  var ROLE_VALUES = [
4764
5252
  "planner",
4765
5253
  "developer",
@@ -4769,136 +5257,172 @@ var ROLE_VALUES = [
4769
5257
  "archivist",
4770
5258
  "architect"
4771
5259
  ];
4772
- var RoleSchema = z.enum(ROLE_VALUES);
5260
+ var RoleSchema = z2.enum(ROLE_VALUES);
4773
5261
  var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4774
- var HostSchema = z.enum(HOST_VALUES);
5262
+ var HostSchema = z2.enum(HOST_VALUES);
4775
5263
  var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4776
- var HeaderMetaSchema = z.object({
4777
- schema: z.literal(1),
4778
- ts: z.string(),
4779
- run_id: z.string().regex(RUN_ID_PATTERN),
4780
- seq: z.number().int().nonnegative(),
4781
- repo: z.string().nullable(),
4782
- vinaya: z.string(),
4783
- doctrine: z.string(),
5264
+ var headerMetaCore = {
5265
+ ts: z2.string(),
5266
+ run_id: z2.string().regex(RUN_ID_PATTERN),
5267
+ seq: z2.number().int().nonnegative(),
5268
+ repo: z2.string().nullable(),
5269
+ vinaya: z2.string(),
5270
+ doctrine: z2.string(),
4784
5271
  host: HostSchema,
4785
- machine: z.string()
5272
+ machine: z2.string()
5273
+ };
5274
+ var HeaderMetaV1Schema = z2.object({
5275
+ schema: z2.literal(1),
5276
+ ...headerMetaCore
5277
+ }).strict();
5278
+ var LineageSchema = z2.object({
5279
+ run: z2.string().nullable(),
5280
+ attempt: z2.number().int().nullable(),
5281
+ parent: z2.string().nullable()
5282
+ }).strict();
5283
+ var InputVersionsSchema = z2.object({
5284
+ objectives_version: z2.string().nullable(),
5285
+ brief_hash: z2.string().nullable(),
5286
+ ruling_ordinal: z2.number().int().nullable(),
5287
+ policy_digest: z2.string().nullable()
4786
5288
  }).strict();
4787
- var SubjectSchema = z.object({
4788
- issue: z.number().int().nullable(),
4789
- pr: z.number().int().optional(),
4790
- sha: z.string().optional(),
4791
- role: z.union([RoleSchema, z.literal("unattributed")]),
4792
- round: z.number().int().optional(),
4793
- objectives_version: z.string().optional()
5289
+ var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
5290
+ var HeaderMetaV2Schema = z2.object({
5291
+ schema: z2.literal(2),
5292
+ ...headerMetaCore,
5293
+ event_id: z2.string().min(1),
5294
+ process_id: z2.string().min(1),
5295
+ actor_id: z2.string().nullable(),
5296
+ lineage: LineageSchema,
5297
+ input_versions: InputVersionsSchema,
5298
+ provenance: ProvenanceSchema
4794
5299
  }).strict();
4795
- var HeaderSchema = z.object({
5300
+ var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
5301
+ var SubjectSchema = z2.object({
5302
+ issue: z2.number().int().nullable(),
5303
+ pr: z2.number().int().optional(),
5304
+ sha: z2.string().optional(),
5305
+ role: z2.union([RoleSchema, z2.literal("unattributed")]),
5306
+ round: z2.number().int().optional(),
5307
+ objectives_version: z2.string().optional()
5308
+ }).strict();
5309
+ var HeaderSchema = z2.object({
4796
5310
  meta: HeaderMetaSchema,
4797
5311
  subject: SubjectSchema
4798
5312
  }).strict();
4799
5313
  var envelopeTail = {
4800
- duration_ms: z.number().nonnegative().optional(),
4801
- payload: z.object({}).strict()
5314
+ duration_ms: z2.number().nonnegative().optional(),
5315
+ payload: z2.object({}).strict()
4802
5316
  };
4803
- var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4804
- z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4805
- z.object({
4806
- type: z.literal("round_pushed"),
4807
- pr: z.number().int(),
4808
- head: z.string(),
4809
- comment_id: z.number().int()
5317
+ var ReviewFindingSchema = z2.object({
5318
+ id: z2.string(),
5319
+ severity: z2.string(),
5320
+ state: z2.string().optional(),
5321
+ severity_scale: z2.string().optional(),
5322
+ policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
5323
+ confidence: z2.number().min(0).max(1).optional(),
5324
+ confidence_scale: z2.string().optional(),
5325
+ confidence_source: z2.string().optional()
5326
+ }).strict();
5327
+ var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
5328
+ z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
5329
+ z2.object({
5330
+ type: z2.literal("round_pushed"),
5331
+ pr: z2.number().int(),
5332
+ head: z2.string(),
5333
+ comment_id: z2.number().int()
4810
5334
  }).strict(),
4811
- z.object({
4812
- type: z.literal("verdict"),
4813
- verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4814
- head: z.string(),
4815
- comment_id: z.number().int(),
4816
- objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4817
- findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
5335
+ z2.object({
5336
+ type: z2.literal("verdict"),
5337
+ verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
5338
+ head: z2.string(),
5339
+ comment_id: z2.number().int(),
5340
+ objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
5341
+ findings: z2.array(ReviewFindingSchema)
4818
5342
  }).strict(),
4819
- z.object({
4820
- type: z.literal("escalation"),
4821
- class: z.enum(["authority", "strategy", "product"]),
4822
- comment_id: z.number().int()
5343
+ z2.object({
5344
+ type: z2.literal("escalation"),
5345
+ class: z2.enum(["authority", "strategy", "product"]),
5346
+ comment_id: z2.number().int()
4823
5347
  }).strict(),
4824
- z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4825
- z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4826
- z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
5348
+ z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
5349
+ z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
5350
+ z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict()
4827
5351
  ]);
4828
5352
  var dispatchShared = {
4829
5353
  meta: HeaderMetaSchema,
4830
5354
  subject: SubjectSchema,
4831
- kind: z.literal("dispatch"),
5355
+ kind: z2.literal("dispatch"),
4832
5356
  ...envelopeTail,
4833
5357
  target_role: RoleSchema,
4834
- model: z.string(),
4835
- round: z.number().int().optional(),
4836
- effect_id: z.string()
5358
+ model: z2.string(),
5359
+ round: z2.number().int().optional(),
5360
+ effect_id: z2.string()
4837
5361
  };
4838
- var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
4839
- var DispatchEventSchema = z.discriminatedUnion("event", [
4840
- z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4841
- z.object({
5362
+ var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
5363
+ var DispatchEventSchema = z2.discriminatedUnion("event", [
5364
+ z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
5365
+ z2.object({
4842
5366
  ...dispatchShared,
4843
- event: z.literal("outcome_received"),
5367
+ event: z2.literal("outcome_received"),
4844
5368
  outcome: DispatchOutcomeSchema,
4845
5369
  usage: dispatchUsageField
4846
5370
  }).strict(),
4847
- z.object({
5371
+ z2.object({
4848
5372
  ...dispatchShared,
4849
- event: z.literal("dispatch_failed"),
4850
- reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
5373
+ event: z2.literal("dispatch_failed"),
5374
+ reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
4851
5375
  usage: dispatchUsageField
4852
5376
  }).strict()
4853
5377
  ]);
4854
5378
  var loopShared = {
4855
5379
  meta: HeaderMetaSchema,
4856
5380
  subject: SubjectSchema,
4857
- kind: z.literal("dev_review_loop"),
5381
+ kind: z2.literal("dev_review_loop"),
4858
5382
  ...envelopeTail,
4859
- loop_id: z.string()
5383
+ loop_id: z2.string()
4860
5384
  };
4861
- var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4862
- z.object({
5385
+ var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
5386
+ z2.object({
4863
5387
  ...loopShared,
4864
- event: z.literal("loop_started"),
4865
- task: z.number().int(),
4866
- policy: z.object({
4867
- max_rounds: z.number().int().nonnegative(),
4868
- reviewers: z.array(RoleSchema),
4869
- models: z.record(RoleSchema, z.string())
5388
+ event: z2.literal("loop_started"),
5389
+ task: z2.number().int(),
5390
+ policy: z2.object({
5391
+ max_rounds: z2.number().int().nonnegative(),
5392
+ reviewers: z2.array(RoleSchema),
5393
+ models: z2.record(RoleSchema, z2.string())
4870
5394
  }).strict()
4871
5395
  }).strict(),
4872
- z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4873
- z.object({
5396
+ z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
5397
+ z2.object({
4874
5398
  ...loopShared,
4875
- event: z.literal("gate_result_read"),
4876
- round: z.number().int(),
4877
- head: z.string(),
4878
- green: z.boolean()
5399
+ event: z2.literal("gate_result_read"),
5400
+ round: z2.number().int(),
5401
+ head: z2.string(),
5402
+ green: z2.boolean()
4879
5403
  }).strict(),
4880
- z.object({
5404
+ z2.object({
4881
5405
  ...loopShared,
4882
- event: z.literal("verdicts_read"),
4883
- round: z.number().int(),
4884
- head: z.string(),
4885
- all_approve: z.boolean(),
4886
- blockers: z.number().int().nonnegative()
5406
+ event: z2.literal("verdicts_read"),
5407
+ round: z2.number().int(),
5408
+ head: z2.string(),
5409
+ all_approve: z2.boolean(),
5410
+ blockers: z2.number().int().nonnegative()
4887
5411
  }).strict(),
4888
- z.object({
5412
+ z2.object({
4889
5413
  ...loopShared,
4890
- event: z.literal("findings_compared"),
4891
- round: z.number().int(),
4892
- open: z.array(z.string()),
4893
- resolved: z.array(z.string()),
4894
- new: z.array(z.string()),
4895
- recurring: z.array(z.string())
5414
+ event: z2.literal("findings_compared"),
5415
+ round: z2.number().int(),
5416
+ open: z2.array(z2.string()),
5417
+ resolved: z2.array(z2.string()),
5418
+ new: z2.array(z2.string()),
5419
+ recurring: z2.array(z2.string())
4896
5420
  }).strict(),
4897
- z.object({
5421
+ z2.object({
4898
5422
  ...loopShared,
4899
- event: z.literal("stop_condition_met"),
4900
- round: z.number().int(),
4901
- condition: z.enum([
5423
+ event: z2.literal("stop_condition_met"),
5424
+ round: z2.number().int(),
5425
+ condition: z2.enum([
4902
5426
  "green",
4903
5427
  "max_rounds",
4904
5428
  "no_progress",
@@ -4908,42 +5432,49 @@ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4908
5432
  "reappearance"
4909
5433
  ])
4910
5434
  }).strict(),
4911
- z.object({
5435
+ z2.object({
4912
5436
  ...loopShared,
4913
- event: z.literal("paused"),
4914
- round: z.number().int(),
4915
- reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
5437
+ event: z2.literal("paused"),
5438
+ round: z2.number().int(),
5439
+ reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
4916
5440
  }).strict(),
4917
- z.object({
5441
+ z2.object({
4918
5442
  ...loopShared,
4919
- event: z.literal("resumed"),
4920
- round: z.number().int(),
4921
- by: z.literal("principal")
5443
+ event: z2.literal("resumed"),
5444
+ round: z2.number().int(),
5445
+ by: z2.literal("principal")
4922
5446
  }).strict(),
4923
- z.object({
5447
+ z2.object({
4924
5448
  ...loopShared,
4925
- event: z.literal("round_ended"),
4926
- round: z.number().int(),
4927
- base_head: z.string(),
4928
- head: z.string(),
4929
- files_changed: z.number().int().nonnegative(),
4930
- insertions: z.number().int().nonnegative(),
4931
- deletions: z.number().int().nonnegative(),
4932
- wall_ms: z.number().nonnegative(),
4933
- outcome: z.enum(["green", "changes_requested", "escalated"])
5449
+ event: z2.literal("unpushed_work_resume"),
5450
+ round: z2.number().int(),
5451
+ branch: z2.string(),
5452
+ detail: z2.string()
4934
5453
  }).strict(),
4935
- z.object({
5454
+ z2.object({
4936
5455
  ...loopShared,
4937
- event: z.literal("journal_finalized"),
4938
- rounds: z.number().int().nonnegative(),
4939
- total_wall_ms: z.number().nonnegative(),
4940
- time_to_green_ms: z.number().nonnegative().nullable(),
4941
- files_changed_total: z.number().int().nonnegative(),
4942
- final_head: z.string(),
4943
- result: z.enum(["merged_ready", "stopped"])
5456
+ event: z2.literal("round_ended"),
5457
+ round: z2.number().int(),
5458
+ base_head: z2.string(),
5459
+ head: z2.string(),
5460
+ files_changed: z2.number().int().nonnegative(),
5461
+ insertions: z2.number().int().nonnegative(),
5462
+ deletions: z2.number().int().nonnegative(),
5463
+ wall_ms: z2.number().nonnegative(),
5464
+ outcome: z2.enum(["green", "changes_requested", "escalated"])
5465
+ }).strict(),
5466
+ z2.object({
5467
+ ...loopShared,
5468
+ event: z2.literal("journal_finalized"),
5469
+ rounds: z2.number().int().nonnegative(),
5470
+ total_wall_ms: z2.number().nonnegative(),
5471
+ time_to_green_ms: z2.number().nonnegative().nullable(),
5472
+ files_changed_total: z2.number().int().nonnegative(),
5473
+ final_head: z2.string(),
5474
+ result: z2.enum(["merged_ready", "stopped"])
4944
5475
  }).strict()
4945
5476
  ]);
4946
- var ForgeOpSchema = z.enum([
5477
+ var ForgeOpSchema = z2.enum([
4947
5478
  "pr.create",
4948
5479
  "pr.comment",
4949
5480
  "pr.body.replace",
@@ -4957,54 +5488,242 @@ var ForgeOpSchema = z.enum([
4957
5488
  "label.add",
4958
5489
  "label.remove"
4959
5490
  ]);
4960
- var ForgeWriteTargetSchema = z.object({
4961
- issue: z.number().int().optional(),
4962
- pr: z.number().int().optional()
5491
+ var ForgeWriteTargetSchema = z2.object({
5492
+ issue: z2.number().int().optional(),
5493
+ pr: z2.number().int().optional()
4963
5494
  }).strict();
4964
5495
  var forgeWriteShared = {
4965
5496
  meta: HeaderMetaSchema,
4966
5497
  subject: SubjectSchema,
4967
- kind: z.literal("forge_write"),
5498
+ kind: z2.literal("forge_write"),
4968
5499
  ...envelopeTail,
4969
5500
  op: ForgeOpSchema,
4970
5501
  target: ForgeWriteTargetSchema
4971
5502
  };
4972
- var ForgeWriteEventSchema = z.discriminatedUnion("event", [
4973
- z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
4974
- z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
4975
- z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
5503
+ var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
5504
+ z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
5505
+ z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
5506
+ z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
5507
+ ]);
5508
+ var GateOutcomeSchema = z2.enum([
5509
+ "pass",
5510
+ "fail",
5511
+ "wait",
5512
+ "skip",
5513
+ "invalid_input",
5514
+ "unavailable_dependency",
5515
+ "timeout",
5516
+ "cancelled"
5517
+ ]);
5518
+ var gateShared = {
5519
+ meta: HeaderMetaSchema,
5520
+ subject: SubjectSchema,
5521
+ kind: z2.literal("gate"),
5522
+ ...envelopeTail,
5523
+ check: z2.string(),
5524
+ check_version: z2.string().nullable(),
5525
+ policy_version: z2.string().nullable(),
5526
+ input_fingerprint: z2.string().nullable()
5527
+ };
5528
+ var GateEventSchema = z2.discriminatedUnion("event", [
5529
+ z2.object({
5530
+ ...gateShared,
5531
+ event: z2.literal("checked"),
5532
+ outcome: GateOutcomeSchema,
5533
+ reason: z2.string().optional()
5534
+ }).strict()
5535
+ ]);
5536
+ var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
5537
+ var operationShared = {
5538
+ meta: HeaderMetaSchema,
5539
+ subject: SubjectSchema,
5540
+ kind: z2.literal("operation"),
5541
+ ...envelopeTail,
5542
+ operation: z2.string(),
5543
+ target: z2.string().nullable()
5544
+ };
5545
+ var OperationEventSchema = z2.discriminatedUnion("event", [
5546
+ z2.object({
5547
+ ...operationShared,
5548
+ event: z2.literal("completed"),
5549
+ result: OperationResultSchema,
5550
+ error_class: z2.string().nullable()
5551
+ }).strict()
5552
+ ]);
5553
+ var UsageUnitsSchema = z2.object({
5554
+ input: z2.number().nonnegative().nullable(),
5555
+ output: z2.number().nonnegative().nullable(),
5556
+ cache: z2.number().nonnegative().nullable()
5557
+ }).strict();
5558
+ var usageShared = {
5559
+ meta: HeaderMetaSchema,
5560
+ subject: SubjectSchema,
5561
+ kind: z2.literal("usage"),
5562
+ ...envelopeTail,
5563
+ model: z2.string().nullable(),
5564
+ source: z2.string(),
5565
+ semantics: z2.enum(["cumulative", "delta"])
5566
+ };
5567
+ var UsageEventSchema = z2.discriminatedUnion("event", [
5568
+ z2.object({
5569
+ ...usageShared,
5570
+ event: z2.literal("observed"),
5571
+ units: UsageUnitsSchema,
5572
+ unknown_reason: z2.string().nullable()
5573
+ }).strict()
5574
+ ]);
5575
+ var RoleAttemptOutcomeSchema = z2.enum([
5576
+ "completed",
5577
+ "incomplete",
5578
+ "infrastructure_failed",
5579
+ "cancelled",
5580
+ "timed_out",
5581
+ "capability_refused"
5582
+ ]);
5583
+ var roleAttemptShared = {
5584
+ meta: HeaderMetaSchema,
5585
+ subject: SubjectSchema,
5586
+ kind: z2.literal("role_attempt"),
5587
+ ...envelopeTail,
5588
+ actor: z2.string().nullable(),
5589
+ attempt: z2.number().int().nullable()
5590
+ };
5591
+ var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
5592
+ z2.object({
5593
+ ...roleAttemptShared,
5594
+ event: z2.literal("attempted"),
5595
+ outcome: RoleAttemptOutcomeSchema,
5596
+ usage: dispatchUsageField
5597
+ }).strict()
5598
+ ]);
5599
+ var handoffShared = {
5600
+ meta: HeaderMetaSchema,
5601
+ subject: SubjectSchema,
5602
+ kind: z2.literal("handoff"),
5603
+ ...envelopeTail,
5604
+ class: z2.enum(["authority", "strategy", "product"]),
5605
+ reason: z2.string()
5606
+ };
5607
+ var HandoffEventSchema = z2.discriminatedUnion("event", [
5608
+ z2.object({
5609
+ ...handoffShared,
5610
+ event: z2.literal("raised"),
5611
+ requested_decision: z2.string().nullable()
5612
+ }).strict(),
5613
+ z2.object({
5614
+ ...handoffShared,
5615
+ event: z2.literal("resolved"),
5616
+ resolution: z2.string().nullable(),
5617
+ resolved_by: z2.string().nullable()
5618
+ }).strict()
5619
+ ]);
5620
+ var EffectTargetSchema = z2.object({
5621
+ kind: z2.string(),
5622
+ ref: z2.string()
5623
+ }).strict();
5624
+ var effectShared = {
5625
+ meta: HeaderMetaSchema,
5626
+ subject: SubjectSchema,
5627
+ kind: z2.literal("effect"),
5628
+ ...envelopeTail,
5629
+ effect_id: z2.string(),
5630
+ target: EffectTargetSchema
5631
+ };
5632
+ var EffectEventSchema = z2.discriminatedUnion("event", [
5633
+ z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
5634
+ z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
5635
+ z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
5636
+ ]);
5637
+ var LogEventSchema = z2.union([
5638
+ DispatchEventSchema,
5639
+ DevReviewLoopEventSchema,
5640
+ ForgeWriteEventSchema,
5641
+ GateEventSchema,
5642
+ OperationEventSchema,
5643
+ UsageEventSchema,
5644
+ RoleAttemptEventSchema,
5645
+ HandoffEventSchema,
5646
+ EffectEventSchema
4976
5647
  ]);
4977
- var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
5648
+ // ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
5649
+ var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
5650
+ // ../../packages/aeg-core/src/control-store/records.ts
5651
+ import { z as z3 } from "zod";
5652
+ var isoTimestamp = z3.string().min(1);
5653
+ var taskId = z3.number().int().positive();
5654
+ var epochNumber = z3.number().int().nonnegative();
5655
+ var RunRecordSchema = z3.object({
5656
+ version: z3.literal(1),
5657
+ kind: z3.literal("run"),
5658
+ task: taskId,
5659
+ runId: z3.string().min(1),
5660
+ pid: z3.number().int().positive(),
5661
+ host: z3.string().min(1),
5662
+ startedAt: isoTimestamp
5663
+ }).strict();
5664
+ var InputRecordSchema = z3.object({
5665
+ version: z3.literal(1),
5666
+ kind: z3.literal("input"),
5667
+ task: taskId,
5668
+ runId: z3.string().min(1),
5669
+ source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
5670
+ pr: z3.number().int().positive().nullable(),
5671
+ round: z3.number().int().nonnegative(),
5672
+ recordedAt: isoTimestamp
5673
+ }).strict();
5674
+ var OwnershipRecordSchema = z3.object({
5675
+ version: z3.literal(1),
5676
+ kind: z3.literal("ownership"),
5677
+ task: taskId,
5678
+ epoch: epochNumber,
5679
+ ownerId: z3.string().min(1),
5680
+ pid: z3.number().int().positive(),
5681
+ host: z3.string().min(1),
5682
+ acquiredAt: isoTimestamp
5683
+ }).strict();
5684
+ var TransitionRecordSchema = z3.object({
5685
+ version: z3.literal(1),
5686
+ kind: z3.literal("transition"),
5687
+ task: taskId,
5688
+ epoch: epochNumber,
5689
+ seq: z3.number().int().nonnegative(),
5690
+ from: z3.string().min(1),
5691
+ to: z3.string().min(1),
5692
+ detail: z3.string().optional(),
5693
+ at: isoTimestamp
5694
+ }).strict();
4978
5695
  // src/lib/diff-evidence.ts
4979
- import { execFileSync as execFileSync4 } from "node:child_process";
4980
- import { join as join2, relative, resolve as resolve2 } from "node:path";
5696
+ import { execFileSync as execFileSync5 } from "node:child_process";
5697
+ import { join as join4, relative, resolve as resolve2 } from "node:path";
4981
5698
 
4982
5699
  // src/lib/config.ts
4983
5700
  import { execFileSync as execFileSync2 } from "node:child_process";
4984
5701
  import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
4985
5702
  import { homedir } from "node:os";
4986
5703
  import { dirname, join, resolve } from "node:path";
4987
- import { z as z2 } from "zod";
5704
+ import { z as z4 } from "zod";
4988
5705
 
4989
5706
  // src/lib/agent-vendors.ts
4990
5707
  var AGENT_VENDORS = ["skills", "claude", "gemini"];
4991
5708
 
4992
5709
  // src/lib/config.ts
4993
- var EnvEntrySchema = z2.union([
4994
- z2.literal(true),
4995
- z2.object({ optional: z2.literal(true) }),
4996
- z2.object({ anyOf: z2.array(z2.string()).min(2) }),
4997
- z2.string()
5710
+ var EnvEntrySchema = z4.union([
5711
+ z4.literal(true),
5712
+ z4.object({ optional: z4.literal(true) }),
5713
+ z4.object({ anyOf: z4.array(z4.string()).min(2) }),
5714
+ z4.string()
4998
5715
  ]);
4999
- var CheckEntrySchema = z2.object({
5000
- run: z2.string(),
5001
- scope: z2.enum(["diff", "full"]),
5002
- include: z2.array(z2.string()).optional(),
5003
- args: z2.array(z2.string()).optional(),
5004
- timeoutMs: z2.number().optional(),
5005
- env: z2.record(z2.string(), EnvEntrySchema).optional(),
5006
- requiresOpenPr: z2.boolean().optional(),
5007
- ownWorkflow: z2.boolean().optional()
5716
+ var CheckEntrySchema = z4.object({
5717
+ run: z4.string(),
5718
+ scope: z4.enum(["diff", "full"]),
5719
+ include: z4.array(z4.string()).optional(),
5720
+ args: z4.array(z4.string()).optional(),
5721
+ timeoutMs: z4.number().optional(),
5722
+ env: z4.record(z4.string(), EnvEntrySchema).optional(),
5723
+ requiresOpenPr: z4.boolean().optional(),
5724
+ ownWorkflow: z4.boolean().optional(),
5725
+ principalOwed: z4.literal(true).optional(),
5726
+ validates: z4.enum(["body", "issue"]).optional()
5008
5727
  }).superRefine((entry2, ctx) => {
5009
5728
  if (!entry2.env)
5010
5729
  return;
@@ -5014,25 +5733,26 @@ var CheckEntrySchema = z2.object({
5014
5733
  const members = value.anyOf;
5015
5734
  if (new Set(members).size !== members.length) {
5016
5735
  ctx.addIssue({
5017
- code: z2.ZodIssueCode.custom,
5736
+ code: z4.ZodIssueCode.custom,
5018
5737
  message: `env.${key}.anyOf has duplicate members`,
5019
5738
  path: ["env", key, "anyOf"]
5020
5739
  });
5021
5740
  }
5022
5741
  if (!members.includes(key)) {
5023
5742
  ctx.addIssue({
5024
- code: z2.ZodIssueCode.custom,
5743
+ code: z4.ZodIssueCode.custom,
5025
5744
  message: `env.${key}.anyOf must include "${key}" itself as a member`,
5026
5745
  path: ["env", key, "anyOf"]
5027
5746
  });
5028
5747
  }
5029
5748
  }
5030
5749
  });
5031
- var RoleEntrySchema = z2.object({
5032
- contract: z2.string().refine((p) => p.includes("/"), {
5750
+ var RoleEntrySchema = z4.object({
5751
+ contract: z4.string().refine((p) => p.includes("/"), {
5033
5752
  message: 'must be a path (contain at least one "/"), not a bare filename'
5034
5753
  })
5035
5754
  });
5755
+ var MAX_REPORT_COMMAND_TIMEOUT_MS = 3600000;
5036
5756
  var BRIEF_BUILTINS = [
5037
5757
  "tier",
5038
5758
  "testPlan",
@@ -5052,17 +5772,17 @@ var BRIEF_BUILTINS = [
5052
5772
  "briefSections",
5053
5773
  "milestoneShape"
5054
5774
  ];
5055
- var BriefSectionSchema = z2.union([
5056
- z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
5057
- z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
5058
- z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
5059
- z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
5775
+ var BriefSectionSchema = z4.union([
5776
+ z4.object({ builtin: z4.enum(BRIEF_BUILTINS) }),
5777
+ z4.object({ heading: z4.string().min(1), name: z4.string().optional() }),
5778
+ z4.object({ field: z4.string().min(1), name: z4.string().optional() }),
5779
+ z4.object({ phrase: z4.string().min(1), name: z4.string().optional() })
5060
5780
  ]);
5061
- var BriefSchemaSchema = z2.object({
5062
- pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5063
- issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5064
- milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5065
- ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
5781
+ var BriefSchemaSchema = z4.object({
5782
+ pr: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5783
+ issue: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5784
+ milestone: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5785
+ ack: z4.array(z4.enum(BRIEF_BUILTINS)).optional()
5066
5786
  });
5067
5787
  function isSafeRepoRelPath(p) {
5068
5788
  if (p.length === 0)
@@ -5071,7 +5791,7 @@ function isSafeRepoRelPath(p) {
5071
5791
  return false;
5072
5792
  return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
5073
5793
  }
5074
- var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
5794
+ var SafeRepoRelPath = z4.string().refine(isSafeRepoRelPath, {
5075
5795
  message: "must be a repo-root-relative path with no `..` segment or absolute root"
5076
5796
  });
5077
5797
  var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
@@ -5081,22 +5801,22 @@ function isCanonicalHookBlockPath(p) {
5081
5801
  var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
5082
5802
  message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
5083
5803
  });
5084
- var ManagedBlockRecordSchema = z2.object({
5804
+ var ManagedBlockRecordSchema = z4.object({
5085
5805
  path: ManagedHookBlockPath,
5086
- marker: z2.string(),
5087
- comment: z2.enum(["hash", "html"])
5806
+ marker: z4.string(),
5807
+ comment: z4.enum(["hash", "html"])
5088
5808
  });
5089
- var ManagedManifestSchema = z2.object({
5090
- version: z2.number().int().positive(),
5091
- files: z2.array(SafeRepoRelPath),
5092
- blocks: z2.array(ManagedBlockRecordSchema),
5093
- labels: z2.array(z2.string()),
5094
- agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
5809
+ var ManagedManifestSchema = z4.object({
5810
+ version: z4.number().int().positive(),
5811
+ files: z4.array(SafeRepoRelPath),
5812
+ blocks: z4.array(ManagedBlockRecordSchema),
5813
+ labels: z4.array(z4.string()),
5814
+ agents: z4.array(z4.enum(AGENT_VENDORS)).optional()
5095
5815
  });
5096
- var ProjectEntrySchema = z2.object({
5097
- name: z2.string().min(1),
5098
- description: z2.string().min(1).optional(),
5099
- path: z2.string().min(1).optional()
5816
+ var ProjectEntrySchema = z4.object({
5817
+ name: z4.string().min(1),
5818
+ description: z4.string().min(1).optional(),
5819
+ path: z4.string().min(1).optional()
5100
5820
  });
5101
5821
  function parseTokensCollectDeclaration(value) {
5102
5822
  const m = value.trim().match(/^(\S+)\s+(\S+)$/);
@@ -5107,34 +5827,55 @@ function parseTokensCollectDeclaration(value) {
5107
5827
  return null;
5108
5828
  return { interpreter, script };
5109
5829
  }
5110
- var VinayaConfigSchema = z2.object({
5111
- rings: z2.object({
5112
- ring1_forgeWriteInterception: z2.boolean(),
5113
- ring2_asyncAudits: z2.boolean()
5830
+ var VinayaConfigSchema = z4.object({
5831
+ rings: z4.object({
5832
+ ring1_forgeWriteInterception: z4.boolean(),
5833
+ ring2_asyncAudits: z4.boolean()
5114
5834
  }).optional(),
5115
- checks: z2.record(z2.string(), CheckEntrySchema).optional(),
5116
- roles: z2.record(z2.string(), RoleEntrySchema).optional(),
5835
+ checks: z4.record(z4.string(), CheckEntrySchema).optional(),
5836
+ roles: z4.record(z4.string(), RoleEntrySchema).optional(),
5117
5837
  briefSchema: BriefSchemaSchema.optional(),
5118
5838
  managed: ManagedManifestSchema.optional(),
5119
- principals: z2.array(z2.string()).min(1).optional(),
5120
- releaseActor: z2.string().min(1).optional(),
5121
- ci: z2.object({ setup: z2.string().min(1) }).optional(),
5122
- tokens: z2.object({
5123
- collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5839
+ principals: z4.array(z4.string()).min(1).optional(),
5840
+ releaseActor: z4.string().min(1).optional(),
5841
+ ci: z4.object({ setup: z4.string().min(1) }).optional(),
5842
+ tokens: z4.object({
5843
+ collect: z4.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5124
5844
  message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
5125
5845
  })
5126
5846
  }).optional(),
5127
- blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
5128
- proseGates: z2.object({
5129
- doctrineRoot: z2.string().min(1).optional(),
5130
- readerFacingPrefix: z2.string().min(1).optional(),
5131
- readerFacingSuffix: z2.string().min(1).optional(),
5132
- legacySlugDir: z2.string().min(1).optional()
5847
+ blastRadius: z4.object({ extraDomains: z4.array(z4.string()).optional() }).optional(),
5848
+ proseGates: z4.object({
5849
+ doctrineRoot: z4.string().min(1).optional(),
5850
+ readerFacingPrefix: z4.string().min(1).optional(),
5851
+ readerFacingSuffix: z4.string().min(1).optional(),
5852
+ legacySlugDir: z4.string().min(1).optional()
5853
+ }).optional(),
5854
+ projects: z4.array(ProjectEntrySchema).optional(),
5855
+ dispatch: z4.object({
5856
+ timeoutMs: z4.number().int().positive().optional(),
5857
+ killGraceMs: z4.number().int().positive().optional(),
5858
+ agent: z4.enum(["claude", "codex", "gemini"]).optional()
5859
+ }).optional(),
5860
+ reviewPolicy: z4.object({
5861
+ codeReviewThreshold: z4.string().min(1).optional(),
5862
+ securityThreshold: z4.string().min(1).optional(),
5863
+ maxRounds: z4.number().optional()
5133
5864
  }).optional(),
5134
- projects: z2.array(ProjectEntrySchema).optional(),
5135
- dispatch: z2.object({
5136
- timeoutMs: z2.number().int().positive().optional(),
5137
- agent: z2.enum(["claude", "codex", "gemini"]).optional()
5865
+ prePush: z4.object({
5866
+ alwaysRun: z4.array(z4.string()).optional()
5867
+ }).optional(),
5868
+ report: z4.object({
5869
+ commandTimeoutMs: z4.number().int().positive().optional()
5870
+ }).superRefine((report, ctx) => {
5871
+ const commandTimeoutMs = report.commandTimeoutMs;
5872
+ if (commandTimeoutMs !== undefined && commandTimeoutMs > MAX_REPORT_COMMAND_TIMEOUT_MS) {
5873
+ ctx.addIssue({
5874
+ code: z4.ZodIssueCode.custom,
5875
+ path: ["commandTimeoutMs"],
5876
+ message: "must be at most 3600000 (1 hour)"
5877
+ });
5878
+ }
5138
5879
  }).optional()
5139
5880
  });
5140
5881
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
@@ -5209,6 +5950,28 @@ function resolvePrincipalAllowlist(config) {
5209
5950
  function resolveReleaseActor(config) {
5210
5951
  return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
5211
5952
  }
5953
+ function resolveReviewPolicy(config) {
5954
+ const raw = config?.reviewPolicy;
5955
+ if (!raw)
5956
+ return DEFAULT_REVIEW_POLICY;
5957
+ const codeReviewThreshold = raw.codeReviewThreshold ?? DEFAULT_REVIEW_POLICY.codeReviewThreshold;
5958
+ if (!isKnownSeverity(CODE_REVIEW_SEVERITY_ORDER, codeReviewThreshold)) {
5959
+ throw new Error(`vinaya.config.json: reviewPolicy.codeReviewThreshold "${codeReviewThreshold}" is not one of ${CODE_REVIEW_SEVERITY_ORDER.join(" > ")} — fix the config, this never falls back to a default.`);
5960
+ }
5961
+ const securityThreshold = raw.securityThreshold ?? DEFAULT_REVIEW_POLICY.securityThreshold;
5962
+ if (!isKnownSeverity(SECURITY_SEVERITY_ORDER, securityThreshold)) {
5963
+ throw new Error(`vinaya.config.json: reviewPolicy.securityThreshold "${securityThreshold}" is not one of ${SECURITY_SEVERITY_ORDER.join(" > ")} — fix the config, this never falls back to a default.`);
5964
+ }
5965
+ const maxRounds = raw.maxRounds ?? DEFAULT_REVIEW_POLICY.maxRounds;
5966
+ if (!Number.isInteger(maxRounds) || maxRounds < 1) {
5967
+ throw new Error(`vinaya.config.json: reviewPolicy.maxRounds "${maxRounds}" is not a positive integer — fix the config, this never falls back to a default.`);
5968
+ }
5969
+ return {
5970
+ codeReviewThreshold,
5971
+ securityThreshold,
5972
+ maxRounds
5973
+ };
5974
+ }
5212
5975
  function trustAnchorRepo() {
5213
5976
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
5214
5977
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -5288,6 +6051,9 @@ function loadConfig() {
5288
6051
  }
5289
6052
  var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
5290
6053
 
6054
+ // src/lib/brief-assembly.ts
6055
+ import { execFileSync as execFileSync4 } from "node:child_process";
6056
+
5291
6057
  // ../../packages/sources/src/commands.ts
5292
6058
  var COMMANDS = [
5293
6059
  {
@@ -5408,14 +6174,52 @@ var COMMANDS = [
5408
6174
  },
5409
6175
  {
5410
6176
  name: "task dispatch",
5411
- description: "Render, pin, and post the brief on the Issue as the frozen original; start the developer",
6177
+ description: "Deprecated — render, pin, and post the brief on the Issue as the frozen original; start the developer",
5412
6178
  flags: [
5413
6179
  { flag: "--agent <claude|codex|gemini>", description: "Start the developer through dispatchRole once posted" }
5414
6180
  ],
5415
6181
  details: [
5416
6182
  "Renders the brief from the Issue and the tree (the same assembly `brief render` uses) and posts it once as an Issue comment whose first line is `<!-- aeg:brief:v1 -->` and whose second line is `Brief hash: <sha256>` — the hash covers only the body below those two lines, so any reader recomputes it. Refuses outright, naming the existing comment's URL, when a `v1` comment already exists on the Issue — the brief is frozen by design, never overwritten or silently reissued.",
5417
6183
  "With `--agent`, starts the Developer through `dispatchRole` when `apps/cli/src/lib/dispatch.ts` exports it; otherwise prints the rendered brief and the manual dispatch instruction and exits `0` — a soft dependency, never a hard block.",
5418
- "Principal-only, with or without `--agent`: refuses before any render, forge read, or post when the authenticated `gh` identity is not on the Principal allowlist (or cannot be resolved at all) — dispatching is the `todo → in-flight` transition, not a general-purpose comment poster."
6184
+ "Principal-only, with or without `--agent`: refuses before any render, forge read, or post when the authenticated `gh` identity is not on the Principal allowlist (or cannot be resolved at all) — dispatching is the `todo → in-flight` transition, not a general-purpose comment poster.",
6185
+ "Deprecated in favor of `task brief` (preparation only) and `task run` (the full unattended loop) — kept for a documented compatibility window while callers migrate."
6186
+ ],
6187
+ status: "shipped"
6188
+ },
6189
+ {
6190
+ name: "task brief",
6191
+ description: "Render and freeze the brief as the Issue's original comment — preparation only, starts nobody",
6192
+ details: [
6193
+ "The preparation half of `task dispatch`, extracted so it is callable on its own: resolves the task's Issue, renders the brief, refuses on any gap, refuses when a frozen brief already exists, and posts it once as the same `aeg:brief:v1` Issue comment `task dispatch` posts. Starts no agent under any circumstances — there is no `--agent` flag here at all.",
6194
+ "Successor to `task dispatch` for the preparation step; the full unattended run (preparation, then the developer, then the review loop) is `task run`."
6195
+ ],
6196
+ status: "shipped"
6197
+ },
6198
+ {
6199
+ name: "task run",
6200
+ description: "One command from a planned Issue to a reviewed pull request — exactly one developer started",
6201
+ flags: [
6202
+ {
6203
+ flag: "--agent <claude|codex|gemini>",
6204
+ description: "Vendor for the developer and both reviewers this run dispatches"
6205
+ }
6206
+ ],
6207
+ details: [
6208
+ "Composes `task brief`'s own preparation (`prepareTask`) with `dev-review-loop` (`devReviewLoop`) — nothing else. Preparation starts no agent; the loop's own round 1 reads the frozen brief off the Issue and is the only place a developer is ever dispatched from a fresh task, so exactly one developer is started by construction.",
6209
+ "A brief already frozen on the Issue is reused, never re-posted — the second `task dispatch`/`task brief` call this composes around does not fail the whole run, it just skips straight to running the loop. A task whose Issue refuses preparation (a missing brief section, an unmet dispatch gate) is refused before any agent starts, with nothing posted.",
6210
+ "Refuses when the frozen brief's developer branch already has an open pull request — the old `task dispatch` followed by `task run` cannot start two developers this way.",
6211
+ "Exit and printed summary distinguish a published, reviewed pull request (exit `0`, the PR URL) from a pause (exit `1`, with the exact `vinaya dev-review-loop --resume <pr>` command to continue), a usage/argv error (exit `2`), and any other failure (exit `3`) — never sharing `1` with a pause, so an unattended host tells them apart from the exit code alone. A run that pauses is resumed with the loop's own existing `--resume <pr>` flag, never a flag on this command."
6212
+ ],
6213
+ status: "shipped"
6214
+ },
6215
+ {
6216
+ name: "task status",
6217
+ description: "Every open task with a frozen brief, its pull request, and whether its loop is running, paused, or published",
6218
+ flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
6219
+ details: [
6220
+ "Read-only: one `gh issue list` for every open task Issue across every tranche (title/label resolved through the same `resolveTaskIssueRef` `list-tasks.ts` already uses), the open pull request per branch, and the driver pid record / pause record / publish effect markers under `<outboxRoot>/dev-review-loop/<task>/` — never a `ps` scan, never a re-parse of posted verdict comments to decide `published`.",
6221
+ "`running` names the driver's pid (`review-validity-v1` task 7's pid record); `paused` names the reason from the pause record; `published` means the newest round's reviewer and security verdict effect markers both read `posted`; `no driver` is the fallback when none of the above holds.",
6222
+ "`vinaya task status <tranche> <n>` narrows to one task and adds the last round's held or published verdict lines (`round-<n>-reviewer.md`/`round-<n>-security.md`, whichever their outbox carries) plus the exact `vinaya dev-review-loop --resume <pr>` command when paused."
5419
6223
  ],
5420
6224
  status: "shipped"
5421
6225
  },
@@ -5623,6 +6427,15 @@ var COMMANDS = [
5623
6427
  ],
5624
6428
  status: "shipped"
5625
6429
  },
6430
+ {
6431
+ name: "milestone status",
6432
+ description: "Print each of a Milestone's declared tranche intents with its derived lifecycle and issue counts",
6433
+ flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
6434
+ details: [
6435
+ "Read-only — writes nothing. For each `- <slug>: …` line in the Milestone's `### Tranche intents` section, prints the tranche's lifecycle (`planned`/`active`/`complete`) and its labeled Issues' counts (merged, open, not planned), all derived from the forge. Refuses if `<n>` isn't a real Milestone in this repo, or if the forge is unreachable."
6436
+ ],
6437
+ status: "shipped"
6438
+ },
5626
6439
  {
5627
6440
  name: "review status",
5628
6441
  description: "Print the review loop's own state for a PR, and its branch's distance from the base",
@@ -5919,10 +6732,10 @@ function createForgeSource(config) {
5919
6732
  };
5920
6733
  }
5921
6734
  // ../../packages/sources/src/select-source.ts
5922
- import { z as z3 } from "zod";
5923
- var StateSourceConfigSchema = z3.discriminatedUnion("kind", [
5924
- z3.object({ kind: z3.literal("forge"), owner: z3.string(), repo: z3.string() }),
5925
- z3.object({ kind: z3.literal("file"), root: z3.string().optional() })
6735
+ import { z as z5 } from "zod";
6736
+ var StateSourceConfigSchema = z5.discriminatedUnion("kind", [
6737
+ z5.object({ kind: z5.literal("forge"), owner: z5.string(), repo: z5.string() }),
6738
+ z5.object({ kind: z5.literal("file"), root: z5.string().optional() })
5926
6739
  ]);
5927
6740
  // src/checks/edge-resolve.ts
5928
6741
  import { execFileSync as execFileSync3 } from "node:child_process";
@@ -6042,6 +6855,580 @@ async function resolveEdge(id, taskById, factsByTaskId, repo, resolveSibling = d
6042
6855
  };
6043
6856
  }
6044
6857
 
6858
+ // src/lib/brief-assembly.ts
6859
+ function git(args) {
6860
+ try {
6861
+ return execFileSync4("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
6862
+ } catch {
6863
+ return "";
6864
+ }
6865
+ }
6866
+ function expandGlob(glob) {
6867
+ const out = git(["ls-files", "--", glob]);
6868
+ return out.split(`
6869
+ `).map((s) => s.trim()).filter(Boolean);
6870
+ }
6871
+ var DRAFT_ISSUE_SENTINEL = Number.MAX_SAFE_INTEGER;
6872
+
6873
+ // src/checks/registry.ts
6874
+ import { existsSync as existsSync3 } from "node:fs";
6875
+ import { join as join3 } from "node:path";
6876
+
6877
+ // src/lib/package-root.ts
6878
+ import { existsSync as existsSync2 } from "node:fs";
6879
+ import { dirname as dirname2, join as join2 } from "node:path";
6880
+ import { fileURLToPath } from "node:url";
6881
+ function packageRoot(moduleUrl) {
6882
+ let dir = dirname2(fileURLToPath(moduleUrl));
6883
+ while (!existsSync2(join2(dir, "package.json"))) {
6884
+ if (existsSync2(join2(dir, ".git")))
6885
+ break;
6886
+ const parent = dirname2(dir);
6887
+ if (parent === dir)
6888
+ break;
6889
+ dir = parent;
6890
+ }
6891
+ return dir;
6892
+ }
6893
+
6894
+ // src/checks/registry.ts
6895
+ var DIST_BIN_DIR = join3(packageRoot(import.meta.url), "dist", "checks", "bin");
6896
+ var SRC_BIN_DIR = join3(packageRoot(import.meta.url), "src", "checks", "bin");
6897
+ var BIN_DIR = existsSync3(DIST_BIN_DIR) ? DIST_BIN_DIR : SRC_BIN_DIR;
6898
+ var BIN_EXT = BIN_DIR === DIST_BIN_DIR ? ".js" : ".ts";
6899
+ function bin(name) {
6900
+ return join3(BIN_DIR, `${name}${BIN_EXT}`);
6901
+ }
6902
+ var REGISTRY = [
6903
+ [
6904
+ {
6905
+ name: "brief-shape",
6906
+ validates: "body",
6907
+ run: bin("check-brief-shape"),
6908
+ scope: "diff",
6909
+ timeoutMs: 15000,
6910
+ env: {
6911
+ PR_BODY: { optional: true },
6912
+ BRANCH: { optional: true },
6913
+ PR_NUMBER: { optional: true },
6914
+ GITHUB_TOKEN: { optional: true },
6915
+ GH_TOKEN: { optional: true }
6916
+ }
6917
+ },
6918
+ 0
6919
+ ],
6920
+ [
6921
+ {
6922
+ name: "pr-report-density",
6923
+ validates: "body",
6924
+ run: bin("check-pr-report-density"),
6925
+ scope: "diff",
6926
+ timeoutMs: 15000,
6927
+ env: {
6928
+ PR_BODY: { optional: true }
6929
+ }
6930
+ },
6931
+ 0
6932
+ ],
6933
+ [
6934
+ {
6935
+ name: "doc-coverage",
6936
+ validates: "body",
6937
+ run: bin("check-doc-coverage"),
6938
+ scope: "diff",
6939
+ timeoutMs: 15000,
6940
+ env: {
6941
+ BASE_SHA: { optional: true },
6942
+ PR_BODY: { optional: true },
6943
+ PR_BODY_FILE: { optional: true },
6944
+ PR_NUMBER: { optional: true },
6945
+ GITHUB_REPOSITORY: { optional: true },
6946
+ GITHUB_TOKEN: { optional: true },
6947
+ GH_TOKEN: { optional: true }
6948
+ }
6949
+ },
6950
+ 0
6951
+ ],
6952
+ [
6953
+ {
6954
+ name: "coherence",
6955
+ run: bin("check-coherence"),
6956
+ scope: "full",
6957
+ timeoutMs: 30000,
6958
+ env: {
6959
+ AEG_REPO: { optional: true },
6960
+ BRANCH: { optional: true },
6961
+ GITHUB_TOKEN: { optional: true },
6962
+ GH_TOKEN: { optional: true }
6963
+ }
6964
+ },
6965
+ 0
6966
+ ],
6967
+ [
6968
+ {
6969
+ name: "pr-premise-reassert",
6970
+ validates: "body",
6971
+ run: bin("check-pr-premise-reassert"),
6972
+ scope: "diff",
6973
+ timeoutMs: 15000,
6974
+ env: {
6975
+ PR_BODY: { optional: true }
6976
+ }
6977
+ },
6978
+ 0
6979
+ ],
6980
+ [
6981
+ {
6982
+ name: "dispatch-readiness",
6983
+ run: bin("check-dispatch-readiness"),
6984
+ scope: "full",
6985
+ timeoutMs: 30000,
6986
+ env: {
6987
+ AEG_REPO: { optional: true },
6988
+ BRANCH: { optional: true },
6989
+ GITHUB_TOKEN: { optional: true },
6990
+ GH_TOKEN: { optional: true },
6991
+ PREMISE_FILE: { optional: true }
6992
+ }
6993
+ },
6994
+ 0
6995
+ ],
6996
+ [
6997
+ {
6998
+ name: "closes-n",
6999
+ validates: "body",
7000
+ run: bin("check-closes-n"),
7001
+ scope: "diff",
7002
+ timeoutMs: 15000,
7003
+ requiresOpenPr: true,
7004
+ env: {
7005
+ BRANCH: { optional: true },
7006
+ PR_BODY: { optional: true },
7007
+ AEG_REPO: { optional: true },
7008
+ GITHUB_TOKEN: { optional: true },
7009
+ GH_TOKEN: { optional: true }
7010
+ }
7011
+ },
7012
+ 1
7013
+ ],
7014
+ [
7015
+ {
7016
+ name: "single-plan-pr",
7017
+ run: bin("check-single-plan-pr"),
7018
+ scope: "diff",
7019
+ timeoutMs: 15000,
7020
+ env: {
7021
+ BASE_SHA: { optional: true },
7022
+ PR_NUMBER: { optional: true },
7023
+ GITHUB_TOKEN: { optional: true },
7024
+ GH_TOKEN: { optional: true }
7025
+ }
7026
+ },
7027
+ 0
7028
+ ],
7029
+ [
7030
+ {
7031
+ name: "surface-scope",
7032
+ run: bin("check-surface-scope"),
7033
+ scope: "diff",
7034
+ timeoutMs: 15000,
7035
+ env: {
7036
+ AEG_REPO: { optional: true },
7037
+ BASE_SHA: { optional: true },
7038
+ BRANCH: { optional: true },
7039
+ GITHUB_TOKEN: { optional: true },
7040
+ GH_TOKEN: { optional: true }
7041
+ }
7042
+ },
7043
+ 0
7044
+ ],
7045
+ [
7046
+ {
7047
+ name: "test-plan",
7048
+ validates: "body",
7049
+ run: bin("check-test-plan"),
7050
+ scope: "diff",
7051
+ timeoutMs: 15000,
7052
+ requiresOpenPr: true,
7053
+ principalOwed: true,
7054
+ env: {
7055
+ PR_BODY: { optional: true },
7056
+ BRANCH: { optional: true }
7057
+ }
7058
+ },
7059
+ 1
7060
+ ],
7061
+ [
7062
+ {
7063
+ name: "body-bare-digits",
7064
+ validates: "body",
7065
+ run: bin("check-body-bare-digits"),
7066
+ scope: "diff",
7067
+ timeoutMs: 15000,
7068
+ requiresOpenPr: true,
7069
+ ownWorkflow: true,
7070
+ env: {
7071
+ PR_BODY: { optional: true },
7072
+ PR_NUMBER: { optional: true },
7073
+ GITHUB_REPOSITORY: { optional: true },
7074
+ GITHUB_TOKEN: { optional: true },
7075
+ GH_TOKEN: { optional: true }
7076
+ }
7077
+ },
7078
+ 1
7079
+ ],
7080
+ [
7081
+ {
7082
+ name: "token-report",
7083
+ validates: "body",
7084
+ run: bin("check-token-report"),
7085
+ scope: "diff",
7086
+ timeoutMs: 15000,
7087
+ requiresOpenPr: true,
7088
+ env: {
7089
+ PR_BODY: { optional: true },
7090
+ BRANCH: { optional: true },
7091
+ CLAUDE_PROJECT_DIR: { optional: true },
7092
+ CLAUDE_CODE_SESSION_ID: { optional: true }
7093
+ }
7094
+ },
7095
+ 1
7096
+ ],
7097
+ [
7098
+ {
7099
+ name: "no-disk-state",
7100
+ run: bin("check-no-disk-state"),
7101
+ scope: "diff",
7102
+ timeoutMs: 15000,
7103
+ env: { BASE_SHA: { optional: true } }
7104
+ },
7105
+ 0
7106
+ ],
7107
+ [
7108
+ {
7109
+ name: "registry-gates",
7110
+ run: bin("check-registry-gates"),
7111
+ scope: "full",
7112
+ timeoutMs: 30000,
7113
+ env: {
7114
+ AEG_REPO: { optional: true },
7115
+ GITHUB_TOKEN: { optional: true },
7116
+ GH_TOKEN: { optional: true }
7117
+ }
7118
+ },
7119
+ 0
7120
+ ],
7121
+ [
7122
+ {
7123
+ name: "review-gate",
7124
+ run: bin("check-review-gate"),
7125
+ scope: "full",
7126
+ timeoutMs: 30000,
7127
+ ownWorkflow: true,
7128
+ env: {
7129
+ PR_NUMBER: { optional: true },
7130
+ GITHUB_REPOSITORY: { optional: true },
7131
+ GITHUB_TOKEN: { optional: true },
7132
+ GH_TOKEN: { optional: true }
7133
+ }
7134
+ },
7135
+ 1
7136
+ ],
7137
+ [
7138
+ {
7139
+ name: "branch-topology",
7140
+ run: bin("check-branch-topology"),
7141
+ scope: "full",
7142
+ timeoutMs: 30000,
7143
+ env: {
7144
+ BRANCH: { optional: true },
7145
+ AEG_REPO: { optional: true },
7146
+ GITHUB_TOKEN: { optional: true },
7147
+ GH_TOKEN: { optional: true }
7148
+ }
7149
+ },
7150
+ 0
7151
+ ],
7152
+ [
7153
+ {
7154
+ name: "dead-branch-push",
7155
+ run: bin("check-dead-branch-push"),
7156
+ scope: "full",
7157
+ timeoutMs: 30000,
7158
+ env: {
7159
+ BRANCH: { optional: true },
7160
+ GITHUB_TOKEN: { optional: true },
7161
+ GH_TOKEN: { optional: true }
7162
+ }
7163
+ },
7164
+ 0
7165
+ ],
7166
+ [
7167
+ {
7168
+ name: "first-push-dispatch",
7169
+ run: bin("check-first-push-dispatch"),
7170
+ scope: "full",
7171
+ timeoutMs: 30000,
7172
+ env: {
7173
+ BRANCH: { optional: true },
7174
+ AEG_REPO: { optional: true },
7175
+ GITHUB_TOKEN: { optional: true },
7176
+ GH_TOKEN: { optional: true }
7177
+ }
7178
+ },
7179
+ 0
7180
+ ],
7181
+ [
7182
+ {
7183
+ name: "doc-coverage-push",
7184
+ validates: "body",
7185
+ run: bin("check-doc-coverage-push"),
7186
+ scope: "diff",
7187
+ timeoutMs: 15000,
7188
+ env: {
7189
+ OVERRIDE_DOCS: { optional: true },
7190
+ BASE_SHA: { optional: true },
7191
+ PR_BODY: { optional: true },
7192
+ PR_BODY_FILE: { optional: true },
7193
+ PR_LABELS: { optional: true },
7194
+ WAIVER_LABEL_ACTOR: { optional: true },
7195
+ GITHUB_REPOSITORY: { optional: true },
7196
+ GITHUB_TOKEN: { optional: true },
7197
+ GH_TOKEN: { optional: true }
7198
+ }
7199
+ },
7200
+ 0
7201
+ ],
7202
+ [
7203
+ {
7204
+ name: "issue-assignment",
7205
+ run: bin("check-issue-assignment"),
7206
+ scope: "full",
7207
+ timeoutMs: 30000,
7208
+ env: {
7209
+ AEG_REPO: { optional: true },
7210
+ BRANCH: { optional: true },
7211
+ GITHUB_TOKEN: { optional: true },
7212
+ GH_TOKEN: { optional: true }
7213
+ }
7214
+ },
7215
+ 0
7216
+ ],
7217
+ [
7218
+ {
7219
+ name: "evidence-fresh",
7220
+ validates: "body",
7221
+ run: bin("check-evidence-fresh"),
7222
+ scope: "diff",
7223
+ timeoutMs: 15000,
7224
+ requiresOpenPr: true,
7225
+ env: {
7226
+ PR_NUMBER: { optional: true },
7227
+ PR_BODY: { optional: true },
7228
+ BASE_SHA: { optional: true },
7229
+ GITHUB_TOKEN: { optional: true },
7230
+ GH_TOKEN: { optional: true }
7231
+ }
7232
+ },
7233
+ 1
7234
+ ],
7235
+ [
7236
+ {
7237
+ name: "reader-resolvable-prose",
7238
+ run: bin("check-reader-resolvable-prose"),
7239
+ scope: "full",
7240
+ timeoutMs: 30000,
7241
+ env: {},
7242
+ include: [
7243
+ "aeg-root/**/*.md",
7244
+ "apps/cli/src/**",
7245
+ ".github/workflows/**",
7246
+ ".vinaya/**",
7247
+ "apps/cli/README.md",
7248
+ "packages/sources/README.md"
7249
+ ]
7250
+ },
7251
+ 0
7252
+ ],
7253
+ [
7254
+ {
7255
+ name: "retired-vocabulary",
7256
+ run: bin("check-retired-vocabulary"),
7257
+ scope: "full",
7258
+ timeoutMs: 30000,
7259
+ env: {},
7260
+ include: ["aeg-root/**/*.md"]
7261
+ },
7262
+ 0
7263
+ ],
7264
+ [
7265
+ {
7266
+ name: "doctrine-portability",
7267
+ run: bin("check-doctrine-portability"),
7268
+ scope: "full",
7269
+ timeoutMs: 30000,
7270
+ env: { BASE_SHA: { optional: true } },
7271
+ include: ["aeg-root/**/*.md"]
7272
+ },
7273
+ 0
7274
+ ],
7275
+ [
7276
+ {
7277
+ name: "doctrine-no-procedures",
7278
+ run: bin("check-doctrine-no-procedures"),
7279
+ scope: "full",
7280
+ timeoutMs: 30000,
7281
+ env: {},
7282
+ include: ["aeg-root/**/*.md"]
7283
+ },
7284
+ 0
7285
+ ],
7286
+ [
7287
+ {
7288
+ name: "exec-bits",
7289
+ run: bin("check-exec-bits"),
7290
+ scope: "diff",
7291
+ timeoutMs: 30000,
7292
+ env: {}
7293
+ },
7294
+ 0
7295
+ ],
7296
+ [
7297
+ {
7298
+ name: "workspace-escape",
7299
+ run: bin("check-workspace-escape"),
7300
+ scope: "full",
7301
+ timeoutMs: 30000,
7302
+ env: {},
7303
+ include: ["aeg-root/**/*.md"]
7304
+ },
7305
+ 0
7306
+ ],
7307
+ [
7308
+ {
7309
+ name: "changeset-coverage",
7310
+ run: bin("check-changeset-coverage"),
7311
+ scope: "diff",
7312
+ timeoutMs: 15000,
7313
+ env: {}
7314
+ },
7315
+ 0
7316
+ ],
7317
+ [
7318
+ {
7319
+ name: "quoted-command",
7320
+ run: bin("check-quoted-command"),
7321
+ scope: "diff",
7322
+ timeoutMs: 15000,
7323
+ env: {}
7324
+ },
7325
+ 0
7326
+ ],
7327
+ [
7328
+ {
7329
+ name: "main-branch-refusal",
7330
+ run: bin("check-main-branch-refusal"),
7331
+ scope: "full",
7332
+ timeoutMs: 15000,
7333
+ env: { VINAYA_PUSH_REFS: { optional: true } }
7334
+ },
7335
+ 0
7336
+ ],
7337
+ [
7338
+ {
7339
+ name: "token-collection-wired",
7340
+ run: bin("check-token-collection-wired"),
7341
+ scope: "full",
7342
+ timeoutMs: 15000,
7343
+ env: {
7344
+ CLAUDE_PROJECT_DIR: { optional: true },
7345
+ CLAUDE_CODE_SESSION_ID: { optional: true }
7346
+ }
7347
+ },
7348
+ 0
7349
+ ],
7350
+ [
7351
+ {
7352
+ name: "issue-title-grammar",
7353
+ run: bin("check-issue-title-grammar"),
7354
+ validates: "issue",
7355
+ scope: "full",
7356
+ ownWorkflow: true,
7357
+ timeoutMs: 15000,
7358
+ env: { ISSUE_TITLE: { optional: true } }
7359
+ },
7360
+ 1
7361
+ ],
7362
+ [
7363
+ {
7364
+ name: "issue-objectives-numbering",
7365
+ run: bin("check-issue-objectives-numbering"),
7366
+ validates: "issue",
7367
+ scope: "full",
7368
+ ownWorkflow: true,
7369
+ timeoutMs: 15000,
7370
+ env: { ISSUE_BODY: { optional: true }, ISSUE_NUMBER: { optional: true } }
7371
+ },
7372
+ 1
7373
+ ],
7374
+ [
7375
+ {
7376
+ name: "issue-parts-coverage",
7377
+ run: bin("check-issue-parts-coverage"),
7378
+ validates: "issue",
7379
+ scope: "full",
7380
+ ownWorkflow: true,
7381
+ timeoutMs: 15000,
7382
+ env: { ISSUE_BODY: { optional: true } }
7383
+ },
7384
+ 1
7385
+ ],
7386
+ [
7387
+ {
7388
+ name: "issue-surface-globs",
7389
+ run: bin("check-issue-surface-globs"),
7390
+ validates: "issue",
7391
+ scope: "full",
7392
+ ownWorkflow: true,
7393
+ timeoutMs: 15000,
7394
+ env: { ISSUE_BODY: { optional: true } }
7395
+ },
7396
+ 1
7397
+ ],
7398
+ [
7399
+ {
7400
+ name: "issue-tranche-label",
7401
+ run: bin("check-issue-tranche-label"),
7402
+ validates: "issue",
7403
+ scope: "full",
7404
+ ownWorkflow: true,
7405
+ timeoutMs: 15000,
7406
+ env: { ISSUE_BODY: { optional: true }, ISSUE_LABELS: { optional: true } }
7407
+ },
7408
+ 1
7409
+ ],
7410
+ [
7411
+ {
7412
+ name: "issue-milestone-attach",
7413
+ run: bin("check-issue-milestone-attach"),
7414
+ validates: "issue",
7415
+ scope: "full",
7416
+ ownWorkflow: true,
7417
+ timeoutMs: 15000,
7418
+ env: {
7419
+ ISSUE_LABELS: { optional: true },
7420
+ CURRENT_MILESTONE_TITLE: { optional: true },
7421
+ RESOLVED_MILESTONE_TITLE: { optional: true }
7422
+ }
7423
+ },
7424
+ 1
7425
+ ]
7426
+ ];
7427
+ function coreCheckRegistry() {
7428
+ return REGISTRY.map(([spec]) => spec);
7429
+ }
7430
+ var CORE_CHECK_RING = Object.fromEntries(REGISTRY.map(([spec, ring]) => [spec.name, ring]));
7431
+
6045
7432
  // src/checks/contract.ts
6046
7433
  var CHECK_SCHEMA_VERSION = 1;
6047
7434
  function emitCheckError(error) {
@@ -6049,6 +7436,15 @@ function emitCheckError(error) {
6049
7436
  `);
6050
7437
  }
6051
7438
 
7439
+ // src/checks/runner.ts
7440
+ var activeKillers = new Set;
7441
+
7442
+ // src/lib/forge-write.ts
7443
+ var INFORMATIONAL_DISPATCH_BLOCKER_CLASSES = new Set([
7444
+ "depends-on-not-merged",
7445
+ "conflicts-with"
7446
+ ]);
7447
+
6052
7448
  // src/commands/review-post.ts
6053
7449
  function parseChangedLineRanges(diffOutput) {
6054
7450
  const result = {};
@@ -6080,7 +7476,7 @@ function parseChangedLineRanges(diffOutput) {
6080
7476
  function fileDiffAgainst(ref, path) {
6081
7477
  let out;
6082
7478
  try {
6083
- out = execFileSync4("git", ["diff", `${ref}...HEAD`, "--", path], {
7479
+ out = execFileSync5("git", ["diff", `${ref}...HEAD`, "--", path], {
6084
7480
  encoding: "utf8",
6085
7481
  stdio: ["ignore", "pipe", "pipe"]
6086
7482
  }).trim();
@@ -6091,7 +7487,7 @@ function fileDiffAgainst(ref, path) {
6091
7487
  }
6092
7488
  function revParse(ref) {
6093
7489
  try {
6094
- return execFileSync4("git", ["rev-parse", "--verify", ref], {
7490
+ return execFileSync5("git", ["rev-parse", "--verify", ref], {
6095
7491
  encoding: "utf8",
6096
7492
  stdio: ["ignore", "pipe", "pipe"]
6097
7493
  }).trim();
@@ -6101,7 +7497,7 @@ function revParse(ref) {
6101
7497
  }
6102
7498
  function repoRoot() {
6103
7499
  try {
6104
- return execFileSync4("git", ["rev-parse", "--show-toplevel"], {
7500
+ return execFileSync5("git", ["rev-parse", "--show-toplevel"], {
6105
7501
  encoding: "utf8",
6106
7502
  stdio: ["ignore", "pipe", "pipe"]
6107
7503
  }).trim();
@@ -6112,7 +7508,7 @@ function repoRoot() {
6112
7508
  function changedFiles(base) {
6113
7509
  let out;
6114
7510
  try {
6115
- out = execFileSync4("git", ["diff", "--name-only", `${base}...HEAD`], {
7511
+ out = execFileSync5("git", ["diff", "--name-only", `${base}...HEAD`], {
6116
7512
  encoding: "utf8",
6117
7513
  stdio: ["ignore", "pipe", "pipe"]
6118
7514
  }).trim();
@@ -6137,7 +7533,7 @@ function resolveDiff(base = process.env.BASE_SHA || "origin/main") {
6137
7533
  const relPaths = changedFiles(ref);
6138
7534
  if (relPaths === null)
6139
7535
  continue;
6140
- return { base: ref, root, files: relPaths.map((p) => join2(root, p)) };
7536
+ return { base: ref, root, files: relPaths.map((p) => join4(root, p)) };
6141
7537
  }
6142
7538
  return null;
6143
7539
  }
@@ -6186,7 +7582,7 @@ function collectAllPaths(dir, out = []) {
6186
7582
  for (const name of entries) {
6187
7583
  if (EXCLUDED_DIRS.has(name))
6188
7584
  continue;
6189
- const full = join3(dir, name);
7585
+ const full = join5(dir, name);
6190
7586
  let isDir;
6191
7587
  try {
6192
7588
  isDir = statSync(full).isDirectory();