@attalabs/vinaya 0.24.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +26 -3
  2. package/aeg-root/contracts/architect-planner.md +5 -5
  3. package/aeg-root/contracts/developer-reviewer.md +10 -7
  4. package/aeg-root/contracts/planner-developer.md +142 -0
  5. package/aeg-root/contracts/reviewer-archivist.md +2 -2
  6. package/aeg-root/contracts/tranche-archivist-planner.md +12 -11
  7. package/aeg-root/enforcement.md +25 -11
  8. package/aeg-root/process.md +53 -75
  9. package/aeg-root/roles/archivist.md +3 -3
  10. package/aeg-root/roles/developer.md +30 -24
  11. package/aeg-root/roles/planner.md +100 -21
  12. package/aeg-root/roles/principal.md +14 -11
  13. package/aeg-root/roles/reviewer.md +33 -17
  14. package/aeg-root/roles/security.md +28 -13
  15. package/aeg-root/roles/tranche-archivist.md +2 -2
  16. package/aeg-root/skills/aeg/SKILL.md +5 -5
  17. package/aeg-root/skills/aeg-roles/SKILL.md +7 -7
  18. package/aeg-root/state-machine.md +36 -35
  19. package/aeg-root/task-model.md +3 -3
  20. package/aeg-root/templates/brief-template.md +22 -6
  21. package/aeg-root/templates/issue-rationale-template.md +35 -2
  22. package/aeg-root/templates/pr-report-template.md +5 -13
  23. package/aeg-root/tranche-model.md +21 -21
  24. package/dist/checks/bin/check-body-bare-digits.js +684 -142
  25. package/dist/checks/bin/check-branch-topology.js +741 -88
  26. package/dist/checks/bin/check-brief-shape.js +708 -83
  27. package/dist/checks/bin/check-changeset-coverage.js +1445 -149
  28. package/dist/checks/bin/check-closes-n.js +741 -88
  29. package/dist/checks/bin/check-coherence.js +808 -149
  30. package/dist/checks/bin/check-dead-branch-push.js +617 -81
  31. package/dist/checks/bin/check-dispatch-readiness.js +811 -152
  32. package/dist/checks/bin/check-doc-coverage-push.js +1445 -149
  33. package/dist/checks/bin/check-doc-coverage.js +1447 -151
  34. package/dist/checks/bin/check-doctrine-no-procedures.js +684 -142
  35. package/dist/checks/bin/check-doctrine-portability.js +1445 -149
  36. package/dist/checks/bin/check-evidence-fresh.js +684 -142
  37. package/dist/checks/bin/check-exec-bits.js +1445 -149
  38. package/dist/checks/bin/check-first-push-dispatch.js +808 -149
  39. package/dist/checks/bin/check-issue-assignment.js +741 -88
  40. package/dist/checks/bin/check-main-branch-refusal.js +618 -82
  41. package/dist/checks/bin/check-no-disk-state.js +617 -81
  42. package/dist/checks/bin/check-pr-premise-reassert.js +5401 -0
  43. package/dist/checks/bin/check-pr-report-density.js +617 -81
  44. package/dist/checks/bin/check-quoted-command.js +1443 -147
  45. package/dist/checks/bin/check-reader-resolvable-prose.js +1481 -153
  46. package/dist/checks/bin/check-registry-gates.js +659 -85
  47. package/dist/checks/bin/check-retired-vocabulary.js +1443 -147
  48. package/dist/checks/bin/check-review-gate.js +758 -145
  49. package/dist/checks/bin/check-single-plan-pr.js +617 -81
  50. package/dist/checks/bin/check-surface-scope.js +5722 -0
  51. package/dist/checks/bin/check-test-plan.js +617 -81
  52. package/dist/checks/bin/check-token-collection-wired.js +617 -81
  53. package/dist/checks/bin/check-token-report.js +629 -88
  54. package/dist/checks/bin/check-workspace-escape.js +1443 -147
  55. package/dist/index.js +8548 -4335
  56. package/package.json +1 -1
  57. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +2 -2
  58. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +26 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  61. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1pndh3_._.js → [root-of-the-server]__0p8q38b._.js} +1 -1
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  105. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  107. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  108. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0wxycau._.js +1 -1
  109. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  110. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  111. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_topbar_index_tsx_1h1gs1y._.js +1 -1
  112. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  113. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  114. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  115. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  116. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3m1kgax7j2vgs.js → 0bn8c8v5q429o.js} +1 -1
  117. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0doqwpd81sjzn.js → 0keji7wvbe1d1.js} +1 -1
  118. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1maoxbrl3pv5d.css +1 -0
  119. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1uogpj5w2n5ju.js → 3-6a3tinbdb-7.js} +1 -1
  120. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0zebsmmk1bvnb.js → 3_9xytdmiv1xi.js} +4 -4
  121. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  122. package/aeg-root/contracts/brief-developer.md +0 -141
  123. package/aeg-root/contracts/planner-brief.md +0 -130
  124. package/aeg-root/roles/brief-author.md +0 -114
  125. package/aeg-root/skills/brief-authoring/SKILL.md +0 -509
  126. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/3qc67qhcvbg0o.css +0 -1
  127. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  128. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_buildManifest.js +0 -0
  129. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_clientMiddlewareManifest.js +0 -0
  130. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_ssgManifest.js +0 -0
@@ -28,6 +28,15 @@ var GATE_AUDIENCE = {
28
28
  internal: "A composite that shells this repo's own `typecheck`/`lint`/`test`/`build` scripts by name. Those names are this monorepo's toolchain (bun + turbo + biome), not an adopter's; the shipped equivalent of its intent is `vinaya check --all`, which is portable by construction."
29
29
  }
