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