@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
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/checks/bin/check-exec-bits.ts
6
- import { execFileSync as execFileSync5 } from "node:child_process";
6
+ import { execFileSync as execFileSync6 } from "node:child_process";
7
7
  import { readFileSync as readFileSync3 } from "node:fs";
8
8
 
9
9
  // src/checks/contract.ts
@@ -14,8 +14,8 @@ function emitCheckError(error) {
14
14
  }
15
15
 
16
16
  // src/lib/diff-evidence.ts
17
- import { execFileSync as execFileSync4 } from "node:child_process";
18
- import { join as join2, relative, resolve as resolve2 } from "node:path";
17
+ import { execFileSync as execFileSync5 } from "node:child_process";
18
+ import { join as join4, relative, resolve as resolve2 } from "node:path";
19
19
  // ../../packages/aeg-core/src/gate-audience.ts
20
20
  var GATE_AUDIENCE = {
21
21
  "check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
@@ -315,7 +315,7 @@ function issueListByLabelArgs(owner, repo, label) {
315
315
  "--state",
316
316
  "all",
317
317
  "--json",
318
- "number,title,body,state,labels,milestone",
318
+ "number,title,body,state,labels,milestone,stateReason",
319
319
  "--limit",
320
320
  "200"
321
321
  ];
@@ -661,14 +661,19 @@ function resolveTaskIssueRef(title, labels) {
661
661
  var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
662
662
  var NEXT_HEADING = /^#{1,6}\s+\S/m;
663
663
  var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
664
- function intentGoalForSlug(description, slug) {
665
- const text = stripCode(description, { inlineSpans: "keep" });
664
+ function intentsSection(text) {
666
665
  const start = text.match(INTENTS_HEADING);
667
666
  if (!start || start.index === undefined)
668
- return "";
667
+ return null;
669
668
  const rest = text.slice(start.index + start[0].length);
670
669
  const next = rest.match(NEXT_HEADING);
671
- const section = rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
670
+ return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
671
+ }
672
+ function intentGoalForSlug(description, slug) {
673
+ const text = stripCode(description, { inlineSpans: "keep" });
674
+ const section = intentsSection(text);
675
+ if (section === null)
676
+ return "";
672
677
  for (const line of section.split(`
673
678
  `)) {
674
679
  const trimmed = line.trim();
@@ -1960,6 +1965,9 @@ function parseInlineFieldList(section) {
1960
1965
  // ../../packages/aeg-core/src/verdict-extraction.ts
1961
1966
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1962
1967
  var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1968
+ var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
1969
+ var BRIEF_HASH_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Brief hash:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1970
+ var POLICY_DIGEST_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Policy digest:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1963
1971
  function firstFiveLines(comment) {
1964
1972
  return comment.split(`
1965
1973
  `).slice(0, 5).join(`
@@ -1973,6 +1981,35 @@ function extractObjectivesVersion(comment) {
1973
1981
  const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
1974
1982
  return m ? m[1].toLowerCase() : null;
1975
1983
  }
1984
+ function firstSevenLines(comment) {
1985
+ return comment.split(`
1986
+ `).slice(0, 7).join(`
1987
+ `);
1988
+ }
1989
+ function extractRulingOrdinal(comment) {
1990
+ const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
1991
+ return m ? Number.parseInt(m[1], 10) : null;
1992
+ }
1993
+ function firstElevenLines(comment) {
1994
+ return comment.split(`
1995
+ `).slice(0, 11).join(`
1996
+ `);
1997
+ }
1998
+ function extractBriefHash(comment) {
1999
+ const m = firstElevenLines(comment).match(BRIEF_HASH_PATTERN);
2000
+ return m ? m[1].toLowerCase() : null;
2001
+ }
2002
+ function extractPolicyDigest(comment) {
2003
+ const m = firstElevenLines(comment).match(POLICY_DIGEST_PATTERN);
2004
+ return m ? m[1].toLowerCase() : null;
2005
+ }
2006
+ var FINDING_SEVERITY_LINE = /^\d+\.\s+\[([A-Z][A-Z]*)\]\s+(.+?)\s+—/gm;
2007
+ function extractFindingSeverities(comment) {
2008
+ return [...comment.matchAll(FINDING_SEVERITY_LINE)].map((m) => ({
2009
+ severity: m[1],
2010
+ location: m[2]
2011
+ }));
2012
+ }
1976
2013
  function extractVerdict(comments, valuePattern, missingLabel) {
1977
2014
  const candidates = comments.filter((c) => valuePattern.test(c));
1978
2015
  if (candidates.length === 0) {
@@ -1980,6 +2017,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1980
2017
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1981
2018
  headSha: null,
1982
2019
  objectivesVersion: null,
2020
+ rulingOrdinal: null,
2021
+ briefHash: null,
2022
+ policyDigest: null,
2023
+ findingSeverities: [],
1983
2024
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1984
2025
  };
1985
2026
  }
@@ -1990,6 +2031,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1990
2031
  value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
1991
2032
  headSha: null,
1992
2033
  objectivesVersion: null,
2034
+ rulingOrdinal: null,
2035
+ briefHash: null,
2036
+ policyDigest: null,
2037
+ findingSeverities: [],
1993
2038
  danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
1994
2039
  };
1995
2040
  }
@@ -1997,6 +2042,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1997
2042
  value: m[1].toUpperCase().replace(/[_-]/g, " "),
1998
2043
  headSha: extractHeadSha(latest),
1999
2044
  objectivesVersion: extractObjectivesVersion(latest),
2045
+ rulingOrdinal: extractRulingOrdinal(latest),
2046
+ briefHash: extractBriefHash(latest),
2047
+ policyDigest: extractPolicyDigest(latest),
2048
+ findingSeverities: extractFindingSeverities(latest),
2000
2049
  danglingNote: null
2001
2050
  };
2002
2051
  }
@@ -2332,6 +2381,53 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
2332
2381
  }
2333
2382
  return out;
2334
2383
  }
2384
+ // ../../packages/aeg-core/src/review-policy.ts
2385
+ var CODE_REVIEW_SEVERITY_ORDER = ["BLOCKER", "MAJOR", "MINOR"];
2386
+ var SECURITY_SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
2387
+ var DEFAULT_MAX_ROUNDS = 3;
2388
+ var DEFAULT_REVIEW_POLICY = {
2389
+ codeReviewThreshold: "BLOCKER",
2390
+ securityThreshold: "HIGH",
2391
+ maxRounds: DEFAULT_MAX_ROUNDS
2392
+ };
2393
+ var FILE_SHAPED_LOCATION = /\.[a-zA-Z0-9]{1,10}(:\d+)?\s*$/;
2394
+ var PROSE_LOCATION_PATTERNS = [/\bpr\s*body\b/i, /\bcomment\b/i];
2395
+ var ROLE_FILE_LOCATION = /(^|\/)aeg-root\/roles\//i;
2396
+ function isProseLocation(location) {
2397
+ if (ROLE_FILE_LOCATION.test(location))
2398
+ return true;
2399
+ if (FILE_SHAPED_LOCATION.test(location))
2400
+ return false;
2401
+ return PROSE_LOCATION_PATTERNS.some((pattern) => pattern.test(location));
2402
+ }
2403
+ var PROSE_CAP_SEVERITY = "MINOR";
2404
+ function blockingSeverities(scale, threshold) {
2405
+ const idx = scale.indexOf(threshold);
2406
+ if (idx === -1) {
2407
+ throw new Error(`blockingSeverities: threshold "${threshold}" is not one of ${scale.join(" > ")}`);
2408
+ }
2409
+ return scale.slice(0, idx + 1);
2410
+ }
2411
+ function evaluateReviewFindings(findings, scale, threshold) {
2412
+ const blocking = new Set(blockingSeverities(scale, threshold));
2413
+ const blockingFindings = findings.filter((f) => {
2414
+ if (!scale.includes(f.severity)) {
2415
+ throw new Error(`evaluateReviewFindings: severity "${f.severity}" is not one of ${scale.join(" > ")}`);
2416
+ }
2417
+ const effectiveSeverity = f.location !== undefined && isProseLocation(f.location) ? PROSE_CAP_SEVERITY : f.severity;
2418
+ return blocking.has(effectiveSeverity);
2419
+ });
2420
+ return { outcome: blockingFindings.length > 0 ? "blocked" : "clean", blockingFindings };
2421
+ }
2422
+ function evaluateCodeReview(findings, policy) {
2423
+ return evaluateReviewFindings(findings, CODE_REVIEW_SEVERITY_ORDER, policy.codeReviewThreshold);
2424
+ }
2425
+ function evaluateSecurityReview(findings, policy) {
2426
+ return evaluateReviewFindings(findings, SECURITY_SEVERITY_ORDER, policy.securityThreshold);
2427
+ }
2428
+ function isKnownSeverity(scale, value) {
2429
+ return scale.includes(value);
2430
+ }
2335
2431
  // ../../packages/aeg-core/src/pr-tier.ts
2336
2432
  var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
2337
2433
  function readTierFromPrBody(prBody) {
@@ -2358,6 +2454,9 @@ import { createHash as createHash2 } from "node:crypto";
2358
2454
  var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
2359
2455
  var NEXT_HEADING_RE = /^##[ \t]/m;
2360
2456
  var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
2457
+ function maskedForHeadingSearch(body) {
2458
+ return maskDetailsBlocks(maskCode(body));
2459
+ }
2361
2460
  function hasBacktickedPath(text) {
2362
2461
  let i = 0;
2363
2462
  while (i < text.length) {
@@ -2380,16 +2479,22 @@ function stripObjectiveBackticks(text) {
2380
2479
  function wordCount(text) {
2381
2480
  return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
2382
2481
  }
2383
- function objectivesSectionText(body) {
2384
- const heading = HEADING_RE.exec(body);
2482
+ function objectivesSectionBounds(body) {
2483
+ const masked = maskedForHeadingSearch(body);
2484
+ const heading = HEADING_RE.exec(masked);
2385
2485
  if (!heading)
2386
2486
  return null;
2387
- const afterHeading = body.slice(heading.index + heading[0].length);
2487
+ const start = heading.index + heading[0].length;
2488
+ const afterHeading = masked.slice(start);
2388
2489
  const next = NEXT_HEADING_RE.exec(afterHeading);
2389
- return next ? afterHeading.slice(0, next.index) : afterHeading;
2490
+ return { start, end: next ? start + next.index : body.length };
2491
+ }
2492
+ function objectivesSectionText(body) {
2493
+ const bounds = objectivesSectionBounds(body);
2494
+ return bounds === null ? null : body.slice(bounds.start, bounds.end);
2390
2495
  }
2391
2496
  function hasObjectivesHeading(body) {
2392
- return HEADING_RE.test(body);
2497
+ return HEADING_RE.test(maskedForHeadingSearch(body));
2393
2498
  }
2394
2499
  function objectivesOf(body) {
2395
2500
  const section = objectivesSectionText(body);
@@ -2449,6 +2554,15 @@ function isIssueNotFoundError(err) {
2449
2554
  `);
2450
2555
  return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
2451
2556
  }
2557
+ function resolveObjectivesSource(prBody, issue, cutoverIssue) {
2558
+ if (issue !== null && issue < cutoverIssue)
2559
+ return { kind: "none" };
2560
+ if (issue !== null)
2561
+ return { kind: "issue", issue };
2562
+ if (hasObjectivesHeading(prBody))
2563
+ return { kind: "body" };
2564
+ return { kind: "none" };
2565
+ }
2452
2566
 
2453
2567
  // ../../packages/aeg-core/src/premise-check.ts
2454
2568
  import { createHash as createHash3 } from "node:crypto";
@@ -2684,7 +2798,7 @@ function checkForField(prBody) {
2684
2798
  ]
2685
2799
  };
2686
2800
  }
2687
- function checkClosesN(prBody) {
2801
+ function checkClosesNPresence(prBody) {
2688
2802
  const closesPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s{0,8}:?\s{0,8}#\d+/i;
2689
2803
  if (closesPattern.test(stripCode(prBody))) {
2690
2804
  return { status: "pass", errors: [] };
@@ -2716,10 +2830,22 @@ var COMMIT_TYPES = [
2716
2830
  "Test"
2717
2831
  ];
2718
2832
  var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
2833
+ function checkForgeTitle(title) {
2834
+ const taskStyle = /^\[[a-z0-9._-]+\] \S+ — \S/;
2835
+ if (COMMIT_TYPE_STYLE.test(title) || taskStyle.test(title))
2836
+ return { status: "pass", errors: [] };
2837
+ return {
2838
+ status: "fail",
2839
+ errors: [
2840
+ `brief-validation title: "${title}" matches neither title grammar — expected \`Type: description\` / \`Type(scope): description\` (commitlint types + Plan) or \`[tranche] id — description\` (task form).`
2841
+ ]
2842
+ };
2843
+ }
2719
2844
  var BRIEF_SHAPE_MARKERS = [checkSurfaceMap, checkDocUpdateList, checkStopConditions, checkAutonomyClause];
2720
2845
  var TASK_BRANCH_PATTERN = /^task\/[^/]+\/[^/]+$/;
2846
+ var TASK_ISSUE_BRANCH_PATTERN = /^task\/issue-\d+$/;
2721
2847
  function isTaskBranch(branch) {
2722
- return TASK_BRANCH_PATTERN.test(branch);
2848
+ return TASK_BRANCH_PATTERN.test(branch) || TASK_ISSUE_BRANCH_PATTERN.test(branch);
2723
2849
  }
2724
2850
  function isBriefShaped(prBody) {
2725
2851
  const stripped = stripCode(prBody);
@@ -2841,8 +2967,9 @@ function packagesNamedIn(text) {
2841
2967
  }
2842
2968
  function hasTestPathForConsumer(text, consumerDir) {
2843
2969
  const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2844
- const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2845
- return re.test(text);
2970
+ const filePathRe = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2971
+ const testDirRe = new RegExp(`${escaped}\\/(?:[\\w-]+\\/)*(?:tests|specs)(?:\\/|\\b)`);
2972
+ return filePathRe.test(text) || testDirRe.test(text);
2846
2973
  }
2847
2974
  function checkConsumerTests(prBody, consumersOf) {
2848
2975
  const section4 = extractNumberedSection(prBody, 4);
@@ -2946,21 +3073,44 @@ function checkBriefSections(prBody, readTier, options = {}) {
2946
3073
  checkConsumerTests(prBody, consumersOf),
2947
3074
  checkDefeatCases(prBody),
2948
3075
  ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2949
- ...requireClosesN ? [checkClosesN(prBody)] : []
3076
+ ...requireClosesN ? [checkClosesNPresence(prBody)] : []
2950
3077
  ];
2951
3078
  return { errors: results.flatMap((r) => r.errors) };
2952
3079
  }
2953
- var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
2954
- function contentAfterTwoLines(body) {
2955
- const first = body.indexOf(`
2956
- `);
2957
- if (first === -1)
2958
- return "";
2959
- const second = body.indexOf(`
2960
- `, first + 1);
2961
- if (second === -1)
2962
- return "";
2963
- return body.slice(second + 1);
3080
+ function contentAfterNLines(body, n) {
3081
+ let idx = -1;
3082
+ for (let i = 0;i < n; i++) {
3083
+ idx = body.indexOf(`
3084
+ `, idx + 1);
3085
+ if (idx === -1)
3086
+ return "";
3087
+ }
3088
+ return body.slice(idx + 1);
3089
+ }
3090
+ var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
3091
+ function parseBriefMarkerVersion(firstLine) {
3092
+ const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
3093
+ if (!m)
3094
+ return null;
3095
+ const version = Number.parseInt(m[1], 10);
3096
+ return Number.isInteger(version) && version >= 1 ? version : null;
3097
+ }
3098
+ function frozenBriefContent(body, version) {
3099
+ return contentAfterNLines(body, version === 1 ? 2 : 3);
3100
+ }
3101
+ function resolveNewestFrozenBrief(comments, allowlist) {
3102
+ let best = null;
3103
+ for (const c of comments) {
3104
+ if (!isPrincipal(c.author, allowlist))
3105
+ continue;
3106
+ const version = parseBriefMarkerVersion(c.body.split(`
3107
+ `)[0] ?? "");
3108
+ if (version === null)
3109
+ continue;
3110
+ if (best === null || version > best.version)
3111
+ best = { ...c, version };
3112
+ }
3113
+ return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
2964
3114
  }
2965
3115
  // ../../packages/aeg-core/src/doctrine-portability.ts
2966
3116
  var DEFAULT_SHIPS_PREFIX = "aeg-root/";
@@ -3213,6 +3363,28 @@ function topLevelSectionText(body, headingName) {
3213
3363
  const next = /^##[ \t]/m.exec(afterHeading);
3214
3364
  return next ? afterHeading.slice(0, next.index) : afterHeading;
3215
3365
  }
3366
+ function partHasBacktickedPath(text) {
3367
+ let i = 0;
3368
+ while (i < text.length) {
3369
+ const start = text.indexOf("`", i);
3370
+ if (start === -1)
3371
+ return false;
3372
+ const end = text.indexOf("`", start + 1);
3373
+ if (end === -1)
3374
+ return false;
3375
+ if (text.slice(start + 1, end).includes("/"))
3376
+ return true;
3377
+ i = end + 1;
3378
+ }
3379
+ return false;
3380
+ }
3381
+ var PART_MIN_WORDS_OUTSIDE_BACKTICKS = 3;
3382
+ function stripPartBackticks(text) {
3383
+ return text.replace(/`[^`\n]*`/g, " ");
3384
+ }
3385
+ function partWordCount(text) {
3386
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
3387
+ }
3216
3388
  function looksLikeFilePath(entry2) {
3217
3389
  const stripped = entry2.replace(/\/\*\*?$/, "");
3218
3390
  const lastSegment = stripped.split("/").pop() ?? stripped;
@@ -3253,6 +3425,18 @@ function globCoversPath(glob, path) {
3253
3425
  const p = path.replace(/\/+$/, "");
3254
3426
  return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
3255
3427
  }
3428
+ function checkSurfaceGlobsResolve(body, resolvesToFile) {
3429
+ const surface = parseIssueSurface(body);
3430
+ if (!surface.ok)
3431
+ return { status: "pass", errors: [] };
3432
+ const errors = [];
3433
+ for (const glob of surface.value.in) {
3434
+ if (!resolvesToFile(glob)) {
3435
+ 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.`);
3436
+ }
3437
+ }
3438
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3439
+ }
3256
3440
  function checkSurfaceScope(changedFiles, outGlobs) {
3257
3441
  if (outGlobs.length === 0)
3258
3442
  return { ok: true };
@@ -3264,9 +3448,77 @@ function checkSurfaceScope(changedFiles, outGlobs) {
3264
3448
  }
3265
3449
  return violations.length > 0 ? { ok: false, violations } : { ok: true };
3266
3450
  }
3451
+ var ISSUE_PART_LINE_RE = /^Part\s+(\d+)\s*\(([^)]*)\)\s*[-—–]\s*(.*)$/i;
3452
+ function parseIssueParts(body) {
3453
+ const section = topLevelSectionText(body, "Parts");
3454
+ if (section === null)
3455
+ return { ok: false, errors: ["no `## Parts` heading found in the body."] };
3456
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
3457
+ const parts = [];
3458
+ const errors = [];
3459
+ for (const line of lines) {
3460
+ const m = ISSUE_PART_LINE_RE.exec(line);
3461
+ if (!m) {
3462
+ errors.push(`"${line}" is not a well-formed Parts line — expected \`Part <n> (<refs>) — <outcome>\`.`);
3463
+ continue;
3464
+ }
3465
+ const n = Number.parseInt(m[1], 10);
3466
+ const refs = m[2];
3467
+ const text = m[3].trim();
3468
+ if (text.length === 0) {
3469
+ errors.push(`Part ${n} has no outcome text after the dash — every Part states one observable outcome.`);
3470
+ continue;
3471
+ }
3472
+ if (partHasBacktickedPath(text) && partWordCount(stripPartBackticks(text)) < PART_MIN_WORDS_OUTSIDE_BACKTICKS) {
3473
+ errors.push(`Part ${n} is little more than a file path — a Part names an outcome and symbols, never a bare path.`);
3474
+ continue;
3475
+ }
3476
+ const objectiveIds = [...refs.matchAll(/O(\d+)/g)].map((r) => Number.parseInt(r[1], 10));
3477
+ parts.push({ n, objectiveIds, text });
3478
+ }
3479
+ if (parts.length === 0) {
3480
+ errors.push("the `## Parts` section has no well-formed `Part <n> (<refs>) — <outcome>` lines.");
3481
+ }
3482
+ if (errors.length > 0)
3483
+ return { ok: false, errors };
3484
+ return { ok: true, value: parts };
3485
+ }
3486
+ function checkPartsCiteDefinedObjectives(body) {
3487
+ const parts = parseIssueParts(body);
3488
+ const objectives = objectivesOf(body);
3489
+ if (!parts.ok || !objectives.ok)
3490
+ return { status: "pass", errors: [] };
3491
+ const definedIds = new Set(objectives.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
3492
+ const errors = [];
3493
+ for (const part of parts.value) {
3494
+ for (const objectiveId of part.objectiveIds) {
3495
+ if (!definedIds.has(objectiveId)) {
3496
+ errors.push(`issue-validation Parts: Part ${part.n} cites O${objectiveId}, which the Issue's own \`## Objectives\` section does not define.`);
3497
+ }
3498
+ }
3499
+ }
3500
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3501
+ }
3267
3502
  function isTaskIssueLabelSet(labels) {
3268
3503
  return hasLabel("tranche", labels);
3269
3504
  }
3505
+ function checkTrancheLabelPresence(_body, _labels) {
3506
+ return { status: "pass", errors: [] };
3507
+ }
3508
+ function checkMilestoneAttach(labels, currentMilestoneTitle, resolvedMilestoneTitle) {
3509
+ if (!isTaskIssueLabelSet(labels))
3510
+ return { status: "pass", errors: [] };
3511
+ if (resolvedMilestoneTitle === null)
3512
+ return { status: "pass", errors: [] };
3513
+ if (currentMilestoneTitle === resolvedMilestoneTitle)
3514
+ return { status: "pass", errors: [] };
3515
+ return {
3516
+ status: "fail",
3517
+ errors: [
3518
+ `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}".`
3519
+ ]
3520
+ };
3521
+ }
3270
3522
  var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
3271
3523
  function isControlCodePoint(codePoint) {
3272
3524
  return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
@@ -3331,6 +3583,19 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
3331
3583
  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;
3332
3584
  var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
3333
3585
 
3586
+ // ../../packages/aeg-core/src/task-branch-identity.ts
3587
+ var ISSUE_BRANCH_PATTERN = /^task\/issue-(\d+)$/;
3588
+ var TRANCHE_BRANCH_PATTERN = /^task\/([^/]+)\/([^/]+)$/;
3589
+ function parseTaskBranchIdentity(branch) {
3590
+ const issueMatch = ISSUE_BRANCH_PATTERN.exec(branch);
3591
+ if (issueMatch)
3592
+ return { kind: "issue", issueNumber: Number(issueMatch[1]) };
3593
+ const trancheMatch = TRANCHE_BRANCH_PATTERN.exec(branch);
3594
+ if (trancheMatch)
3595
+ return { kind: "tranche", tranche: trancheMatch[1], taskId: trancheMatch[2] };
3596
+ return null;
3597
+ }
3598
+
3334
3599
  // ../../packages/aeg-core/src/coherence-checks.ts
3335
3600
  var COHERENCE_ENFORCED_FROM = "2026-07-01";
3336
3601
  function isGrandfathered(isoDate) {
@@ -3351,6 +3616,7 @@ function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
3351
3616
  if (handClosed)
3352
3617
  continue;
3353
3618
  failures.push({
3619
+ code: "closed-without-merge",
3354
3620
  issue: e.task.issue,
3355
3621
  tranche: e.trancheSlug,
3356
3622
  task: e.task.id,
@@ -3375,6 +3641,7 @@ function checkA3(entries) {
3375
3641
  continue;
3376
3642
  if (e.facts.prState === "merged" && e.facts.issueState !== "closed") {
3377
3643
  failures.push({
3644
+ code: "auto-close-misfire",
3378
3645
  issue: e.task.issue,
3379
3646
  tranche: e.trancheSlug,
3380
3647
  task: e.task.id,
@@ -3399,6 +3666,7 @@ function checkT1(entries) {
3399
3666
  continue;
3400
3667
  if (e.facts === undefined) {
3401
3668
  failures.push({
3669
+ code: "phantom-issue-ref",
3402
3670
  issue: e.task.issue,
3403
3671
  tranche: e.trancheSlug,
3404
3672
  task: e.task.id,
@@ -3417,6 +3685,7 @@ function checkT2(openIssuesBySlug, topologyIssuesBySlug, ciTrancheSlug) {
3417
3685
  for (const num of openNums) {
3418
3686
  if (!topologySet.has(num)) {
3419
3687
  failures.push({
3688
+ code: "orphan-task",
3420
3689
  issue: num,
3421
3690
  tranche: slug,
3422
3691
  reason: `Issue #${num} is open and labeled ${trancheLabel(slug)} but does not appear in the topology file`
@@ -3451,6 +3720,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3451
3720
  continue;
3452
3721
  if (forgeUnavailableSlugs?.has(e.trancheSlug)) {
3453
3722
  failures.push({
3723
+ code: "tbd-in-active-tranche",
3454
3724
  issue: null,
3455
3725
  tranche: e.trancheSlug,
3456
3726
  task: e.task.id,
@@ -3460,6 +3730,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3460
3730
  continue;
3461
3731
  }
3462
3732
  failures.push({
3733
+ code: "tbd-in-active-tranche",
3463
3734
  issue: null,
3464
3735
  tranche: e.trancheSlug,
3465
3736
  task: e.task.id,
@@ -3492,6 +3763,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3492
3763
  const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3493
3764
  if (sameTask || sameIssue) {
3494
3765
  failures.push({
3766
+ code: "d1-self-dependency",
3495
3767
  issue: e.task.issue,
3496
3768
  tranche: e.trancheSlug,
3497
3769
  task: e.task.id,
@@ -3503,6 +3775,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3503
3775
  const depClosed = depFacts?.issueState === "closed";
3504
3776
  if (!depClosed) {
3505
3777
  failures.push({
3778
+ code: "dispatched-on-unmet-deps",
3506
3779
  issue: e.task.issue,
3507
3780
  tranche: e.trancheSlug,
3508
3781
  task: e.task.id,
@@ -3528,11 +3801,13 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3528
3801
  const errors = [
3529
3802
  ...checkIssueRationale(issue.body).errors,
3530
3803
  ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3531
- ...checkIssueObjectives(issue.body, issue.number).errors
3804
+ ...checkIssueObjectives(issue.body, issue.number).errors,
3805
+ ...checkPartsCiteDefinedObjectives(issue.body).errors
3532
3806
  ];
3533
3807
  if (errors.length === 0)
3534
3808
  continue;
3535
3809
  failures.push({
3810
+ code: "missing-rationale-field",
3536
3811
  issue: issue.number,
3537
3812
  tranche: slug,
3538
3813
  reason: `Issue #${issue.number} fails the rationale gate: ${errors.join(" | ")}`,
@@ -3561,6 +3836,7 @@ function checkL1(files, entriesBySlug) {
3561
3836
  const allClosed = withFacts.every((e) => e.facts?.issueState === "closed");
3562
3837
  if (allClosed) {
3563
3838
  failures.push({
3839
+ code: "archive-recommended",
3564
3840
  tranche: f.slug,
3565
3841
  reason: "Active tranche has no open task-Issues — consider archiving to completed/"
3566
3842
  });
@@ -3592,27 +3868,38 @@ function extractClosesReferences(prBody) {
3592
3868
  }
3593
3869
  return referenced;
3594
3870
  }
3595
- function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3871
+ function checkClosesNTopology(branch, prBody, trancheFiles, taskIssueRefs) {
3596
3872
  const referenced = extractClosesReferences(prBody);
3597
3873
  if (taskIssueRefs) {
3598
3874
  for (const n of referenced) {
3599
- const ref = taskIssueRefs.get(n);
3600
- if (!ref)
3875
+ const ref2 = taskIssueRefs.get(n);
3876
+ if (!ref2)
3601
3877
  continue;
3602
- const expectedBranch = `task/${ref.trancheSlug}/${ref.taskId}`;
3878
+ const expectedBranch = `task/${ref2.trancheSlug}/${ref2.taskId}`;
3603
3879
  if (branch !== expectedBranch) {
3604
3880
  return {
3605
3881
  ok: false,
3606
- 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.`
3882
+ 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.`
3607
3883
  };
3608
3884
  }
3609
3885
  }
3610
3886
  }
3611
- const m = branch.match(/^task\/([^/]+)\/([^/]+)$/);
3612
- if (!m)
3887
+ const ref = parseTaskBranchIdentity(branch);
3888
+ if (!ref)
3613
3889
  return { ok: true };
3614
- const trancheSlug = m[1];
3615
- const taskId = m[2];
3890
+ if (ref.kind === "issue") {
3891
+ const expectedIssue2 = ref.issueNumber;
3892
+ if (!referenced.has(expectedIssue2)) {
3893
+ return {
3894
+ ok: false,
3895
+ expectedIssue: expectedIssue2,
3896
+ message: `closes-n: PR body does not contain \`Closes #${expectedIssue2}\` (required for branch "${branch}"). Add it to the PR body Summary section.`
3897
+ };
3898
+ }
3899
+ return { ok: true, expectedIssue: expectedIssue2 };
3900
+ }
3901
+ const trancheSlug = ref.tranche;
3902
+ const taskId = ref.taskId;
3616
3903
  const trancheFile = trancheFiles.find((f) => f.slug === trancheSlug);
3617
3904
  if (!trancheFile) {
3618
3905
  return {
@@ -3664,29 +3951,67 @@ function extractIssue(body) {
3664
3951
  const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3665
3952
  return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3666
3953
  }
3667
- // ../../packages/aeg-core/src/review-gate.ts
3668
- function isBoundToHead(extraction, headSha) {
3669
- if (!extraction.headSha)
3954
+ // ../../packages/aeg-core/src/review-input-manifest.ts
3955
+ import { createHash as createHash4 } from "node:crypto";
3956
+ function briefHash(brief) {
3957
+ return createHash4("sha256").update(`${brief}
3958
+ `).digest("hex");
3959
+ }
3960
+ function policyDigest(policy) {
3961
+ return createHash4("sha256").update(JSON.stringify({ codeReviewThreshold: policy.codeReviewThreshold, securityThreshold: policy.securityThreshold })).digest("hex");
3962
+ }
3963
+ function isBoundToHead(echoed, headSha) {
3964
+ if (!echoed.headSha)
3670
3965
  return false;
3671
- return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
3966
+ return headSha.toLowerCase().startsWith(echoed.headSha.toLowerCase());
3672
3967
  }
3673
- function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3674
- if (patchIdOf === undefined || !extraction.headSha)
3968
+ function isBoundByPatchIdentity(echoed, headSha, patchIdOf) {
3969
+ if (patchIdOf === undefined || !echoed.headSha)
3675
3970
  return false;
3676
- const judged = patchIdOf(extraction.headSha);
3971
+ const judged = patchIdOf(echoed.headSha);
3677
3972
  const current = patchIdOf(headSha);
3678
3973
  if (judged === null || current === null)
3679
3974
  return false;
3680
3975
  return judged === current;
3681
3976
  }
3682
- function isBoundToPatch(extraction, headSha, patchIdOf) {
3683
- return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3977
+ function isBoundToPatch(echoed, headSha, patchIdOf) {
3978
+ return isBoundToHead(echoed, headSha) || isBoundByPatchIdentity(echoed, headSha, patchIdOf);
3684
3979
  }
3685
- function isBoundToObjectives(extraction, currentVersion) {
3980
+ function isBoundToObjectives(echoed, currentVersion) {
3686
3981
  if (currentVersion === null)
3687
3982
  return true;
3688
- return extraction.objectivesVersion === currentVersion;
3983
+ return echoed.objectivesVersion === currentVersion;
3984
+ }
3985
+ function isBoundToRulings(echoed, currentOrdinal) {
3986
+ if (echoed.rulingOrdinal === null)
3987
+ return currentOrdinal === 0;
3988
+ return echoed.rulingOrdinal === currentOrdinal;
3989
+ }
3990
+ function isBoundToBriefHash(echoed, currentHash) {
3991
+ if (currentHash === null)
3992
+ return true;
3993
+ return echoed.briefHash === currentHash;
3994
+ }
3995
+ function isBoundToPolicy(echoed, currentDigest) {
3996
+ return echoed.policyDigest === currentDigest;
3689
3997
  }
3998
+ function compareManifest(echoed, current, patchIdOf) {
3999
+ const head = isBoundToPatch(echoed, current.headSha, patchIdOf);
4000
+ const briefHashBound = isBoundToBriefHash(echoed, current.briefHash);
4001
+ const objectivesVersion2 = isBoundToObjectives(echoed, current.objectivesVersion);
4002
+ const rulingOrdinal = isBoundToRulings(echoed, current.rulingOrdinal);
4003
+ const policyDigestBound = isBoundToPolicy(echoed, current.policyDigest);
4004
+ return {
4005
+ bound: head && briefHashBound && objectivesVersion2 && rulingOrdinal && policyDigestBound,
4006
+ head,
4007
+ briefHash: briefHashBound,
4008
+ objectivesVersion: objectivesVersion2,
4009
+ rulingOrdinal,
4010
+ policyDigest: policyDigestBound
4011
+ };
4012
+ }
4013
+
4014
+ // ../../packages/aeg-core/src/review-gate.ts
3690
4015
  function checkReviewGate(input) {
3691
4016
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3692
4017
  const waived = isWaiverLabelActorVerified({
@@ -3709,13 +4034,55 @@ function checkReviewGate(input) {
3709
4034
  const verifiedBodies = verified.map((c) => c.body);
3710
4035
  const codeReview = extractCodeReviewVerdict(verifiedBodies);
3711
4036
  const security = extractSecurityReviewVerdict(verifiedBodies);
3712
- const codeReviewClean = codeReview.value === "APPROVE";
3713
- const securityClean = security.value === "PASS";
3714
- const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3715
- const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3716
- const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3717
- const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3718
- if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
4037
+ const policy = input.policy ?? DEFAULT_REVIEW_POLICY;
4038
+ let codeReviewPolicyEvaluation;
4039
+ let securityPolicyEvaluation;
4040
+ try {
4041
+ codeReviewPolicyEvaluation = evaluateCodeReview(codeReview.findingSeverities, policy);
4042
+ securityPolicyEvaluation = evaluateSecurityReview(security.findingSeverities, policy);
4043
+ } catch (err) {
4044
+ return {
4045
+ verdict: "fail",
4046
+ reason: `a verdict comment carries a finding severity this repository's policy does not recognize: ${err instanceof Error ? err.message : String(err)}`,
4047
+ waived: false
4048
+ };
4049
+ }
4050
+ const codeReviewTextClean = codeReview.value === "APPROVE";
4051
+ const securityTextClean = security.value === "PASS";
4052
+ const codeReviewPolicyClean = codeReviewPolicyEvaluation.outcome === "clean";
4053
+ const securityPolicyClean = securityPolicyEvaluation.outcome === "clean";
4054
+ const codeReviewClean = codeReviewTextClean && codeReviewPolicyClean;
4055
+ const securityClean = securityTextClean && securityPolicyClean;
4056
+ const currentManifest = {
4057
+ headSha: input.headSha,
4058
+ briefHash: input.briefHash ?? null,
4059
+ objectivesVersion: input.objectivesVersion,
4060
+ rulingOrdinal: input.rulingOrdinal,
4061
+ policyDigest: policyDigest(policy)
4062
+ };
4063
+ const codeReviewEchoed = {
4064
+ headSha: codeReview.headSha,
4065
+ briefHash: codeReview.briefHash,
4066
+ objectivesVersion: codeReview.objectivesVersion,
4067
+ rulingOrdinal: codeReview.rulingOrdinal,
4068
+ policyDigest: codeReview.policyDigest
4069
+ };
4070
+ const securityEchoed = {
4071
+ headSha: security.headSha,
4072
+ briefHash: security.briefHash,
4073
+ objectivesVersion: security.objectivesVersion,
4074
+ rulingOrdinal: security.rulingOrdinal,
4075
+ policyDigest: security.policyDigest
4076
+ };
4077
+ const codeReviewBinding = compareManifest(codeReviewEchoed, currentManifest, input.patchIdOf);
4078
+ const securityBinding = compareManifest(securityEchoed, currentManifest, input.patchIdOf);
4079
+ const codeReviewBound = codeReviewBinding.head;
4080
+ const securityBound = securityBinding.head;
4081
+ const codeReviewObjectivesBound = codeReviewBinding.objectivesVersion;
4082
+ const securityObjectivesBound = securityBinding.objectivesVersion;
4083
+ const codeReviewRulingsBound = codeReviewBinding.rulingOrdinal;
4084
+ const securityRulingsBound = securityBinding.rulingOrdinal;
4085
+ if (codeReviewClean && codeReviewBinding.bound && securityClean && securityBinding.bound && mechanicalChecksClean) {
3719
4086
  return {
3720
4087
  verdict: "pass",
3721
4088
  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.`,
@@ -3723,19 +4090,35 @@ function checkReviewGate(input) {
3723
4090
  };
3724
4091
  }
3725
4092
  const problems = [];
3726
- if (!codeReviewClean) {
4093
+ if (!codeReviewTextClean) {
3727
4094
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
4095
+ } else if (!codeReviewPolicyClean) {
4096
+ 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`);
3728
4097
  } else if (!codeReviewBound) {
3729
4098
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3730
4099
  } else if (!codeReviewObjectivesBound) {
3731
4100
  problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3732
- }
3733
- if (!securityClean) {
4101
+ } else if (!codeReviewRulingsBound) {
4102
+ 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`);
4103
+ } else if (!codeReviewBinding.briefHash) {
4104
+ 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"}`);
4105
+ } else if (!codeReviewBinding.policyDigest) {
4106
+ 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}`);
4107
+ }
4108
+ if (!securityTextClean) {
3734
4109
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
4110
+ } else if (!securityPolicyClean) {
4111
+ 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`);
3735
4112
  } else if (!securityBound) {
3736
4113
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3737
4114
  } else if (!securityObjectivesBound) {
3738
4115
  problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
4116
+ } else if (!securityRulingsBound) {
4117
+ 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`);
4118
+ } else if (!securityBinding.briefHash) {
4119
+ 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"}`);
4120
+ } else if (!securityBinding.policyDigest) {
4121
+ 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}`);
3739
4122
  }
3740
4123
  if (!mechanicalChecksClean) {
3741
4124
  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(", ")}`);
@@ -3752,6 +4135,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
3752
4135
  function isChangesetsReleasePr(branch, author, expectedAuthor) {
3753
4136
  return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
3754
4137
  }
4138
+ // ../../packages/aeg-core/src/ruling-ordinal.ts
4139
+ var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
4140
+ function newestPrincipalRulingOrdinal(comments, allowlist) {
4141
+ let best = 0;
4142
+ for (const c of comments) {
4143
+ if (!isPrincipal(c.author, allowlist))
4144
+ continue;
4145
+ const firstLine = (c.body.split(`
4146
+ `)[0] ?? "").trim();
4147
+ const m = RULING_MARKER_ORDINAL.exec(firstLine);
4148
+ if (!m)
4149
+ continue;
4150
+ const k = Number.parseInt(m[1], 10);
4151
+ if (k > best)
4152
+ best = k;
4153
+ }
4154
+ return best;
4155
+ }
3755
4156
  // ../../packages/aeg-core/src/markdown-table.ts
3756
4157
  function splitRow(line) {
3757
4158
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -4409,42 +4810,45 @@ function checkDispatchReadiness(input) {
4409
4810
  const { trancheSlug, task } = input;
4410
4811
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
4411
4812
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
4412
- const blockers = [];
4813
+ const blockerDetails = [];
4814
+ const push = (blockerClass, message) => {
4815
+ blockerDetails.push({ class: blockerClass, message });
4816
+ };
4413
4817
  if (task.issue === null) {
4414
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
4818
+ 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.`);
4415
4819
  } else if (input.issue === null) {
4416
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4820
+ push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4417
4821
  }
4418
4822
  if (input.issue !== null && !input.issueRationalePass) {
4419
- 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.`);
4823
+ 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.`);
4420
4824
  }
4421
4825
  for (const dep of input.dependsOn) {
4422
4826
  if (isSelfDependency(dep, task, input.issue)) {
4423
4827
  const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4424
- 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.`);
4828
+ 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.`);
4425
4829
  continue;
4426
4830
  }
4427
4831
  if (dep.resolved === false) {
4428
- 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.`);
4832
+ 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.`);
4429
4833
  continue;
4430
4834
  }
4431
4835
  if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
4432
4836
  const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
4433
- blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
4837
+ 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.`);
4434
4838
  }
4435
4839
  }
4436
4840
  for (const c of input.conflictsWith) {
4437
4841
  if (c.openOrInFlight) {
4438
4842
  const issueStr = c.issue !== null ? ` (#${c.issue})` : "";
4439
- blockers.push(`dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
4843
+ 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.`);
4440
4844
  }
4441
4845
  }
4442
4846
  for (const proj of input.priorTrancheArchival) {
4443
4847
  if (proj.priorTrancheSlug !== null && !proj.archived) {
4444
- 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.`);
4848
+ 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.`);
4445
4849
  }
4446
4850
  }
4447
- return { ready: blockers.length === 0, blockers };
4851
+ return { ready: blockerDetails.length === 0, blockers: blockerDetails.map((b) => b.message), blockerDetails };
4448
4852
  }
4449
4853
  // ../../packages/aeg-core/src/single-plan-pr.ts
4450
4854
  function trancheSlugFromTopologyPath(path) {
@@ -4546,6 +4950,91 @@ function checkBranchTopology(input) {
4546
4950
  reason: `Branch \`${branch}\` matches topology row \`${taskId}\` in ${topoPath}.`
4547
4951
  };
4548
4952
  }
4953
+ // ../../packages/aeg-core/src/task-tools.ts
4954
+ import { z } from "zod";
4955
+ var TASK_TOOL_ERROR_KINDS = [
4956
+ "validation",
4957
+ "authority",
4958
+ "precondition",
4959
+ "capability",
4960
+ "infrastructure",
4961
+ "cancellation",
4962
+ "timeout",
4963
+ "uncertain_effect"
4964
+ ];
4965
+ var TaskToolErrorSchema = z.object({
4966
+ kind: z.enum(TASK_TOOL_ERROR_KINDS),
4967
+ message: z.string().min(1),
4968
+ detail: z.string().optional()
4969
+ });
4970
+ var TaskToolRefSchema = z.union([
4971
+ z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
4972
+ z.object({ issue: z.number().int().positive() })
4973
+ ]);
4974
+ var MAX_PAGE_LIMIT = 100;
4975
+ var PageRequestSchema = z.object({
4976
+ cursor: z.string().optional(),
4977
+ limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
4978
+ });
4979
+ var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
4980
+ var ObservedSchema = z.object({
4981
+ observedAt: z.string(),
4982
+ freshness: FreshnessSchema
4983
+ });
4984
+ var TaskStatusInputSchema = z.object({
4985
+ task: TaskToolRefSchema.optional()
4986
+ }).merge(PageRequestSchema);
4987
+ var TaskStatusItemSchema = z.object({
4988
+ task: TaskToolRefSchema,
4989
+ issue: z.number().int().positive(),
4990
+ pr: z.number().int().positive().nullable(),
4991
+ state: z.string()
4992
+ }).merge(ObservedSchema);
4993
+ var TaskStatusResultSchema = z.object({
4994
+ items: z.array(TaskStatusItemSchema),
4995
+ nextCursor: z.string().nullable()
4996
+ });
4997
+ var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
4998
+ var EscalationInputsSchema = z.object({
4999
+ task: z.number().int().positive(),
5000
+ round: z.number().int().nonnegative(),
5001
+ head: z.string(),
5002
+ branch: z.string(),
5003
+ prNumber: z.number().int().positive()
5004
+ });
5005
+ var EscalationEvidenceSchema = z.object({
5006
+ round: z.number().int().nonnegative(),
5007
+ reviewer: z.string().nullable(),
5008
+ security: z.string().nullable()
5009
+ });
5010
+ var TaskEscalationReadInputSchema = z.object({
5011
+ task: TaskToolRefSchema
5012
+ }).merge(PageRequestSchema);
5013
+ var TaskEscalationPacketSchema = z.object({
5014
+ reason: z.string(),
5015
+ detail: z.string().nullable(),
5016
+ inputs: EscalationInputsSchema.nullable(),
5017
+ evidence: EscalationEvidenceSchema.nullable(),
5018
+ attemptedRecovery: z.string(),
5019
+ requestedAuthority: RequestedAuthoritySchema,
5020
+ permittedNextActions: z.array(z.string())
5021
+ }).merge(ObservedSchema);
5022
+ var TaskEscalationReadResultSchema = z.object({
5023
+ items: z.array(TaskEscalationPacketSchema),
5024
+ nextCursor: z.string().nullable()
5025
+ }).merge(ObservedSchema);
5026
+ var TaskStartInputSchema = z.object({
5027
+ tranche: z.string().min(1),
5028
+ id: z.string().min(1)
5029
+ });
5030
+ var TaskResumeInputSchema = z.object({
5031
+ task: TaskToolRefSchema
5032
+ });
5033
+ var TaskCancelInputSchema = z.object({
5034
+ task: TaskToolRefSchema,
5035
+ reason: z.string().min(1)
5036
+ });
5037
+ var NoResultSchema = z.never();
4549
5038
  // ../../packages/aeg-core/src/first-push-dispatch-gate.ts
4550
5039
  function parseTaskBranch(branch) {
4551
5040
  const m = /^task\/([^/]+)\/([^/]+)$/.exec(branch);
@@ -4629,7 +5118,6 @@ function decideIssueAssignment(input) {
4629
5118
  };
4630
5119
  }
4631
5120
  // ../../packages/aeg-core/src/test-plan-gate.ts
4632
- var TASK_BRANCH_PATTERN2 = /^task\/[^/]+\/[^/]+$/;
4633
5121
  function evaluateTestPlanGate(body, branch) {
4634
5122
  if (!body) {
4635
5123
  return {
@@ -4642,7 +5130,7 @@ function evaluateTestPlanGate(body, branch) {
4642
5130
  }
4643
5131
  const located = locateTestPlanSection(body);
4644
5132
  if (!located.found) {
4645
- if (TASK_BRANCH_PATTERN2.test(branch)) {
5133
+ if (parseTaskBranchIdentity(branch) !== null) {
4646
5134
  return {
4647
5135
  verdict: "fail",
4648
5136
  messages: [
@@ -4770,7 +5258,7 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4770
5258
  return findings;
4771
5259
  }
4772
5260
  // ../../packages/aeg-core/src/log/schema.ts
4773
- import { z } from "zod";
5261
+ import { z as z2 } from "zod";
4774
5262
  var ROLE_VALUES = [
4775
5263
  "planner",
4776
5264
  "developer",
@@ -4780,136 +5268,172 @@ var ROLE_VALUES = [
4780
5268
  "archivist",
4781
5269
  "architect"
4782
5270
  ];
4783
- var RoleSchema = z.enum(ROLE_VALUES);
5271
+ var RoleSchema = z2.enum(ROLE_VALUES);
4784
5272
  var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4785
- var HostSchema = z.enum(HOST_VALUES);
5273
+ var HostSchema = z2.enum(HOST_VALUES);
4786
5274
  var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4787
- var HeaderMetaSchema = z.object({
4788
- schema: z.literal(1),
4789
- ts: z.string(),
4790
- run_id: z.string().regex(RUN_ID_PATTERN),
4791
- seq: z.number().int().nonnegative(),
4792
- repo: z.string().nullable(),
4793
- vinaya: z.string(),
4794
- doctrine: z.string(),
5275
+ var headerMetaCore = {
5276
+ ts: z2.string(),
5277
+ run_id: z2.string().regex(RUN_ID_PATTERN),
5278
+ seq: z2.number().int().nonnegative(),
5279
+ repo: z2.string().nullable(),
5280
+ vinaya: z2.string(),
5281
+ doctrine: z2.string(),
4795
5282
  host: HostSchema,
4796
- machine: z.string()
5283
+ machine: z2.string()
5284
+ };
5285
+ var HeaderMetaV1Schema = z2.object({
5286
+ schema: z2.literal(1),
5287
+ ...headerMetaCore
5288
+ }).strict();
5289
+ var LineageSchema = z2.object({
5290
+ run: z2.string().nullable(),
5291
+ attempt: z2.number().int().nullable(),
5292
+ parent: z2.string().nullable()
5293
+ }).strict();
5294
+ var InputVersionsSchema = z2.object({
5295
+ objectives_version: z2.string().nullable(),
5296
+ brief_hash: z2.string().nullable(),
5297
+ ruling_ordinal: z2.number().int().nullable(),
5298
+ policy_digest: z2.string().nullable()
5299
+ }).strict();
5300
+ var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
5301
+ var HeaderMetaV2Schema = z2.object({
5302
+ schema: z2.literal(2),
5303
+ ...headerMetaCore,
5304
+ event_id: z2.string().min(1),
5305
+ process_id: z2.string().min(1),
5306
+ actor_id: z2.string().nullable(),
5307
+ lineage: LineageSchema,
5308
+ input_versions: InputVersionsSchema,
5309
+ provenance: ProvenanceSchema
4797
5310
  }).strict();
4798
- var SubjectSchema = z.object({
4799
- issue: z.number().int().nullable(),
4800
- pr: z.number().int().optional(),
4801
- sha: z.string().optional(),
4802
- role: z.union([RoleSchema, z.literal("unattributed")]),
4803
- round: z.number().int().optional(),
4804
- objectives_version: z.string().optional()
5311
+ var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
5312
+ var SubjectSchema = z2.object({
5313
+ issue: z2.number().int().nullable(),
5314
+ pr: z2.number().int().optional(),
5315
+ sha: z2.string().optional(),
5316
+ role: z2.union([RoleSchema, z2.literal("unattributed")]),
5317
+ round: z2.number().int().optional(),
5318
+ objectives_version: z2.string().optional()
4805
5319
  }).strict();
4806
- var HeaderSchema = z.object({
5320
+ var HeaderSchema = z2.object({
4807
5321
  meta: HeaderMetaSchema,
4808
5322
  subject: SubjectSchema
4809
5323
  }).strict();
4810
5324
  var envelopeTail = {
4811
- duration_ms: z.number().nonnegative().optional(),
4812
- payload: z.object({}).strict()
5325
+ duration_ms: z2.number().nonnegative().optional(),
5326
+ payload: z2.object({}).strict()
4813
5327
  };
4814
- var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4815
- z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4816
- z.object({
4817
- type: z.literal("round_pushed"),
4818
- pr: z.number().int(),
4819
- head: z.string(),
4820
- comment_id: z.number().int()
5328
+ var ReviewFindingSchema = z2.object({
5329
+ id: z2.string(),
5330
+ severity: z2.string(),
5331
+ state: z2.string().optional(),
5332
+ severity_scale: z2.string().optional(),
5333
+ policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
5334
+ confidence: z2.number().min(0).max(1).optional(),
5335
+ confidence_scale: z2.string().optional(),
5336
+ confidence_source: z2.string().optional()
5337
+ }).strict();
5338
+ var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
5339
+ z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
5340
+ z2.object({
5341
+ type: z2.literal("round_pushed"),
5342
+ pr: z2.number().int(),
5343
+ head: z2.string(),
5344
+ comment_id: z2.number().int()
4821
5345
  }).strict(),
4822
- z.object({
4823
- type: z.literal("verdict"),
4824
- verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4825
- head: z.string(),
4826
- comment_id: z.number().int(),
4827
- objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4828
- findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
5346
+ z2.object({
5347
+ type: z2.literal("verdict"),
5348
+ verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
5349
+ head: z2.string(),
5350
+ comment_id: z2.number().int(),
5351
+ objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
5352
+ findings: z2.array(ReviewFindingSchema)
4829
5353
  }).strict(),
4830
- z.object({
4831
- type: z.literal("escalation"),
4832
- class: z.enum(["authority", "strategy", "product"]),
4833
- comment_id: z.number().int()
5354
+ z2.object({
5355
+ type: z2.literal("escalation"),
5356
+ class: z2.enum(["authority", "strategy", "product"]),
5357
+ comment_id: z2.number().int()
4834
5358
  }).strict(),
4835
- z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4836
- z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4837
- z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
5359
+ z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
5360
+ z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
5361
+ z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict()
4838
5362
  ]);
4839
5363
  var dispatchShared = {
4840
5364
  meta: HeaderMetaSchema,
4841
5365
  subject: SubjectSchema,
4842
- kind: z.literal("dispatch"),
5366
+ kind: z2.literal("dispatch"),
4843
5367
  ...envelopeTail,
4844
5368
  target_role: RoleSchema,
4845
- model: z.string(),
4846
- round: z.number().int().optional(),
4847
- effect_id: z.string()
5369
+ model: z2.string(),
5370
+ round: z2.number().int().optional(),
5371
+ effect_id: z2.string()
4848
5372
  };
4849
- var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
4850
- var DispatchEventSchema = z.discriminatedUnion("event", [
4851
- z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4852
- z.object({
5373
+ var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
5374
+ var DispatchEventSchema = z2.discriminatedUnion("event", [
5375
+ z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
5376
+ z2.object({
4853
5377
  ...dispatchShared,
4854
- event: z.literal("outcome_received"),
5378
+ event: z2.literal("outcome_received"),
4855
5379
  outcome: DispatchOutcomeSchema,
4856
5380
  usage: dispatchUsageField
4857
5381
  }).strict(),
4858
- z.object({
5382
+ z2.object({
4859
5383
  ...dispatchShared,
4860
- event: z.literal("dispatch_failed"),
4861
- reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
5384
+ event: z2.literal("dispatch_failed"),
5385
+ reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
4862
5386
  usage: dispatchUsageField
4863
5387
  }).strict()
4864
5388
  ]);
4865
5389
  var loopShared = {
4866
5390
  meta: HeaderMetaSchema,
4867
5391
  subject: SubjectSchema,
4868
- kind: z.literal("dev_review_loop"),
5392
+ kind: z2.literal("dev_review_loop"),
4869
5393
  ...envelopeTail,
4870
- loop_id: z.string()
5394
+ loop_id: z2.string()
4871
5395
  };
4872
- var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4873
- z.object({
5396
+ var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
5397
+ z2.object({
4874
5398
  ...loopShared,
4875
- event: z.literal("loop_started"),
4876
- task: z.number().int(),
4877
- policy: z.object({
4878
- max_rounds: z.number().int().nonnegative(),
4879
- reviewers: z.array(RoleSchema),
4880
- models: z.record(RoleSchema, z.string())
5399
+ event: z2.literal("loop_started"),
5400
+ task: z2.number().int(),
5401
+ policy: z2.object({
5402
+ max_rounds: z2.number().int().nonnegative(),
5403
+ reviewers: z2.array(RoleSchema),
5404
+ models: z2.record(RoleSchema, z2.string())
4881
5405
  }).strict()
4882
5406
  }).strict(),
4883
- z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4884
- z.object({
5407
+ z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
5408
+ z2.object({
4885
5409
  ...loopShared,
4886
- event: z.literal("gate_result_read"),
4887
- round: z.number().int(),
4888
- head: z.string(),
4889
- green: z.boolean()
5410
+ event: z2.literal("gate_result_read"),
5411
+ round: z2.number().int(),
5412
+ head: z2.string(),
5413
+ green: z2.boolean()
4890
5414
  }).strict(),
4891
- z.object({
5415
+ z2.object({
4892
5416
  ...loopShared,
4893
- event: z.literal("verdicts_read"),
4894
- round: z.number().int(),
4895
- head: z.string(),
4896
- all_approve: z.boolean(),
4897
- blockers: z.number().int().nonnegative()
5417
+ event: z2.literal("verdicts_read"),
5418
+ round: z2.number().int(),
5419
+ head: z2.string(),
5420
+ all_approve: z2.boolean(),
5421
+ blockers: z2.number().int().nonnegative()
4898
5422
  }).strict(),
4899
- z.object({
5423
+ z2.object({
4900
5424
  ...loopShared,
4901
- event: z.literal("findings_compared"),
4902
- round: z.number().int(),
4903
- open: z.array(z.string()),
4904
- resolved: z.array(z.string()),
4905
- new: z.array(z.string()),
4906
- recurring: z.array(z.string())
5425
+ event: z2.literal("findings_compared"),
5426
+ round: z2.number().int(),
5427
+ open: z2.array(z2.string()),
5428
+ resolved: z2.array(z2.string()),
5429
+ new: z2.array(z2.string()),
5430
+ recurring: z2.array(z2.string())
4907
5431
  }).strict(),
4908
- z.object({
5432
+ z2.object({
4909
5433
  ...loopShared,
4910
- event: z.literal("stop_condition_met"),
4911
- round: z.number().int(),
4912
- condition: z.enum([
5434
+ event: z2.literal("stop_condition_met"),
5435
+ round: z2.number().int(),
5436
+ condition: z2.enum([
4913
5437
  "green",
4914
5438
  "max_rounds",
4915
5439
  "no_progress",
@@ -4919,42 +5443,49 @@ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4919
5443
  "reappearance"
4920
5444
  ])
4921
5445
  }).strict(),
4922
- z.object({
5446
+ z2.object({
5447
+ ...loopShared,
5448
+ event: z2.literal("paused"),
5449
+ round: z2.number().int(),
5450
+ reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
5451
+ }).strict(),
5452
+ z2.object({
4923
5453
  ...loopShared,
4924
- event: z.literal("paused"),
4925
- round: z.number().int(),
4926
- reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
5454
+ event: z2.literal("resumed"),
5455
+ round: z2.number().int(),
5456
+ by: z2.literal("principal")
4927
5457
  }).strict(),
4928
- z.object({
5458
+ z2.object({
4929
5459
  ...loopShared,
4930
- event: z.literal("resumed"),
4931
- round: z.number().int(),
4932
- by: z.literal("principal")
5460
+ event: z2.literal("unpushed_work_resume"),
5461
+ round: z2.number().int(),
5462
+ branch: z2.string(),
5463
+ detail: z2.string()
4933
5464
  }).strict(),
4934
- z.object({
5465
+ z2.object({
4935
5466
  ...loopShared,
4936
- event: z.literal("round_ended"),
4937
- round: z.number().int(),
4938
- base_head: z.string(),
4939
- head: z.string(),
4940
- files_changed: z.number().int().nonnegative(),
4941
- insertions: z.number().int().nonnegative(),
4942
- deletions: z.number().int().nonnegative(),
4943
- wall_ms: z.number().nonnegative(),
4944
- outcome: z.enum(["green", "changes_requested", "escalated"])
5467
+ event: z2.literal("round_ended"),
5468
+ round: z2.number().int(),
5469
+ base_head: z2.string(),
5470
+ head: z2.string(),
5471
+ files_changed: z2.number().int().nonnegative(),
5472
+ insertions: z2.number().int().nonnegative(),
5473
+ deletions: z2.number().int().nonnegative(),
5474
+ wall_ms: z2.number().nonnegative(),
5475
+ outcome: z2.enum(["green", "changes_requested", "escalated"])
4945
5476
  }).strict(),
4946
- z.object({
5477
+ z2.object({
4947
5478
  ...loopShared,
4948
- event: z.literal("journal_finalized"),
4949
- rounds: z.number().int().nonnegative(),
4950
- total_wall_ms: z.number().nonnegative(),
4951
- time_to_green_ms: z.number().nonnegative().nullable(),
4952
- files_changed_total: z.number().int().nonnegative(),
4953
- final_head: z.string(),
4954
- result: z.enum(["merged_ready", "stopped"])
5479
+ event: z2.literal("journal_finalized"),
5480
+ rounds: z2.number().int().nonnegative(),
5481
+ total_wall_ms: z2.number().nonnegative(),
5482
+ time_to_green_ms: z2.number().nonnegative().nullable(),
5483
+ files_changed_total: z2.number().int().nonnegative(),
5484
+ final_head: z2.string(),
5485
+ result: z2.enum(["merged_ready", "stopped"])
4955
5486
  }).strict()
4956
5487
  ]);
4957
- var ForgeOpSchema = z.enum([
5488
+ var ForgeOpSchema = z2.enum([
4958
5489
  "pr.create",
4959
5490
  "pr.comment",
4960
5491
  "pr.body.replace",
@@ -4968,50 +5499,238 @@ var ForgeOpSchema = z.enum([
4968
5499
  "label.add",
4969
5500
  "label.remove"
4970
5501
  ]);
4971
- var ForgeWriteTargetSchema = z.object({
4972
- issue: z.number().int().optional(),
4973
- pr: z.number().int().optional()
5502
+ var ForgeWriteTargetSchema = z2.object({
5503
+ issue: z2.number().int().optional(),
5504
+ pr: z2.number().int().optional()
4974
5505
  }).strict();
4975
5506
  var forgeWriteShared = {
4976
5507
  meta: HeaderMetaSchema,
4977
5508
  subject: SubjectSchema,
4978
- kind: z.literal("forge_write"),
5509
+ kind: z2.literal("forge_write"),
4979
5510
  ...envelopeTail,
4980
5511
  op: ForgeOpSchema,
4981
5512
  target: ForgeWriteTargetSchema
4982
5513
  };
4983
- var ForgeWriteEventSchema = z.discriminatedUnion("event", [
4984
- z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
4985
- z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
4986
- z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
5514
+ var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
5515
+ z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
5516
+ z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
5517
+ z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
5518
+ ]);
5519
+ var GateOutcomeSchema = z2.enum([
5520
+ "pass",
5521
+ "fail",
5522
+ "wait",
5523
+ "skip",
5524
+ "invalid_input",
5525
+ "unavailable_dependency",
5526
+ "timeout",
5527
+ "cancelled"
5528
+ ]);
5529
+ var gateShared = {
5530
+ meta: HeaderMetaSchema,
5531
+ subject: SubjectSchema,
5532
+ kind: z2.literal("gate"),
5533
+ ...envelopeTail,
5534
+ check: z2.string(),
5535
+ check_version: z2.string().nullable(),
5536
+ policy_version: z2.string().nullable(),
5537
+ input_fingerprint: z2.string().nullable()
5538
+ };
5539
+ var GateEventSchema = z2.discriminatedUnion("event", [
5540
+ z2.object({
5541
+ ...gateShared,
5542
+ event: z2.literal("checked"),
5543
+ outcome: GateOutcomeSchema,
5544
+ reason: z2.string().optional()
5545
+ }).strict()
5546
+ ]);
5547
+ var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
5548
+ var operationShared = {
5549
+ meta: HeaderMetaSchema,
5550
+ subject: SubjectSchema,
5551
+ kind: z2.literal("operation"),
5552
+ ...envelopeTail,
5553
+ operation: z2.string(),
5554
+ target: z2.string().nullable()
5555
+ };
5556
+ var OperationEventSchema = z2.discriminatedUnion("event", [
5557
+ z2.object({
5558
+ ...operationShared,
5559
+ event: z2.literal("completed"),
5560
+ result: OperationResultSchema,
5561
+ error_class: z2.string().nullable()
5562
+ }).strict()
5563
+ ]);
5564
+ var UsageUnitsSchema = z2.object({
5565
+ input: z2.number().nonnegative().nullable(),
5566
+ output: z2.number().nonnegative().nullable(),
5567
+ cache: z2.number().nonnegative().nullable()
5568
+ }).strict();
5569
+ var usageShared = {
5570
+ meta: HeaderMetaSchema,
5571
+ subject: SubjectSchema,
5572
+ kind: z2.literal("usage"),
5573
+ ...envelopeTail,
5574
+ model: z2.string().nullable(),
5575
+ source: z2.string(),
5576
+ semantics: z2.enum(["cumulative", "delta"])
5577
+ };
5578
+ var UsageEventSchema = z2.discriminatedUnion("event", [
5579
+ z2.object({
5580
+ ...usageShared,
5581
+ event: z2.literal("observed"),
5582
+ units: UsageUnitsSchema,
5583
+ unknown_reason: z2.string().nullable()
5584
+ }).strict()
5585
+ ]);
5586
+ var RoleAttemptOutcomeSchema = z2.enum([
5587
+ "completed",
5588
+ "incomplete",
5589
+ "infrastructure_failed",
5590
+ "cancelled",
5591
+ "timed_out",
5592
+ "capability_refused"
5593
+ ]);
5594
+ var roleAttemptShared = {
5595
+ meta: HeaderMetaSchema,
5596
+ subject: SubjectSchema,
5597
+ kind: z2.literal("role_attempt"),
5598
+ ...envelopeTail,
5599
+ actor: z2.string().nullable(),
5600
+ attempt: z2.number().int().nullable()
5601
+ };
5602
+ var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
5603
+ z2.object({
5604
+ ...roleAttemptShared,
5605
+ event: z2.literal("attempted"),
5606
+ outcome: RoleAttemptOutcomeSchema,
5607
+ usage: dispatchUsageField
5608
+ }).strict()
5609
+ ]);
5610
+ var handoffShared = {
5611
+ meta: HeaderMetaSchema,
5612
+ subject: SubjectSchema,
5613
+ kind: z2.literal("handoff"),
5614
+ ...envelopeTail,
5615
+ class: z2.enum(["authority", "strategy", "product"]),
5616
+ reason: z2.string()
5617
+ };
5618
+ var HandoffEventSchema = z2.discriminatedUnion("event", [
5619
+ z2.object({
5620
+ ...handoffShared,
5621
+ event: z2.literal("raised"),
5622
+ requested_decision: z2.string().nullable()
5623
+ }).strict(),
5624
+ z2.object({
5625
+ ...handoffShared,
5626
+ event: z2.literal("resolved"),
5627
+ resolution: z2.string().nullable(),
5628
+ resolved_by: z2.string().nullable()
5629
+ }).strict()
5630
+ ]);
5631
+ var EffectTargetSchema = z2.object({
5632
+ kind: z2.string(),
5633
+ ref: z2.string()
5634
+ }).strict();
5635
+ var effectShared = {
5636
+ meta: HeaderMetaSchema,
5637
+ subject: SubjectSchema,
5638
+ kind: z2.literal("effect"),
5639
+ ...envelopeTail,
5640
+ effect_id: z2.string(),
5641
+ target: EffectTargetSchema
5642
+ };
5643
+ var EffectEventSchema = z2.discriminatedUnion("event", [
5644
+ z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
5645
+ z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
5646
+ z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
4987
5647
  ]);
4988
- var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
5648
+ var LogEventSchema = z2.union([
5649
+ DispatchEventSchema,
5650
+ DevReviewLoopEventSchema,
5651
+ ForgeWriteEventSchema,
5652
+ GateEventSchema,
5653
+ OperationEventSchema,
5654
+ UsageEventSchema,
5655
+ RoleAttemptEventSchema,
5656
+ HandoffEventSchema,
5657
+ EffectEventSchema
5658
+ ]);
5659
+ // ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
5660
+ var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
5661
+ // ../../packages/aeg-core/src/control-store/records.ts
5662
+ import { z as z3 } from "zod";
5663
+ var isoTimestamp = z3.string().min(1);
5664
+ var taskId = z3.number().int().positive();
5665
+ var epochNumber = z3.number().int().nonnegative();
5666
+ var RunRecordSchema = z3.object({
5667
+ version: z3.literal(1),
5668
+ kind: z3.literal("run"),
5669
+ task: taskId,
5670
+ runId: z3.string().min(1),
5671
+ pid: z3.number().int().positive(),
5672
+ host: z3.string().min(1),
5673
+ startedAt: isoTimestamp
5674
+ }).strict();
5675
+ var InputRecordSchema = z3.object({
5676
+ version: z3.literal(1),
5677
+ kind: z3.literal("input"),
5678
+ task: taskId,
5679
+ runId: z3.string().min(1),
5680
+ source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
5681
+ pr: z3.number().int().positive().nullable(),
5682
+ round: z3.number().int().nonnegative(),
5683
+ recordedAt: isoTimestamp
5684
+ }).strict();
5685
+ var OwnershipRecordSchema = z3.object({
5686
+ version: z3.literal(1),
5687
+ kind: z3.literal("ownership"),
5688
+ task: taskId,
5689
+ epoch: epochNumber,
5690
+ ownerId: z3.string().min(1),
5691
+ pid: z3.number().int().positive(),
5692
+ host: z3.string().min(1),
5693
+ acquiredAt: isoTimestamp
5694
+ }).strict();
5695
+ var TransitionRecordSchema = z3.object({
5696
+ version: z3.literal(1),
5697
+ kind: z3.literal("transition"),
5698
+ task: taskId,
5699
+ epoch: epochNumber,
5700
+ seq: z3.number().int().nonnegative(),
5701
+ from: z3.string().min(1),
5702
+ to: z3.string().min(1),
5703
+ detail: z3.string().optional(),
5704
+ at: isoTimestamp
5705
+ }).strict();
4989
5706
  // src/lib/config.ts
4990
5707
  import { execFileSync as execFileSync2 } from "node:child_process";
4991
5708
  import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
4992
5709
  import { homedir } from "node:os";
4993
5710
  import { dirname, join, resolve } from "node:path";
4994
- import { z as z2 } from "zod";
5711
+ import { z as z4 } from "zod";
4995
5712
 
4996
5713
  // src/lib/agent-vendors.ts
4997
5714
  var AGENT_VENDORS = ["skills", "claude", "gemini"];
4998
5715
 
4999
5716
  // src/lib/config.ts
5000
- var EnvEntrySchema = z2.union([
5001
- z2.literal(true),
5002
- z2.object({ optional: z2.literal(true) }),
5003
- z2.object({ anyOf: z2.array(z2.string()).min(2) }),
5004
- z2.string()
5717
+ var EnvEntrySchema = z4.union([
5718
+ z4.literal(true),
5719
+ z4.object({ optional: z4.literal(true) }),
5720
+ z4.object({ anyOf: z4.array(z4.string()).min(2) }),
5721
+ z4.string()
5005
5722
  ]);
5006
- var CheckEntrySchema = z2.object({
5007
- run: z2.string(),
5008
- scope: z2.enum(["diff", "full"]),
5009
- include: z2.array(z2.string()).optional(),
5010
- args: z2.array(z2.string()).optional(),
5011
- timeoutMs: z2.number().optional(),
5012
- env: z2.record(z2.string(), EnvEntrySchema).optional(),
5013
- requiresOpenPr: z2.boolean().optional(),
5014
- ownWorkflow: z2.boolean().optional()
5723
+ var CheckEntrySchema = z4.object({
5724
+ run: z4.string(),
5725
+ scope: z4.enum(["diff", "full"]),
5726
+ include: z4.array(z4.string()).optional(),
5727
+ args: z4.array(z4.string()).optional(),
5728
+ timeoutMs: z4.number().optional(),
5729
+ env: z4.record(z4.string(), EnvEntrySchema).optional(),
5730
+ requiresOpenPr: z4.boolean().optional(),
5731
+ ownWorkflow: z4.boolean().optional(),
5732
+ principalOwed: z4.literal(true).optional(),
5733
+ validates: z4.enum(["body", "issue"]).optional()
5015
5734
  }).superRefine((entry2, ctx) => {
5016
5735
  if (!entry2.env)
5017
5736
  return;
@@ -5021,25 +5740,26 @@ var CheckEntrySchema = z2.object({
5021
5740
  const members = value.anyOf;
5022
5741
  if (new Set(members).size !== members.length) {
5023
5742
  ctx.addIssue({
5024
- code: z2.ZodIssueCode.custom,
5743
+ code: z4.ZodIssueCode.custom,
5025
5744
  message: `env.${key}.anyOf has duplicate members`,
5026
5745
  path: ["env", key, "anyOf"]
5027
5746
  });
5028
5747
  }
5029
5748
  if (!members.includes(key)) {
5030
5749
  ctx.addIssue({
5031
- code: z2.ZodIssueCode.custom,
5750
+ code: z4.ZodIssueCode.custom,
5032
5751
  message: `env.${key}.anyOf must include "${key}" itself as a member`,
5033
5752
  path: ["env", key, "anyOf"]
5034
5753
  });
5035
5754
  }
5036
5755
  }
5037
5756
  });
5038
- var RoleEntrySchema = z2.object({
5039
- contract: z2.string().refine((p) => p.includes("/"), {
5757
+ var RoleEntrySchema = z4.object({
5758
+ contract: z4.string().refine((p) => p.includes("/"), {
5040
5759
  message: 'must be a path (contain at least one "/"), not a bare filename'
5041
5760
  })
5042
5761
  });
5762
+ var MAX_REPORT_COMMAND_TIMEOUT_MS = 3600000;
5043
5763
  var BRIEF_BUILTINS = [
5044
5764
  "tier",
5045
5765
  "testPlan",
@@ -5059,17 +5779,17 @@ var BRIEF_BUILTINS = [
5059
5779
  "briefSections",
5060
5780
  "milestoneShape"
5061
5781
  ];
5062
- var BriefSectionSchema = z2.union([
5063
- z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
5064
- z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
5065
- z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
5066
- z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
5782
+ var BriefSectionSchema = z4.union([
5783
+ z4.object({ builtin: z4.enum(BRIEF_BUILTINS) }),
5784
+ z4.object({ heading: z4.string().min(1), name: z4.string().optional() }),
5785
+ z4.object({ field: z4.string().min(1), name: z4.string().optional() }),
5786
+ z4.object({ phrase: z4.string().min(1), name: z4.string().optional() })
5067
5787
  ]);
5068
- var BriefSchemaSchema = z2.object({
5069
- pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5070
- issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5071
- milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5072
- ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
5788
+ var BriefSchemaSchema = z4.object({
5789
+ pr: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5790
+ issue: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5791
+ milestone: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5792
+ ack: z4.array(z4.enum(BRIEF_BUILTINS)).optional()
5073
5793
  });
5074
5794
  function isSafeRepoRelPath(p) {
5075
5795
  if (p.length === 0)
@@ -5078,7 +5798,7 @@ function isSafeRepoRelPath(p) {
5078
5798
  return false;
5079
5799
  return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
5080
5800
  }
5081
- var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
5801
+ var SafeRepoRelPath = z4.string().refine(isSafeRepoRelPath, {
5082
5802
  message: "must be a repo-root-relative path with no `..` segment or absolute root"
5083
5803
  });
5084
5804
  var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
@@ -5088,22 +5808,22 @@ function isCanonicalHookBlockPath(p) {
5088
5808
  var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
5089
5809
  message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
5090
5810
  });
5091
- var ManagedBlockRecordSchema = z2.object({
5811
+ var ManagedBlockRecordSchema = z4.object({
5092
5812
  path: ManagedHookBlockPath,
5093
- marker: z2.string(),
5094
- comment: z2.enum(["hash", "html"])
5813
+ marker: z4.string(),
5814
+ comment: z4.enum(["hash", "html"])
5095
5815
  });
5096
- var ManagedManifestSchema = z2.object({
5097
- version: z2.number().int().positive(),
5098
- files: z2.array(SafeRepoRelPath),
5099
- blocks: z2.array(ManagedBlockRecordSchema),
5100
- labels: z2.array(z2.string()),
5101
- agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
5816
+ var ManagedManifestSchema = z4.object({
5817
+ version: z4.number().int().positive(),
5818
+ files: z4.array(SafeRepoRelPath),
5819
+ blocks: z4.array(ManagedBlockRecordSchema),
5820
+ labels: z4.array(z4.string()),
5821
+ agents: z4.array(z4.enum(AGENT_VENDORS)).optional()
5102
5822
  });
5103
- var ProjectEntrySchema = z2.object({
5104
- name: z2.string().min(1),
5105
- description: z2.string().min(1).optional(),
5106
- path: z2.string().min(1).optional()
5823
+ var ProjectEntrySchema = z4.object({
5824
+ name: z4.string().min(1),
5825
+ description: z4.string().min(1).optional(),
5826
+ path: z4.string().min(1).optional()
5107
5827
  });
5108
5828
  function parseTokensCollectDeclaration(value) {
5109
5829
  const m = value.trim().match(/^(\S+)\s+(\S+)$/);
@@ -5114,34 +5834,55 @@ function parseTokensCollectDeclaration(value) {
5114
5834
  return null;
5115
5835
  return { interpreter, script };
5116
5836
  }
5117
- var VinayaConfigSchema = z2.object({
5118
- rings: z2.object({
5119
- ring1_forgeWriteInterception: z2.boolean(),
5120
- ring2_asyncAudits: z2.boolean()
5837
+ var VinayaConfigSchema = z4.object({
5838
+ rings: z4.object({
5839
+ ring1_forgeWriteInterception: z4.boolean(),
5840
+ ring2_asyncAudits: z4.boolean()
5121
5841
  }).optional(),
5122
- checks: z2.record(z2.string(), CheckEntrySchema).optional(),
5123
- roles: z2.record(z2.string(), RoleEntrySchema).optional(),
5842
+ checks: z4.record(z4.string(), CheckEntrySchema).optional(),
5843
+ roles: z4.record(z4.string(), RoleEntrySchema).optional(),
5124
5844
  briefSchema: BriefSchemaSchema.optional(),
5125
5845
  managed: ManagedManifestSchema.optional(),
5126
- principals: z2.array(z2.string()).min(1).optional(),
5127
- releaseActor: z2.string().min(1).optional(),
5128
- ci: z2.object({ setup: z2.string().min(1) }).optional(),
5129
- tokens: z2.object({
5130
- collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5846
+ principals: z4.array(z4.string()).min(1).optional(),
5847
+ releaseActor: z4.string().min(1).optional(),
5848
+ ci: z4.object({ setup: z4.string().min(1) }).optional(),
5849
+ tokens: z4.object({
5850
+ collect: z4.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5131
5851
  message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
5132
5852
  })
5133
5853
  }).optional(),
5134
- blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
5135
- proseGates: z2.object({
5136
- doctrineRoot: z2.string().min(1).optional(),
5137
- readerFacingPrefix: z2.string().min(1).optional(),
5138
- readerFacingSuffix: z2.string().min(1).optional(),
5139
- legacySlugDir: z2.string().min(1).optional()
5854
+ blastRadius: z4.object({ extraDomains: z4.array(z4.string()).optional() }).optional(),
5855
+ proseGates: z4.object({
5856
+ doctrineRoot: z4.string().min(1).optional(),
5857
+ readerFacingPrefix: z4.string().min(1).optional(),
5858
+ readerFacingSuffix: z4.string().min(1).optional(),
5859
+ legacySlugDir: z4.string().min(1).optional()
5860
+ }).optional(),
5861
+ projects: z4.array(ProjectEntrySchema).optional(),
5862
+ dispatch: z4.object({
5863
+ timeoutMs: z4.number().int().positive().optional(),
5864
+ killGraceMs: z4.number().int().positive().optional(),
5865
+ agent: z4.enum(["claude", "codex", "gemini"]).optional()
5866
+ }).optional(),
5867
+ reviewPolicy: z4.object({
5868
+ codeReviewThreshold: z4.string().min(1).optional(),
5869
+ securityThreshold: z4.string().min(1).optional(),
5870
+ maxRounds: z4.number().optional()
5140
5871
  }).optional(),
5141
- projects: z2.array(ProjectEntrySchema).optional(),
5142
- dispatch: z2.object({
5143
- timeoutMs: z2.number().int().positive().optional(),
5144
- agent: z2.enum(["claude", "codex", "gemini"]).optional()
5872
+ prePush: z4.object({
5873
+ alwaysRun: z4.array(z4.string()).optional()
5874
+ }).optional(),
5875
+ report: z4.object({
5876
+ commandTimeoutMs: z4.number().int().positive().optional()
5877
+ }).superRefine((report, ctx) => {
5878
+ const commandTimeoutMs = report.commandTimeoutMs;
5879
+ if (commandTimeoutMs !== undefined && commandTimeoutMs > MAX_REPORT_COMMAND_TIMEOUT_MS) {
5880
+ ctx.addIssue({
5881
+ code: z4.ZodIssueCode.custom,
5882
+ path: ["commandTimeoutMs"],
5883
+ message: "must be at most 3600000 (1 hour)"
5884
+ });
5885
+ }
5145
5886
  }).optional()
5146
5887
  });
5147
5888
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
@@ -5216,6 +5957,28 @@ function resolvePrincipalAllowlist(config) {
5216
5957
  function resolveReleaseActor(config) {
5217
5958
  return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
5218
5959
  }
5960
+ function resolveReviewPolicy(config) {
5961
+ const raw = config?.reviewPolicy;
5962
+ if (!raw)
5963
+ return DEFAULT_REVIEW_POLICY;
5964
+ const codeReviewThreshold = raw.codeReviewThreshold ?? DEFAULT_REVIEW_POLICY.codeReviewThreshold;
5965
+ if (!isKnownSeverity(CODE_REVIEW_SEVERITY_ORDER, codeReviewThreshold)) {
5966
+ 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.`);
5967
+ }
5968
+ const securityThreshold = raw.securityThreshold ?? DEFAULT_REVIEW_POLICY.securityThreshold;
5969
+ if (!isKnownSeverity(SECURITY_SEVERITY_ORDER, securityThreshold)) {
5970
+ 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.`);
5971
+ }
5972
+ const maxRounds = raw.maxRounds ?? DEFAULT_REVIEW_POLICY.maxRounds;
5973
+ if (!Number.isInteger(maxRounds) || maxRounds < 1) {
5974
+ throw new Error(`vinaya.config.json: reviewPolicy.maxRounds "${maxRounds}" is not a positive integer — fix the config, this never falls back to a default.`);
5975
+ }
5976
+ return {
5977
+ codeReviewThreshold,
5978
+ securityThreshold,
5979
+ maxRounds
5980
+ };
5981
+ }
5219
5982
  function trustAnchorRepo() {
5220
5983
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
5221
5984
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -5295,6 +6058,9 @@ function loadConfig() {
5295
6058
  }
5296
6059
  var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
5297
6060
 
6061
+ // src/lib/brief-assembly.ts
6062
+ import { execFileSync as execFileSync4 } from "node:child_process";
6063
+
5298
6064
  // ../../packages/sources/src/commands.ts
5299
6065
  var COMMANDS = [
5300
6066
  {
@@ -5415,14 +6181,52 @@ var COMMANDS = [
5415
6181
  },
5416
6182
  {
5417
6183
  name: "task dispatch",
5418
- description: "Render, pin, and post the brief on the Issue as the frozen original; start the developer",
6184
+ description: "Deprecated — render, pin, and post the brief on the Issue as the frozen original; start the developer",
5419
6185
  flags: [
5420
6186
  { flag: "--agent <claude|codex|gemini>", description: "Start the developer through dispatchRole once posted" }
5421
6187
  ],
5422
6188
  details: [
5423
6189
  "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.",
5424
6190
  "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.",
5425
- "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."
6191
+ "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.",
6192
+ "Deprecated in favor of `task brief` (preparation only) and `task run` (the full unattended loop) — kept for a documented compatibility window while callers migrate."
6193
+ ],
6194
+ status: "shipped"
6195
+ },
6196
+ {
6197
+ name: "task brief",
6198
+ description: "Render and freeze the brief as the Issue's original comment — preparation only, starts nobody",
6199
+ details: [
6200
+ "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.",
6201
+ "Successor to `task dispatch` for the preparation step; the full unattended run (preparation, then the developer, then the review loop) is `task run`."
6202
+ ],
6203
+ status: "shipped"
6204
+ },
6205
+ {
6206
+ name: "task run",
6207
+ description: "One command from a planned Issue to a reviewed pull request — exactly one developer started",
6208
+ flags: [
6209
+ {
6210
+ flag: "--agent <claude|codex|gemini>",
6211
+ description: "Vendor for the developer and both reviewers this run dispatches"
6212
+ }
6213
+ ],
6214
+ details: [
6215
+ "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.",
6216
+ "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.",
6217
+ "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.",
6218
+ "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."
6219
+ ],
6220
+ status: "shipped"
6221
+ },
6222
+ {
6223
+ name: "task status",
6224
+ description: "Every open task with a frozen brief, its pull request, and whether its loop is running, paused, or published",
6225
+ flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
6226
+ details: [
6227
+ "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`.",
6228
+ "`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.",
6229
+ "`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."
5426
6230
  ],
5427
6231
  status: "shipped"
5428
6232
  },
@@ -5630,6 +6434,15 @@ var COMMANDS = [
5630
6434
  ],
5631
6435
  status: "shipped"
5632
6436
  },
6437
+ {
6438
+ name: "milestone status",
6439
+ description: "Print each of a Milestone's declared tranche intents with its derived lifecycle and issue counts",
6440
+ flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
6441
+ details: [
6442
+ "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."
6443
+ ],
6444
+ status: "shipped"
6445
+ },
5633
6446
  {
5634
6447
  name: "review status",
5635
6448
  description: "Print the review loop's own state for a PR, and its branch's distance from the base",
@@ -5926,10 +6739,10 @@ function createForgeSource(config) {
5926
6739
  };
5927
6740
  }
5928
6741
  // ../../packages/sources/src/select-source.ts
5929
- import { z as z3 } from "zod";
5930
- var StateSourceConfigSchema = z3.discriminatedUnion("kind", [
5931
- z3.object({ kind: z3.literal("forge"), owner: z3.string(), repo: z3.string() }),
5932
- z3.object({ kind: z3.literal("file"), root: z3.string().optional() })
6742
+ import { z as z5 } from "zod";
6743
+ var StateSourceConfigSchema = z5.discriminatedUnion("kind", [
6744
+ z5.object({ kind: z5.literal("forge"), owner: z5.string(), repo: z5.string() }),
6745
+ z5.object({ kind: z5.literal("file"), root: z5.string().optional() })
5933
6746
  ]);
5934
6747
  // src/checks/edge-resolve.ts
5935
6748
  import { execFileSync as execFileSync3 } from "node:child_process";
@@ -6049,6 +6862,589 @@ async function resolveEdge(id, taskById, factsByTaskId, repo, resolveSibling = d
6049
6862
  };
6050
6863
  }
6051
6864
 
6865
+ // src/lib/brief-assembly.ts
6866
+ function git(args) {
6867
+ try {
6868
+ return execFileSync4("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
6869
+ } catch {
6870
+ return "";
6871
+ }
6872
+ }
6873
+ function expandGlob(glob) {
6874
+ const out = git(["ls-files", "--", glob]);
6875
+ return out.split(`
6876
+ `).map((s) => s.trim()).filter(Boolean);
6877
+ }
6878
+ var DRAFT_ISSUE_SENTINEL = Number.MAX_SAFE_INTEGER;
6879
+
6880
+ // src/checks/registry.ts
6881
+ import { existsSync as existsSync3 } from "node:fs";
6882
+ import { join as join3 } from "node:path";
6883
+
6884
+ // src/lib/package-root.ts
6885
+ import { existsSync as existsSync2 } from "node:fs";
6886
+ import { dirname as dirname2, join as join2 } from "node:path";
6887
+ import { fileURLToPath } from "node:url";
6888
+ function packageRoot(moduleUrl) {
6889
+ let dir = dirname2(fileURLToPath(moduleUrl));
6890
+ while (!existsSync2(join2(dir, "package.json"))) {
6891
+ if (existsSync2(join2(dir, ".git")))
6892
+ break;
6893
+ const parent = dirname2(dir);
6894
+ if (parent === dir)
6895
+ break;
6896
+ dir = parent;
6897
+ }
6898
+ return dir;
6899
+ }
6900
+
6901
+ // src/checks/registry.ts
6902
+ var DIST_BIN_DIR = join3(packageRoot(import.meta.url), "dist", "checks", "bin");
6903
+ var SRC_BIN_DIR = join3(packageRoot(import.meta.url), "src", "checks", "bin");
6904
+ var BIN_DIR = existsSync3(DIST_BIN_DIR) ? DIST_BIN_DIR : SRC_BIN_DIR;
6905
+ var BIN_EXT = BIN_DIR === DIST_BIN_DIR ? ".js" : ".ts";
6906
+ function bin(name) {
6907
+ return join3(BIN_DIR, `${name}${BIN_EXT}`);
6908
+ }
6909
+ var REGISTRY = [
6910
+ [
6911
+ {
6912
+ name: "brief-shape",
6913
+ validates: "body",
6914
+ run: bin("check-brief-shape"),
6915
+ scope: "diff",
6916
+ timeoutMs: 15000,
6917
+ env: {
6918
+ PR_BODY: { optional: true },
6919
+ BRANCH: { optional: true },
6920
+ PR_NUMBER: { optional: true },
6921
+ GITHUB_TOKEN: { optional: true },
6922
+ GH_TOKEN: { optional: true }
6923
+ }
6924
+ },
6925
+ 0
6926
+ ],
6927
+ [
6928
+ {
6929
+ name: "pr-report-density",
6930
+ validates: "body",
6931
+ run: bin("check-pr-report-density"),
6932
+ scope: "diff",
6933
+ timeoutMs: 15000,
6934
+ env: {
6935
+ PR_BODY: { optional: true }
6936
+ }
6937
+ },
6938
+ 0
6939
+ ],
6940
+ [
6941
+ {
6942
+ name: "doc-coverage",
6943
+ validates: "body",
6944
+ run: bin("check-doc-coverage"),
6945
+ scope: "diff",
6946
+ timeoutMs: 15000,
6947
+ env: {
6948
+ BASE_SHA: { optional: true },
6949
+ PR_BODY: { optional: true },
6950
+ PR_BODY_FILE: { optional: true },
6951
+ PR_NUMBER: { optional: true },
6952
+ GITHUB_REPOSITORY: { optional: true },
6953
+ GITHUB_TOKEN: { optional: true },
6954
+ GH_TOKEN: { optional: true }
6955
+ }
6956
+ },
6957
+ 0
6958
+ ],
6959
+ [
6960
+ {
6961
+ name: "coherence",
6962
+ run: bin("check-coherence"),
6963
+ scope: "full",
6964
+ timeoutMs: 30000,
6965
+ env: {
6966
+ AEG_REPO: { optional: true },
6967
+ BRANCH: { optional: true },
6968
+ GITHUB_TOKEN: { optional: true },
6969
+ GH_TOKEN: { optional: true }
6970
+ }
6971
+ },
6972
+ 0
6973
+ ],
6974
+ [
6975
+ {
6976
+ name: "pr-premise-reassert",
6977
+ validates: "body",
6978
+ run: bin("check-pr-premise-reassert"),
6979
+ scope: "diff",
6980
+ timeoutMs: 15000,
6981
+ env: {
6982
+ PR_BODY: { optional: true }
6983
+ }
6984
+ },
6985
+ 0
6986
+ ],
6987
+ [
6988
+ {
6989
+ name: "dispatch-readiness",
6990
+ run: bin("check-dispatch-readiness"),
6991
+ scope: "full",
6992
+ timeoutMs: 30000,
6993
+ env: {
6994
+ AEG_REPO: { optional: true },
6995
+ BRANCH: { optional: true },
6996
+ GITHUB_TOKEN: { optional: true },
6997
+ GH_TOKEN: { optional: true },
6998
+ PREMISE_FILE: { optional: true }
6999
+ }
7000
+ },
7001
+ 0
7002
+ ],
7003
+ [
7004
+ {
7005
+ name: "closes-n",
7006
+ validates: "body",
7007
+ run: bin("check-closes-n"),
7008
+ scope: "diff",
7009
+ timeoutMs: 15000,
7010
+ requiresOpenPr: true,
7011
+ env: {
7012
+ BRANCH: { optional: true },
7013
+ PR_BODY: { optional: true },
7014
+ AEG_REPO: { optional: true },
7015
+ GITHUB_TOKEN: { optional: true },
7016
+ GH_TOKEN: { optional: true }
7017
+ }
7018
+ },
7019
+ 1
7020
+ ],
7021
+ [
7022
+ {
7023
+ name: "single-plan-pr",
7024
+ run: bin("check-single-plan-pr"),
7025
+ scope: "diff",
7026
+ timeoutMs: 15000,
7027
+ env: {
7028
+ BASE_SHA: { optional: true },
7029
+ PR_NUMBER: { optional: true },
7030
+ GITHUB_TOKEN: { optional: true },
7031
+ GH_TOKEN: { optional: true }
7032
+ }
7033
+ },
7034
+ 0
7035
+ ],
7036
+ [
7037
+ {
7038
+ name: "surface-scope",
7039
+ run: bin("check-surface-scope"),
7040
+ scope: "diff",
7041
+ timeoutMs: 15000,
7042
+ env: {
7043
+ AEG_REPO: { optional: true },
7044
+ BASE_SHA: { optional: true },
7045
+ BRANCH: { optional: true },
7046
+ GITHUB_TOKEN: { optional: true },
7047
+ GH_TOKEN: { optional: true }
7048
+ }
7049
+ },
7050
+ 0
7051
+ ],
7052
+ [
7053
+ {
7054
+ name: "test-plan",
7055
+ validates: "body",
7056
+ run: bin("check-test-plan"),
7057
+ scope: "diff",
7058
+ timeoutMs: 15000,
7059
+ requiresOpenPr: true,
7060
+ principalOwed: true,
7061
+ env: {
7062
+ PR_BODY: { optional: true },
7063
+ BRANCH: { optional: true }
7064
+ }
7065
+ },
7066
+ 1
7067
+ ],
7068
+ [
7069
+ {
7070
+ name: "body-bare-digits",
7071
+ validates: "body",
7072
+ run: bin("check-body-bare-digits"),
7073
+ scope: "diff",
7074
+ timeoutMs: 15000,
7075
+ requiresOpenPr: true,
7076
+ ownWorkflow: true,
7077
+ env: {
7078
+ PR_BODY: { optional: true },
7079
+ PR_NUMBER: { optional: true },
7080
+ GITHUB_REPOSITORY: { optional: true },
7081
+ GITHUB_TOKEN: { optional: true },
7082
+ GH_TOKEN: { optional: true }
7083
+ }
7084
+ },
7085
+ 1
7086
+ ],
7087
+ [
7088
+ {
7089
+ name: "token-report",
7090
+ validates: "body",
7091
+ run: bin("check-token-report"),
7092
+ scope: "diff",
7093
+ timeoutMs: 15000,
7094
+ requiresOpenPr: true,
7095
+ env: {
7096
+ PR_BODY: { optional: true },
7097
+ BRANCH: { optional: true },
7098
+ CLAUDE_PROJECT_DIR: { optional: true },
7099
+ CLAUDE_CODE_SESSION_ID: { optional: true }
7100
+ }
7101
+ },
7102
+ 1
7103
+ ],
7104
+ [
7105
+ {
7106
+ name: "no-disk-state",
7107
+ run: bin("check-no-disk-state"),
7108
+ scope: "diff",
7109
+ timeoutMs: 15000,
7110
+ env: { BASE_SHA: { optional: true } }
7111
+ },
7112
+ 0
7113
+ ],
7114
+ [
7115
+ {
7116
+ name: "registry-gates",
7117
+ run: bin("check-registry-gates"),
7118
+ scope: "full",
7119
+ timeoutMs: 30000,
7120
+ env: {
7121
+ AEG_REPO: { optional: true },
7122
+ GITHUB_TOKEN: { optional: true },
7123
+ GH_TOKEN: { optional: true }
7124
+ }
7125
+ },
7126
+ 0
7127
+ ],
7128
+ [
7129
+ {
7130
+ name: "review-gate",
7131
+ run: bin("check-review-gate"),
7132
+ scope: "full",
7133
+ timeoutMs: 30000,
7134
+ ownWorkflow: true,
7135
+ env: {
7136
+ PR_NUMBER: { optional: true },
7137
+ GITHUB_REPOSITORY: { optional: true },
7138
+ GITHUB_TOKEN: { optional: true },
7139
+ GH_TOKEN: { optional: true }
7140
+ }
7141
+ },
7142
+ 1
7143
+ ],
7144
+ [
7145
+ {
7146
+ name: "branch-topology",
7147
+ run: bin("check-branch-topology"),
7148
+ scope: "full",
7149
+ timeoutMs: 30000,
7150
+ env: {
7151
+ BRANCH: { optional: true },
7152
+ AEG_REPO: { optional: true },
7153
+ GITHUB_TOKEN: { optional: true },
7154
+ GH_TOKEN: { optional: true }
7155
+ }
7156
+ },
7157
+ 0
7158
+ ],
7159
+ [
7160
+ {
7161
+ name: "dead-branch-push",
7162
+ run: bin("check-dead-branch-push"),
7163
+ scope: "full",
7164
+ timeoutMs: 30000,
7165
+ env: {
7166
+ BRANCH: { optional: true },
7167
+ GITHUB_TOKEN: { optional: true },
7168
+ GH_TOKEN: { optional: true }
7169
+ }
7170
+ },
7171
+ 0
7172
+ ],
7173
+ [
7174
+ {
7175
+ name: "first-push-dispatch",
7176
+ run: bin("check-first-push-dispatch"),
7177
+ scope: "full",
7178
+ timeoutMs: 30000,
7179
+ env: {
7180
+ BRANCH: { optional: true },
7181
+ AEG_REPO: { optional: true },
7182
+ GITHUB_TOKEN: { optional: true },
7183
+ GH_TOKEN: { optional: true }
7184
+ }
7185
+ },
7186
+ 0
7187
+ ],
7188
+ [
7189
+ {
7190
+ name: "doc-coverage-push",
7191
+ validates: "body",
7192
+ run: bin("check-doc-coverage-push"),
7193
+ scope: "diff",
7194
+ timeoutMs: 15000,
7195
+ env: {
7196
+ OVERRIDE_DOCS: { optional: true },
7197
+ BASE_SHA: { optional: true },
7198
+ PR_BODY: { optional: true },
7199
+ PR_BODY_FILE: { optional: true },
7200
+ PR_LABELS: { optional: true },
7201
+ WAIVER_LABEL_ACTOR: { optional: true },
7202
+ GITHUB_REPOSITORY: { optional: true },
7203
+ GITHUB_TOKEN: { optional: true },
7204
+ GH_TOKEN: { optional: true }
7205
+ }
7206
+ },
7207
+ 0
7208
+ ],
7209
+ [
7210
+ {
7211
+ name: "issue-assignment",
7212
+ run: bin("check-issue-assignment"),
7213
+ scope: "full",
7214
+ timeoutMs: 30000,
7215
+ env: {
7216
+ AEG_REPO: { optional: true },
7217
+ BRANCH: { optional: true },
7218
+ GITHUB_TOKEN: { optional: true },
7219
+ GH_TOKEN: { optional: true }
7220
+ }
7221
+ },
7222
+ 0
7223
+ ],
7224
+ [
7225
+ {
7226
+ name: "evidence-fresh",
7227
+ validates: "body",
7228
+ run: bin("check-evidence-fresh"),
7229
+ scope: "diff",
7230
+ timeoutMs: 15000,
7231
+ requiresOpenPr: true,
7232
+ env: {
7233
+ PR_NUMBER: { optional: true },
7234
+ PR_BODY: { optional: true },
7235
+ BASE_SHA: { optional: true },
7236
+ GITHUB_TOKEN: { optional: true },
7237
+ GH_TOKEN: { optional: true }
7238
+ }
7239
+ },
7240
+ 1
7241
+ ],
7242
+ [
7243
+ {
7244
+ name: "reader-resolvable-prose",
7245
+ run: bin("check-reader-resolvable-prose"),
7246
+ scope: "full",
7247
+ timeoutMs: 30000,
7248
+ env: {},
7249
+ include: [
7250
+ "aeg-root/**/*.md",
7251
+ "apps/cli/src/**",
7252
+ ".github/workflows/**",
7253
+ ".vinaya/**",
7254
+ "apps/cli/README.md",
7255
+ "packages/sources/README.md"
7256
+ ]
7257
+ },
7258
+ 0
7259
+ ],
7260
+ [
7261
+ {
7262
+ name: "retired-vocabulary",
7263
+ run: bin("check-retired-vocabulary"),
7264
+ scope: "full",
7265
+ timeoutMs: 30000,
7266
+ env: {},
7267
+ include: ["aeg-root/**/*.md"]
7268
+ },
7269
+ 0
7270
+ ],
7271
+ [
7272
+ {
7273
+ name: "doctrine-portability",
7274
+ run: bin("check-doctrine-portability"),
7275
+ scope: "full",
7276
+ timeoutMs: 30000,
7277
+ env: { BASE_SHA: { optional: true } },
7278
+ include: ["aeg-root/**/*.md"]
7279
+ },
7280
+ 0
7281
+ ],
7282
+ [
7283
+ {
7284
+ name: "doctrine-no-procedures",
7285
+ run: bin("check-doctrine-no-procedures"),
7286
+ scope: "full",
7287
+ timeoutMs: 30000,
7288
+ env: {},
7289
+ include: ["aeg-root/**/*.md"]
7290
+ },
7291
+ 0
7292
+ ],
7293
+ [
7294
+ {
7295
+ name: "exec-bits",
7296
+ run: bin("check-exec-bits"),
7297
+ scope: "diff",
7298
+ timeoutMs: 30000,
7299
+ env: {}
7300
+ },
7301
+ 0
7302
+ ],
7303
+ [
7304
+ {
7305
+ name: "workspace-escape",
7306
+ run: bin("check-workspace-escape"),
7307
+ scope: "full",
7308
+ timeoutMs: 30000,
7309
+ env: {},
7310
+ include: ["aeg-root/**/*.md"]
7311
+ },
7312
+ 0
7313
+ ],
7314
+ [
7315
+ {
7316
+ name: "changeset-coverage",
7317
+ run: bin("check-changeset-coverage"),
7318
+ scope: "diff",
7319
+ timeoutMs: 15000,
7320
+ env: {}
7321
+ },
7322
+ 0
7323
+ ],
7324
+ [
7325
+ {
7326
+ name: "quoted-command",
7327
+ run: bin("check-quoted-command"),
7328
+ scope: "diff",
7329
+ timeoutMs: 15000,
7330
+ env: {}
7331
+ },
7332
+ 0
7333
+ ],
7334
+ [
7335
+ {
7336
+ name: "main-branch-refusal",
7337
+ run: bin("check-main-branch-refusal"),
7338
+ scope: "full",
7339
+ timeoutMs: 15000,
7340
+ env: { VINAYA_PUSH_REFS: { optional: true } }
7341
+ },
7342
+ 0
7343
+ ],
7344
+ [
7345
+ {
7346
+ name: "token-collection-wired",
7347
+ run: bin("check-token-collection-wired"),
7348
+ scope: "full",
7349
+ timeoutMs: 15000,
7350
+ env: {
7351
+ CLAUDE_PROJECT_DIR: { optional: true },
7352
+ CLAUDE_CODE_SESSION_ID: { optional: true }
7353
+ }
7354
+ },
7355
+ 0
7356
+ ],
7357
+ [
7358
+ {
7359
+ name: "issue-title-grammar",
7360
+ run: bin("check-issue-title-grammar"),
7361
+ validates: "issue",
7362
+ scope: "full",
7363
+ ownWorkflow: true,
7364
+ timeoutMs: 15000,
7365
+ env: { ISSUE_TITLE: { optional: true } }
7366
+ },
7367
+ 1
7368
+ ],
7369
+ [
7370
+ {
7371
+ name: "issue-objectives-numbering",
7372
+ run: bin("check-issue-objectives-numbering"),
7373
+ validates: "issue",
7374
+ scope: "full",
7375
+ ownWorkflow: true,
7376
+ timeoutMs: 15000,
7377
+ env: { ISSUE_BODY: { optional: true }, ISSUE_NUMBER: { optional: true } }
7378
+ },
7379
+ 1
7380
+ ],
7381
+ [
7382
+ {
7383
+ name: "issue-parts-coverage",
7384
+ run: bin("check-issue-parts-coverage"),
7385
+ validates: "issue",
7386
+ scope: "full",
7387
+ ownWorkflow: true,
7388
+ timeoutMs: 15000,
7389
+ env: { ISSUE_BODY: { optional: true } }
7390
+ },
7391
+ 1
7392
+ ],
7393
+ [
7394
+ {
7395
+ name: "issue-surface-globs",
7396
+ run: bin("check-issue-surface-globs"),
7397
+ validates: "issue",
7398
+ scope: "full",
7399
+ ownWorkflow: true,
7400
+ timeoutMs: 15000,
7401
+ env: { ISSUE_BODY: { optional: true } }
7402
+ },
7403
+ 1
7404
+ ],
7405
+ [
7406
+ {
7407
+ name: "issue-tranche-label",
7408
+ run: bin("check-issue-tranche-label"),
7409
+ validates: "issue",
7410
+ scope: "full",
7411
+ ownWorkflow: true,
7412
+ timeoutMs: 15000,
7413
+ env: { ISSUE_BODY: { optional: true }, ISSUE_LABELS: { optional: true } }
7414
+ },
7415
+ 1
7416
+ ],
7417
+ [
7418
+ {
7419
+ name: "issue-milestone-attach",
7420
+ run: bin("check-issue-milestone-attach"),
7421
+ validates: "issue",
7422
+ scope: "full",
7423
+ ownWorkflow: true,
7424
+ timeoutMs: 15000,
7425
+ env: {
7426
+ ISSUE_LABELS: { optional: true },
7427
+ CURRENT_MILESTONE_TITLE: { optional: true },
7428
+ RESOLVED_MILESTONE_TITLE: { optional: true }
7429
+ }
7430
+ },
7431
+ 1
7432
+ ]
7433
+ ];
7434
+ function coreCheckRegistry() {
7435
+ return REGISTRY.map(([spec]) => spec);
7436
+ }
7437
+ var CORE_CHECK_RING = Object.fromEntries(REGISTRY.map(([spec, ring]) => [spec.name, ring]));
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
  }
@@ -6171,15 +7567,15 @@ function findingsInThisDiff(findings) {
6171
7567
  // src/checks/bin/check-exec-bits.ts
6172
7568
  var CHECK_NAME = "exec-bits";
6173
7569
  var EXECUTABLE_MODE = "100755";
6174
- function git(args) {
7570
+ function git2(args) {
6175
7571
  try {
6176
- return execFileSync5("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
7572
+ return execFileSync6("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
6177
7573
  } catch {
6178
7574
  return null;
6179
7575
  }
6180
7576
  }
6181
7577
  function indexMode(path) {
6182
- const out = git(["ls-files", "-s", "--", path]);
7578
+ const out = git2(["ls-files", "-s", "--", path]);
6183
7579
  if (out === null || out === "")
6184
7580
  return null;
6185
7581
  const mode = out.split(/\s+/)[0] ?? "";