30
30
  };
31
+ var NON_GATE_BINS = [
32
+ "archive-task",
33
+ "assign-task-issue",
34
+ "dead-branch-audit",
35
+ "eval-agent-compliance",
36
+ "open-issue",
37
+ "open-pr",
38
+ "report-tokens"
39
+ ];
31
40
  function isShipped(a) {
32
41
  return "shippedAs" in a;
33
42
  }
@@ -388,7 +397,7 @@ var LABELS = [
388
397
  id: "vinaya/needs:brief-correction",
389
398
  category: "needs",
390
399
  form: "literal",
391
- carries: "Waiting on the Brief Author — the brief contradicts the surface it describes."
400
+ carries: "Waiting on the Planner — the brief contradicts the surface it describes."
392
401
  },
393
402
  {
394
403
  key: "waiver-docs",
@@ -1939,13 +1948,18 @@ function parseInlineFieldList(section) {
1939
1948
  }
1940
1949
  // ../../packages/aeg-core/src/verdict-extraction.ts
1941
1950
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1942
- function firstThreeLines(comment) {
1951
+ var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1952
+ function firstFiveLines(comment) {
1943
1953
  return comment.split(`
1944
- `).slice(0, 3).join(`
1954
+ `).slice(0, 5).join(`
1945
1955
  `);
1946
1956
  }
1947
1957
  function extractHeadSha(comment) {
1948
- const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
1958
+ const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
1959
+ return m ? m[1].toLowerCase() : null;
1960
+ }
1961
+ function extractObjectivesVersion(comment) {
1962
+ const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
1949
1963
  return m ? m[1].toLowerCase() : null;
1950
1964
  }
1951
1965
  function extractVerdict(comments, valuePattern, missingLabel) {
@@ -1954,21 +1968,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1954
1968
  return {
1955
1969
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1956
1970
  headSha: null,
1971
+ objectivesVersion: null,
1957
1972
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1958
1973
  };
1959
1974
  }
1960
1975
  const latest = candidates[candidates.length - 1];
1961
- const m = firstThreeLines(latest).match(valuePattern);
1976
+ const m = firstFiveLines(latest).match(valuePattern);
1962
1977
  if (!m) {
1963
1978
  return {
1964
- value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
1979
+ value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
1965
1980
  headSha: null,
1966
- danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
1981
+ objectivesVersion: null,
1982
+ danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
1967
1983
  };
1968
1984
  }
1969
1985
  return {
1970
1986
  value: m[1].toUpperCase().replace(/[_-]/g, " "),
1971
1987
  headSha: extractHeadSha(latest),
1988
+ objectivesVersion: extractObjectivesVersion(latest),
1972
1989
  danglingNote: null
1973
1990
  };
1974
1991
  }
@@ -2325,8 +2342,105 @@ function overrideActive(opts) {
2325
2342
  return true;
2326
2343
  return false;
2327
2344
  }
2328
- // ../../packages/aeg-core/src/premise-check.ts
2345
+ // ../../packages/aeg-core/src/objectives.ts
2329
2346
  import { createHash as createHash2 } from "node:crypto";
2347
+ var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
2348
+ var NEXT_HEADING_RE = /^##[ \t]/m;
2349
+ var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
2350
+ function hasBacktickedPath(text) {
2351
+ let i = 0;
2352
+ while (i < text.length) {
2353
+ const start = text.indexOf("`", i);
2354
+ if (start === -1)
2355
+ return false;
2356
+ const end = text.indexOf("`", start + 1);
2357
+ if (end === -1)
2358
+ return false;
2359
+ if (text.slice(start + 1, end).includes("/"))
2360
+ return true;
2361
+ i = end + 1;
2362
+ }
2363
+ return false;
2364
+ }
2365
+ var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
2366
+ function stripObjectiveBackticks(text) {
2367
+ return text.replace(/`[^`\n]*`/g, " ");
2368
+ }
2369
+ function wordCount(text) {
2370
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
2371
+ }
2372
+ function objectivesSectionText(body) {
2373
+ const heading = HEADING_RE.exec(body);
2374
+ if (!heading)
2375
+ return null;
2376
+ const afterHeading = body.slice(heading.index + heading[0].length);
2377
+ const next = NEXT_HEADING_RE.exec(afterHeading);
2378
+ return next ? afterHeading.slice(0, next.index) : afterHeading;
2379
+ }
2380
+ function hasObjectivesHeading(body) {
2381
+ return HEADING_RE.test(body);
2382
+ }
2383
+ function objectivesOf(body) {
2384
+ const section = objectivesSectionText(body);
2385
+ if (section === null) {
2386
+ return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
2387
+ }
2388
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
2389
+ if (lines.length === 0) {
2390
+ return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
2391
+ }
2392
+ const objectives = [];
2393
+ const errors = [];
2394
+ for (const line of lines) {
2395
+ const m = OBJECTIVE_LINE_RE.exec(line);
2396
+ if (!m) {
2397
+ errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
2398
+ continue;
2399
+ }
2400
+ const n = m[1];
2401
+ const text = m[2].trim();
2402
+ if (text.length === 0) {
2403
+ errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
2404
+ continue;
2405
+ }
2406
+ if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
2407
+ errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Planner maps it to files).`);
2408
+ continue;
2409
+ }
2410
+ objectives.push({ id: `O${n}`, text });
2411
+ }
2412
+ for (let i = 0;i < objectives.length; i++) {
2413
+ const expected = i + 1;
2414
+ const actual = Number.parseInt(objectives[i].id.slice(1), 10);
2415
+ if (actual !== expected) {
2416
+ errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
2417
+ break;
2418
+ }
2419
+ }
2420
+ if (errors.length > 0)
2421
+ return { ok: false, errors };
2422
+ return { ok: true, objectives };
2423
+ }
2424
+ function normalizeText(text) {
2425
+ return text.trim().replace(/\s+/g, " ");
2426
+ }
2427
+ function objectivesVersion(objectives) {
2428
+ const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
2429
+ `);
2430
+ return createHash2("sha256").update(normalized).digest("hex");
2431
+ }
2432
+ function isIssueNotFoundError(err) {
2433
+ const stderr = err?.stderr;
2434
+ const haystack = [
2435
+ err?.message ?? "",
2436
+ typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
2437
+ ].join(`
2438
+ `);
2439
+ return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
2440
+ }
2441
+
2442
+ // ../../packages/aeg-core/src/premise-check.ts
2443
+ import { createHash as createHash3 } from "node:crypto";
2330
2444
  var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
2331
2445
  function isPremiseHeader(line) {
2332
2446
  const stripped = line.replace(/[*#]/g, "").trim();
@@ -2397,7 +2511,7 @@ function checkPremises(assertions, fileReader) {
2397
2511
  } else if (a.kind === "absent" && content.includes(a.value)) {
2398
2512
  failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
2399
2513
  } else if (a.kind === "sha256") {
2400
- const actual = createHash2("sha256").update(content).digest("hex");
2514
+ const actual = createHash3("sha256").update(content).digest("hex");
2401
2515
  if (actual !== a.value.toLowerCase()) {
2402
2516
  failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
2403
2517
  }
@@ -2502,18 +2616,14 @@ function checkTestPlanExclusivity(prBody) {
2502
2616
  function checkPrincipalPlaceholder(prBody) {
2503
2617
  const region = testPlanRegion(prBody);
2504
2618
  const lineRe = /^-\s*\[[ xX]\]\s*\*{2}\[principal\]\*{2}(.*)$/gim;
2619
+ const errors = [];
2505
2620
  for (const m of region.matchAll(lineRe)) {
2506
2621
  const content = m[1] ?? "";
2507
2622
  if (/^\s*None\b/i.test(content)) {
2508
- return {
2509
- status: "fail",
2510
- errors: [
2511
- 'brief-validation Test Plan shape: a `**[principal]**` checkbox item is a "None" placeholder — if there is no principal-runnable surface, omit the item entirely; an untickable placeholder box blocks the merge gate forever.'
2512
- ]
2513
- };
2623
+ errors.push(`brief-validation Test Plan shape: a \`**[principal]**\` checkbox item is a "None" placeholder ("${content.trim()}") — if there is no principal-runnable surface, omit the item entirely; an untickable placeholder box blocks the merge gate forever.`);
2514
2624
  }
2515
2625
  }
2516
- return { status: "pass", errors: [] };
2626
+ return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
2517
2627
  }
2518
2628
  function checkSurfaceMap(prBody) {
2519
2629
  return headingCheck(prBody, "(?:technical\\s+)?surface map", "Technical surface map");
@@ -2758,8 +2868,55 @@ function checkDefeatCases(prBody) {
2758
2868
  ]
2759
2869
  };
2760
2870
  }
2871
+ var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
2872
+ var OBJECTIVE_REF_RE = /O(\d+)/g;
2873
+ function citedObjectiveIds(section6) {
2874
+ const ids = new Set;
2875
+ for (const m of section6.matchAll(PART_CITATION_RE)) {
2876
+ for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
2877
+ ids.add(Number.parseInt(r[1], 10));
2878
+ }
2879
+ return ids;
2880
+ }
2881
+ function checkObjectivesCopy(prBody, issueObjectives) {
2882
+ const parsed = objectivesOf(prBody);
2883
+ if (!parsed.ok) {
2884
+ return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
2885
+ }
2886
+ if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
2887
+ return {
2888
+ status: "fail",
2889
+ errors: [
2890
+ "brief-validation objectives copy: the brief's `## Objectives` section does not match the Issue's — copy the Issue's `## Objectives` section (aeg-root/roles/developer.md)."
2891
+ ]
2892
+ };
2893
+ }
2894
+ return { status: "pass", errors: [] };
2895
+ }
2896
+ function checkObjectivesCoverage(prBody) {
2897
+ const parsed = objectivesOf(prBody);
2898
+ if (!parsed.ok) {
2899
+ return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
2900
+ }
2901
+ const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
2902
+ const section6 = extractNumberedSection(prBody, 6) ?? "";
2903
+ const cited = citedObjectiveIds(section6);
2904
+ const maxDeclared = Math.max(...declaredIds, 0);
2905
+ const errors = [];
2906
+ for (const id of declaredIds) {
2907
+ if (!cited.has(id)) {
2908
+ errors.push(`brief-validation objectives coverage: O${id} is not cited by any Part in §6 — every objective must be cited by at least one Part.`);
2909
+ }
2910
+ }
2911
+ for (const id of cited) {
2912
+ if (!declaredIds.has(id)) {
2913
+ errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
2914
+ }
2915
+ }
2916
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2917
+ }
2761
2918
  function checkBriefSections(prBody, readTier, options = {}) {
2762
- const { requireClosesN = true, consumersOf = () => [] } = options;
2919
+ const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
2763
2920
  const results = [
2764
2921
  checkTierField(prBody, readTier),
2765
2922
  checkTestPlan(prBody),
@@ -2777,10 +2934,23 @@ function checkBriefSections(prBody, readTier, options = {}) {
2777
2934
  checkCommandsCarryOutput(prBody),
2778
2935
  checkConsumerTests(prBody, consumersOf),
2779
2936
  checkDefeatCases(prBody),
2937
+ ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2780
2938
  ...requireClosesN ? [checkClosesN(prBody)] : []
2781
2939
  ];
2782
2940
  return { errors: results.flatMap((r) => r.errors) };
2783
2941
  }
2942
+ var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
2943
+ function contentAfterTwoLines(body) {
2944
+ const first = body.indexOf(`
2945
+ `);
2946
+ if (first === -1)
2947
+ return "";
2948
+ const second = body.indexOf(`
2949
+ `, first + 1);
2950
+ if (second === -1)
2951
+ return "";
2952
+ return body.slice(second + 1);
2953
+ }
2784
2954
  // ../../packages/aeg-core/src/doctrine-portability.ts
2785
2955
  var DEFAULT_SHIPS_PREFIX = "aeg-root/";
2786
2956
  var STATIC_PORTABLE_PREFIXES = [
@@ -2975,14 +3145,14 @@ function checkSectionDensity(prBody, heading) {
2975
3145
  ]
2976
3146
  };
2977
3147
  }
2978
- function checkSummaryDensity(prBody) {
2979
- return checkSectionDensity(prBody, "Summary");
3148
+ function checkDecisionsDensity(prBody) {
3149
+ return checkSectionDensity(prBody, "Decisions");
2980
3150
  }
2981
3151
  function checkScopeDensity(prBody) {
2982
3152
  return checkSectionDensity(prBody, "Scope");
2983
3153
  }
2984
3154
  function checkPrReportDensity(prBody) {
2985
- const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
3155
+ const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
2986
3156
  return { errors: results.flatMap((r) => r.errors) };
2987
3157
  }
2988
3158
  // ../../packages/aeg-core/src/issue-validation.ts
@@ -3014,6 +3184,75 @@ function checkIssueRationale(body) {
3014
3184
  }
3015
3185
  return { status: errors.length > 0 ? "fail" : "pass", errors };
3016
3186
  }
3187
+ var OBJECTIVES_SINCE_ISSUE = 404;
3188
+ function checkIssueObjectives(body, issueNumber) {
3189
+ if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
3190
+ return { status: "pass", errors: [] };
3191
+ const result = objectivesOf(body);
3192
+ if (result.ok)
3193
+ return { status: "pass", errors: [] };
3194
+ return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
3195
+ }
3196
+ function topLevelSectionText(body, headingName) {
3197
+ const headingRe = new RegExp(`^##[ \\t]*${headingName}[ \\t]*$`, "im");
3198
+ const heading = headingRe.exec(body);
3199
+ if (!heading)
3200
+ return null;
3201
+ const afterHeading = body.slice(heading.index + heading[0].length);
3202
+ const next = /^##[ \t]/m.exec(afterHeading);
3203
+ return next ? afterHeading.slice(0, next.index) : afterHeading;
3204
+ }
3205
+ function looksLikeFilePath(entry2) {
3206
+ const stripped = entry2.replace(/\/\*\*?$/, "");
3207
+ const lastSegment = stripped.split("/").pop() ?? stripped;
3208
+ const extMatch = /\.[A-Za-z0-9]{1,6}$/.exec(lastSegment);
3209
+ return extMatch !== null && extMatch.index > 0;
3210
+ }
3211
+ function splitGlobList(raw) {
3212
+ return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
3213
+ }
3214
+ function parseIssueSurface(body) {
3215
+ const section = topLevelSectionText(body, "Surface");
3216
+ if (section === null)
3217
+ return { ok: false, errors: ["no `## Surface` heading found in the body."] };
3218
+ const inLine = /^in:\s*(.+)$/im.exec(section);
3219
+ const outLine = /^out:\s*(.+)$/im.exec(section);
3220
+ const errors = [];
3221
+ if (!inLine)
3222
+ errors.push("`## Surface` has no `in:` line.");
3223
+ if (!outLine)
3224
+ errors.push("`## Surface` has no `out:` line.");
3225
+ if (errors.length > 0)
3226
+ return { ok: false, errors };
3227
+ const inGlobs = splitGlobList(inLine[1]);
3228
+ const outGlobs = splitGlobList(outLine[1]);
3229
+ for (const g of [...inGlobs, ...outGlobs]) {
3230
+ if (looksLikeFilePath(g)) {
3231
+ errors.push(`\`${g}\` in \`## Surface\` looks like a file path — Surface entries are directory-level globs, never file paths.`);
3232
+ }
3233
+ }
3234
+ if (inGlobs.length === 0)
3235
+ errors.push("`## Surface`'s `in:` line resolved to zero globs.");
3236
+ if (errors.length > 0)
3237
+ return { ok: false, errors };
3238
+ return { ok: true, value: { in: inGlobs, out: outGlobs } };
3239
+ }
3240
+ function globCoversPath(glob, path) {
3241
+ const g = glob.replace(/\/\*\*?$/, "").replace(/\/+$/, "");
3242
+ const p = path.replace(/\/+$/, "");
3243
+ return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
3244
+ }
3245
+ function checkSurfaceScope(changedFiles, outGlobs) {
3246
+ if (outGlobs.length === 0)
3247
+ return { ok: true };
3248
+ const violations = [];
3249
+ for (const file of changedFiles) {
3250
+ const glob = outGlobs.find((g) => globCoversPath(g, file));
3251
+ if (glob)
3252
+ violations.push({ file, glob });
3253
+ }
3254
+ return violations.length > 0 ? { ok: false, violations } : { ok: true };
3255
+ }
3017
3256
  function isTaskIssueLabelSet(labels) {
3018
3257
  return hasLabel("tranche", labels);
3019
3258
  }
@@ -3078,6 +3317,8 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
3078
3317
  }
3079
3318
  return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
3080
3319
  }
3320
+ var DOC_PATH_RE = /(?:(?:aeg-root|apps|packages|specs|docs|tools|\.claude|\.github)\/[\w./@-]*\.(?:md|mdx)|\.claude\/(?:skills|rules)\/[\w./-]+|\b(?:docs-index|decisions|projects|state-machine|enforcement|process|README|CLAUDE)\.md\b|\b[\w-]+-(?:spec|decisions|backlog)\.md\b)/i;
3321
+ var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
3081
3322
 
3082
3323
  // ../../packages/aeg-core/src/coherence-checks.ts
3083
3324
  var COHERENCE_ENFORCED_FROM = "2026-07-01";
@@ -3275,7 +3516,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3275
3516
  continue;
3276
3517
  const errors = [
3277
3518
  ...checkIssueRationale(issue.body).errors,
3278
- ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
3519
+ ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3520
+ ...checkIssueObjectives(issue.body, issue.number).errors
3279
3521
  ];
3280
3522
  if (errors.length === 0)
3281
3523
  continue;
@@ -3390,6 +3632,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3390
3632
  }
3391
3633
  return { ok: true, expectedIssue };
3392
3634
  }
3635
+ // ../../packages/aeg-core/src/archive-task.ts
3636
+ function closesRefs(text) {
3637
+ return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
3638
+ }
3639
+ function extractIssue(body) {
3640
+ const stripped = stripCode(body);
3641
+ const bodyNums = closesRefs(stripped);
3642
+ const anchored = anchoredRegion(stripped, "CLOSES");
3643
+ if (anchored !== null) {
3644
+ const anchorNums = closesRefs(anchored);
3645
+ if (anchorNums.length === 0)
3646
+ return { issue: null, extraIssues: bodyNums, outsideHeader: false };
3647
+ const issue2 = anchorNums[0];
3648
+ return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
3649
+ }
3650
+ const headerNums = closesRefs(headerRegion(stripped));
3651
+ if (bodyNums.length === 0)
3652
+ return { issue: null, extraIssues: [], outsideHeader: false };
3653
+ const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3654
+ return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3655
+ }
3393
3656
  // ../../packages/aeg-core/src/review-gate.ts
3394
3657
  function isBoundToHead(extraction, headSha) {
3395
3658
  if (!extraction.headSha)
@@ -3408,6 +3671,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3408
3671
  function isBoundToPatch(extraction, headSha, patchIdOf) {
3409
3672
  return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3410
3673
  }
3674
+ function isBoundToObjectives(extraction, currentVersion) {
3675
+ if (currentVersion === null)
3676
+ return true;
3677
+ return extraction.objectivesVersion === currentVersion;
3678
+ }
3411
3679
  function checkReviewGate(input) {
3412
3680
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3413
3681
  const waived = isWaiverLabelActorVerified({
@@ -3434,7 +3702,9 @@ function checkReviewGate(input) {
3434
3702
  const securityClean = security.value === "PASS";
3435
3703
  const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3436
3704
  const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3437
- if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
3705
+ const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3706
+ const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3707
+ if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
3438
3708
  return {
3439
3709
  verdict: "pass",
3440
3710
  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.`,
@@ -3446,11 +3716,15 @@ function checkReviewGate(input) {
3446
3716
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
3447
3717
  } else if (!codeReviewBound) {
3448
3718
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3719
+ } else if (!codeReviewObjectivesBound) {
3720
+ problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3449
3721
  }
3450
3722
  if (!securityClean) {
3451
3723
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
3452
3724
  } else if (!securityBound) {
3453
3725
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3726
+ } else if (!securityObjectivesBound) {
3727
+ problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3454
3728
  }
3455
3729
  if (!mechanicalChecksClean) {
3456
3730
  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(", ")}`);
@@ -3574,16 +3848,33 @@ function checkG1(rows, existsFn) {
3574
3848
  return { check: "G1", status: findings.length > 0 ? "fail" : "pass", findings };
3575
3849
  }
3576
3850
  var SCAFFOLD_PLACEHOLDER = "[undocumented — fill in why]";
3851
+ var NON_GATE_BIN_NAMES = NON_GATE_BINS;
3852
+ var TWIN_CANDIDATE_EXCEPTIONS = {
3853
+ "apps/cli/src/checks/bin/check-dead-branch-push.ts": 'Ring 0 "Pushing to a branch whose pull request already resolved" (implementation: check-push-target.ts)',
3854
+ "apps/cli/src/checks/bin/check-issue-assignment.ts": `Ring 0 "A task branch's first push (Issue self-assignment)" (implementation: assign-task-issue.ts)`,
3855
+ "apps/cli/src/checks/bin/check-closes-n.ts": 'Ring 1 "Closes linkage" (implementation: verify-coherence.ts)'
3856
+ };
3857
+ var NON_GATE_HOOK_SCRIPTS = [".claude/hooks/track-transcript.sh"];
3577
3858
  function checkG2(rows, candidateFiles) {
3578
3859
  const implementations = new Set(rows.map((r) => r.implementation).filter((p) => p !== ""));
3860
+ const claimedNames = new Set(rows.flatMap((r) => claimedCheckNames(r.implementation)));
3579
3861
  const findings = [];
3580
3862
  for (const path of candidateFiles) {
3581
- if (!implementations.has(path)) {
3582
- findings.push({
3583
- path,
3584
- reason: `"${path}" is not named as the implementation of any row in enforcement.md's ring tables`
3585
- });
3586
- }
3863
+ if (implementations.has(path))
3864
+ continue;
3865
+ if (path.startsWith(AEG_CORE_BIN_PATH_PREFIX) && NON_GATE_BIN_NAMES.includes(basenameNoExt(path)))
3866
+ continue;
3867
+ if (NON_GATE_HOOK_SCRIPTS.includes(path))
3868
+ continue;
3869
+ if (path in TWIN_CANDIDATE_EXCEPTIONS)
3870
+ continue;
3871
+ const candidateNames = claimedCheckNames(path);
3872
+ if (candidateNames.length > 0 && candidateNames.some((n) => claimedNames.has(n)))
3873
+ continue;
3874
+ findings.push({
3875
+ path,
3876
+ reason: `"${path}" is not named as the implementation of any row in enforcement.md's ring tables`
3877
+ });
3587
3878
  }
3588
3879
  for (const row of rows) {
3589
3880
  const carriesPlaceholder = [row.summary, row.description, row.spec].some((cell) => cell === SCAFFOLD_PLACEHOLDER);
@@ -3595,7 +3886,7 @@ function checkG2(rows, candidateFiles) {
3595
3886
  });
3596
3887
  }
3597
3888
  }
3598
- return { check: "G2", status: findings.length > 0 ? "info" : "pass", findings };
3889
+ return { check: "G2", status: findings.length > 0 ? "fail" : "pass", findings };
3599
3890
  }
3600
3891
  function checkG3(ring0Rows, crossingFiles) {
3601
3892
  const ring0Implementations = new Set(ring0Rows.map((r) => r.implementation));
@@ -3686,55 +3977,20 @@ function checkG6(rows, registeredCheckNames) {
3686
3977
  }
3687
3978
  return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
3688
3979
  }
3689
- // ../../packages/aeg-core/src/dead-branch-push-guard.ts
3690
- function checkDeadBranchPush(input) {
3691
- const { branch, prState, prNumber } = input;
3692
- if (prState === "MERGED") {
3693
- return {
3694
- verdict: "refuse",
3695
- reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
3696
- };
3697
- }
3698
- if (prState === "CLOSED") {
3699
- return {
3700
- verdict: "refuse",
3701
- reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
3702
- };
3703
- }
3704
- if (prState === "OPEN") {
3705
- return {
3706
- verdict: "allow",
3707
- reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
3708
- };
3709
- }
3710
- if (prState === "NONE") {
3711
- return {
3712
- verdict: "allow",
3713
- reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
3714
- };
3715
- }
3716
- return {
3717
- verdict: "allow",
3718
- reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
3719
- };
3720
- }
3721
- // ../../packages/aeg-core/src/baseline-capture.ts
3722
- function captureBaseline(counts, capturedAt) {
3723
- return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
3980
+ // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3981
+ var PRODUCT_SLUG_SCOPE = [
3982
+ "apps/cli/src",
3983
+ ".github/workflows",
3984
+ ".vinaya",
3985
+ "apps/cli/README.md",
3986
+ "packages/sources/README.md"
3987
+ ];
3988
+ function isUnderOrEqual(path, prefix) {
3989
+ return path === prefix || path.startsWith(`${prefix}/`);
3724
3990
  }
3725
- function compareToBaseline(current, baseline) {
3726
- const perTool = current.map((c) => {
3727
- const baselineEntry = baseline.find((b) => b.tool === c.tool);
3728
- const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
3729
- return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
3730
- });
3731
- return {
3732
- withinBudget: perTool.every((t) => t.delta <= 0),
3733
- delta: perTool.reduce((sum, t) => sum + t.delta, 0),
3734
- perTool
3735
- };
3991
+ function isProductScopeFile(path) {
3992
+ return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
3736
3993
  }
3737
- // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3738
3994
  var SHIPS_PREFIX = "aeg-root/";
3739
3995
  function shipsArchivePrefix(shipsPrefix) {
3740
3996
  return `${shipsPrefix}tranches/completed/`;
@@ -3754,6 +4010,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
3754
4010
  }
3755
4011
  if (isSpecFile2(path) || isClaudeMdFile(path))
3756
4012
  return "internal";
4013
+ if (isProductScopeFile(path))
4014
+ return "product";
3757
4015
  return null;
3758
4016
  }
3759
4017
  var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
@@ -3766,6 +4024,9 @@ function stripNonProse(path, content) {
3766
4024
  }
3767
4025
  return content;
3768
4026
  }
4027
+ function stripNonProseForProduct(path, content) {
4028
+ return path.endsWith(".md") ? stripNonProse(path, content) : content;
4029
+ }
3769
4030
  function lineAt(content, index) {
3770
4031
  let line = 1;
3771
4032
  for (let i = 0;i < index; i++) {
@@ -3790,12 +4051,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
3790
4051
  { pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
3791
4052
  ...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
3792
4053
  ];
4054
+ const productPatterns = [
4055
+ { pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
4056
+ ];
3793
4057
  for (const file of files) {
3794
4058
  const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
3795
- if (!cls || !SWEPT_CLASSES.has(cls))
4059
+ if (!cls || cls === "internal")
3796
4060
  continue;
3797
- const scrubbed = stripNonProse(file.path, file.content);
3798
- for (const { pattern, what, group } of patterns) {
4061
+ if (cls !== "product" && !SWEPT_CLASSES.has(cls))
4062
+ continue;
4063
+ const blocking = cls === "product";
4064
+ const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
4065
+ const patternsToRun = blocking ? productPatterns : patterns;
4066
+ for (const { pattern, what, group } of patternsToRun) {
3799
4067
  pattern.lastIndex = 0;
3800
4068
  let match = pattern.exec(scrubbed);
3801
4069
  while (match !== null) {
@@ -3803,7 +4071,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
3803
4071
  findings.push({
3804
4072
  file: file.path,
3805
4073
  line: lineAt(scrubbed, match.index),
3806
- message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`
4074
+ message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
4075
+ blocking
3807
4076
  });
3808
4077
  match = pattern.exec(scrubbed);
3809
4078
  }
@@ -3853,7 +4122,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
3853
4122
  findings.push({
3854
4123
  file: file.path,
3855
4124
  line,
3856
- message: `uses coined term "${term}" without defining it inline or linking the glossary`
4125
+ message: `uses coined term "${term}" without defining it inline or linking the glossary`,
4126
+ blocking: false
3857
4127
  });
3858
4128
  }
3859
4129
  }
@@ -3876,6 +4146,7 @@ function parseGlossaryTerms(glossaryContent) {
3876
4146
  }
3877
4147
  return terms;
3878
4148
  }
4149
+
3879
4150
  // ../../packages/aeg-core/src/quoted-command.ts
3880
4151
  var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
3881
4152
  var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
@@ -3970,6 +4241,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
3970
4241
  }
3971
4242
  return findings;
3972
4243
  }
4244
+ // ../../packages/aeg-core/src/dead-branch-push-guard.ts
4245
+ function checkDeadBranchPush(input) {
4246
+ const { branch, prState, prNumber } = input;
4247
+ if (prState === "MERGED") {
4248
+ return {
4249
+ verdict: "refuse",
4250
+ reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
4251
+ };
4252
+ }
4253
+ if (prState === "CLOSED") {
4254
+ return {
4255
+ verdict: "refuse",
4256
+ reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
4257
+ };
4258
+ }
4259
+ if (prState === "OPEN") {
4260
+ return {
4261
+ verdict: "allow",
4262
+ reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
4263
+ };
4264
+ }
4265
+ if (prState === "NONE") {
4266
+ return {
4267
+ verdict: "allow",
4268
+ reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
4269
+ };
4270
+ }
4271
+ return {
4272
+ verdict: "allow",
4273
+ reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
4274
+ };
4275
+ }
4276
+ // ../../packages/aeg-core/src/baseline-capture.ts
4277
+ function captureBaseline(counts, capturedAt) {
4278
+ return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
4279
+ }
4280
+ function compareToBaseline(current, baseline) {
4281
+ const perTool = current.map((c) => {
4282
+ const baselineEntry = baseline.find((b) => b.tool === c.tool);
4283
+ const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
4284
+ return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
4285
+ });
4286
+ return {
4287
+ withinBudget: perTool.every((t) => t.delta <= 0),
4288
+ delta: perTool.reduce((sum, t) => sum + t.delta, 0),
4289
+ perTool
4290
+ };
4291
+ }
3973
4292
  // ../../packages/aeg-core/src/retired-vocabulary.ts
3974
4293
  var RETIRED_PATTERNS = [
3975
4294
  String.raw`\bD-[0-9]{3}\b`,
@@ -4439,6 +4758,223 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4439
4758
  }
4440
4759
  return findings;
4441
4760
  }
4761
+ // ../../packages/aeg-core/src/log/schema.ts
4762
+ import { z } from "zod";
4763
+ var ROLE_VALUES = [
4764
+ "planner",
4765
+ "developer",
4766
+ "code-reviewer",
4767
+ "security",
4768
+ "principal",
4769
+ "archivist",
4770
+ "architect"
4771
+ ];
4772
+ var RoleSchema = z.enum(ROLE_VALUES);
4773
+ var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4774
+ var HostSchema = z.enum(HOST_VALUES);
4775
+ var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4776
+ var HeaderMetaSchema = z.object({
4777
+ schema: z.literal(1),
4778
+ ts: z.string(),
4779
+ run_id: z.string().regex(RUN_ID_PATTERN),
4780
+ seq: z.number().int().nonnegative(),
4781
+ repo: z.string().nullable(),
4782
+ vinaya: z.string(),
4783
+ doctrine: z.string(),
4784
+ host: HostSchema,
4785
+ machine: z.string()
4786
+ }).strict();
4787
+ var SubjectSchema = z.object({
4788
+ issue: z.number().int().nullable(),
4789
+ pr: z.number().int().optional(),
4790
+ sha: z.string().optional(),
4791
+ role: z.union([RoleSchema, z.literal("unattributed")]),
4792
+ round: z.number().int().optional(),
4793
+ objectives_version: z.string().optional()
4794
+ }).strict();
4795
+ var HeaderSchema = z.object({
4796
+ meta: HeaderMetaSchema,
4797
+ subject: SubjectSchema
4798
+ }).strict();
4799
+ var envelopeTail = {
4800
+ duration_ms: z.number().nonnegative().optional(),
4801
+ payload: z.object({}).strict()
4802
+ };
4803
+ var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4804
+ z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4805
+ z.object({
4806
+ type: z.literal("round_pushed"),
4807
+ pr: z.number().int(),
4808
+ head: z.string(),
4809
+ comment_id: z.number().int()
4810
+ }).strict(),
4811
+ z.object({
4812
+ type: z.literal("verdict"),
4813
+ verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4814
+ head: z.string(),
4815
+ comment_id: z.number().int(),
4816
+ objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4817
+ findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
4818
+ }).strict(),
4819
+ z.object({
4820
+ type: z.literal("escalation"),
4821
+ class: z.enum(["authority", "strategy", "product"]),
4822
+ comment_id: z.number().int()
4823
+ }).strict(),
4824
+ z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4825
+ z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4826
+ z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
4827
+ ]);
4828
+ var dispatchShared = {
4829
+ meta: HeaderMetaSchema,
4830
+ subject: SubjectSchema,
4831
+ kind: z.literal("dispatch"),
4832
+ ...envelopeTail,
4833
+ target_role: RoleSchema,
4834
+ model: z.string(),
4835
+ round: z.number().int().optional(),
4836
+ effect_id: z.string()
4837
+ };
4838
+ var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
4839
+ var DispatchEventSchema = z.discriminatedUnion("event", [
4840
+ z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4841
+ z.object({
4842
+ ...dispatchShared,
4843
+ event: z.literal("outcome_received"),
4844
+ outcome: DispatchOutcomeSchema,
4845
+ usage: dispatchUsageField
4846
+ }).strict(),
4847
+ z.object({
4848
+ ...dispatchShared,
4849
+ event: z.literal("dispatch_failed"),
4850
+ reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
4851
+ usage: dispatchUsageField
4852
+ }).strict()
4853
+ ]);
4854
+ var loopShared = {
4855
+ meta: HeaderMetaSchema,
4856
+ subject: SubjectSchema,
4857
+ kind: z.literal("dev_review_loop"),
4858
+ ...envelopeTail,
4859
+ loop_id: z.string()
4860
+ };
4861
+ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4862
+ z.object({
4863
+ ...loopShared,
4864
+ event: z.literal("loop_started"),
4865
+ task: z.number().int(),
4866
+ policy: z.object({
4867
+ max_rounds: z.number().int().nonnegative(),
4868
+ reviewers: z.array(RoleSchema),
4869
+ models: z.record(RoleSchema, z.string())
4870
+ }).strict()
4871
+ }).strict(),
4872
+ z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4873
+ z.object({
4874
+ ...loopShared,
4875
+ event: z.literal("gate_result_read"),
4876
+ round: z.number().int(),
4877
+ head: z.string(),
4878
+ green: z.boolean()
4879
+ }).strict(),
4880
+ z.object({
4881
+ ...loopShared,
4882
+ event: z.literal("verdicts_read"),
4883
+ round: z.number().int(),
4884
+ head: z.string(),
4885
+ all_approve: z.boolean(),
4886
+ blockers: z.number().int().nonnegative()
4887
+ }).strict(),
4888
+ z.object({
4889
+ ...loopShared,
4890
+ event: z.literal("findings_compared"),
4891
+ round: z.number().int(),
4892
+ open: z.array(z.string()),
4893
+ resolved: z.array(z.string()),
4894
+ new: z.array(z.string()),
4895
+ recurring: z.array(z.string())
4896
+ }).strict(),
4897
+ z.object({
4898
+ ...loopShared,
4899
+ event: z.literal("stop_condition_met"),
4900
+ round: z.number().int(),
4901
+ condition: z.enum([
4902
+ "green",
4903
+ "max_rounds",
4904
+ "no_progress",
4905
+ "escalated",
4906
+ "principal_stop",
4907
+ "confidence",
4908
+ "reappearance"
4909
+ ])
4910
+ }).strict(),
4911
+ z.object({
4912
+ ...loopShared,
4913
+ event: z.literal("paused"),
4914
+ round: z.number().int(),
4915
+ reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
4916
+ }).strict(),
4917
+ z.object({
4918
+ ...loopShared,
4919
+ event: z.literal("resumed"),
4920
+ round: z.number().int(),
4921
+ by: z.literal("principal")
4922
+ }).strict(),
4923
+ z.object({
4924
+ ...loopShared,
4925
+ event: z.literal("round_ended"),
4926
+ round: z.number().int(),
4927
+ base_head: z.string(),
4928
+ head: z.string(),
4929
+ files_changed: z.number().int().nonnegative(),
4930
+ insertions: z.number().int().nonnegative(),
4931
+ deletions: z.number().int().nonnegative(),
4932
+ wall_ms: z.number().nonnegative(),
4933
+ outcome: z.enum(["green", "changes_requested", "escalated"])
4934
+ }).strict(),
4935
+ z.object({
4936
+ ...loopShared,
4937
+ event: z.literal("journal_finalized"),
4938
+ rounds: z.number().int().nonnegative(),
4939
+ total_wall_ms: z.number().nonnegative(),
4940
+ time_to_green_ms: z.number().nonnegative().nullable(),
4941
+ files_changed_total: z.number().int().nonnegative(),
4942
+ final_head: z.string(),
4943
+ result: z.enum(["merged_ready", "stopped"])
4944
+ }).strict()
4945
+ ]);
4946
+ var ForgeOpSchema = z.enum([
4947
+ "pr.create",
4948
+ "pr.comment",
4949
+ "pr.body.replace",
4950
+ "pr.refreeze",
4951
+ "issue.create",
4952
+ "issue.edit",
4953
+ "issue.comment",
4954
+ "milestone.create",
4955
+ "milestone.edit",
4956
+ "milestone.close",
4957
+ "label.add",
4958
+ "label.remove"
4959
+ ]);
4960
+ var ForgeWriteTargetSchema = z.object({
4961
+ issue: z.number().int().optional(),
4962
+ pr: z.number().int().optional()
4963
+ }).strict();
4964
+ var forgeWriteShared = {
4965
+ meta: HeaderMetaSchema,
4966
+ subject: SubjectSchema,
4967
+ kind: z.literal("forge_write"),
4968
+ ...envelopeTail,
4969
+ op: ForgeOpSchema,
4970
+ target: ForgeWriteTargetSchema
4971
+ };
4972
+ var ForgeWriteEventSchema = z.discriminatedUnion("event", [
4973
+ z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
4974
+ z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
4975
+ z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
4976
+ ]);
4977
+ var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
4442
4978
  // src/checks/contract.ts
4443
4979
  var CHECK_SCHEMA_VERSION = 1;
4444
4980
  function emitCheckError(error) {