@attalabs/vinaya 0.22.0 → 0.24.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 (194) hide show
  1. package/README.md +14 -3
  2. package/aeg-root/contracts/archivist-tranche-archivist.md +2 -2
  3. package/aeg-root/contracts/brief-developer.md +1 -0
  4. package/aeg-root/contracts/developer-reviewer.md +3 -3
  5. package/aeg-root/contracts/reviewer-archivist.md +3 -3
  6. package/aeg-root/enforcement.md +26 -20
  7. package/aeg-root/process.md +8 -4
  8. package/aeg-root/roles/archivist.md +2 -2
  9. package/aeg-root/roles/brief-author.md +9 -3
  10. package/aeg-root/roles/developer.md +63 -31
  11. package/aeg-root/roles/planner.md +1 -1
  12. package/aeg-root/roles/principal.md +14 -0
  13. package/aeg-root/roles/reviewer.md +32 -15
  14. package/aeg-root/roles/security.md +15 -5
  15. package/aeg-root/skills/aeg/SKILL.md +9 -5
  16. package/aeg-root/skills/aeg-roles/SKILL.md +2 -2
  17. package/aeg-root/skills/brief-authoring/SKILL.md +39 -27
  18. package/aeg-root/state-machine.md +9 -11
  19. package/aeg-root/templates/brief-template.md +6 -6
  20. package/aeg-root/templates/pr-report-template.md +10 -7
  21. package/aeg-root/tranche-model.md +6 -2
  22. package/dist/checks/bin/check-body-bare-digits.js +559 -140
  23. package/dist/checks/bin/check-branch-topology.js +630 -145
  24. package/dist/checks/bin/check-brief-shape.js +600 -143
  25. package/dist/checks/bin/check-changeset-coverage.js +942 -162
  26. package/dist/checks/bin/check-closes-n.js +630 -145
  27. package/dist/checks/bin/check-coherence.js +641 -148
  28. package/dist/checks/bin/check-dead-branch-push.js +559 -140
  29. package/dist/checks/bin/check-dispatch-readiness.js +639 -146
  30. package/dist/checks/bin/check-doc-coverage-push.js +696 -219
  31. package/dist/checks/bin/check-doc-coverage.js +696 -219
  32. package/dist/checks/bin/check-doctrine-no-procedures.js +4800 -0
  33. package/dist/checks/bin/check-doctrine-portability.js +691 -147
  34. package/dist/checks/bin/check-evidence-fresh.js +1048 -222
  35. package/dist/checks/bin/check-exec-bits.js +4937 -0
  36. package/dist/checks/bin/check-first-push-dispatch.js +630 -145
  37. package/dist/checks/bin/check-issue-assignment.js +630 -145
  38. package/dist/checks/bin/check-main-branch-refusal.js +565 -141
  39. package/dist/checks/bin/check-no-disk-state.js +559 -140
  40. package/dist/checks/bin/check-pr-report-density.js +4467 -0
  41. package/dist/checks/bin/check-quoted-command.js +656 -164
  42. package/dist/checks/bin/check-reader-resolvable-prose.js +652 -163
  43. package/dist/checks/bin/check-registry-gates.js +602 -145
  44. package/dist/checks/bin/check-retired-vocabulary.js +652 -163
  45. package/dist/checks/bin/check-review-gate.js +677 -142
  46. package/dist/checks/bin/check-single-plan-pr.js +559 -140
  47. package/dist/checks/bin/check-test-plan.js +560 -141
  48. package/dist/checks/bin/check-token-collection-wired.js +559 -140
  49. package/dist/checks/bin/check-token-report.js +559 -140
  50. package/dist/checks/bin/check-workspace-escape.js +992 -146
  51. package/dist/index.js +8884 -5627
  52. package/package.json +1 -1
  53. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/actions.ts +173 -0
  54. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/anchored-region.ts +118 -0
  55. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/archive-task.ts +206 -0
  56. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/baseline-capture.ts +65 -0
  57. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/blast-radius-domains.ts +198 -0
  58. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/branch-topology-gate.ts +85 -0
  59. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +462 -0
  60. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-validation.ts +912 -0
  61. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/claude-code-transcript.ts +460 -0
  62. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/coherence-checks.ts +806 -0
  63. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/consumer-enumeration.ts +75 -0
  64. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-audit.ts +55 -0
  65. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-guard.ts +77 -0
  66. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-section7.ts +66 -0
  67. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-tranche.ts +94 -0
  68. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/diagram-model.ts +372 -0
  69. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/direct-main-push.ts +39 -0
  70. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dispatch-gate.ts +268 -0
  71. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doc-owners.ts +314 -0
  72. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/build-doc-nav.ts +38 -0
  73. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/docs-coherence.ts +136 -0
  74. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/index.ts +29 -0
  75. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/legacy-anchors.ts +48 -0
  76. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/nav-helpers.ts +17 -0
  77. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/node-route.ts +134 -0
  78. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/parse-doc.ts +43 -0
  79. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/published-prose.ts +306 -0
  80. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/surfaced-manifest.ts +89 -0
  81. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/types.ts +33 -0
  82. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-no-procedures.ts +94 -0
  83. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-portability.ts +293 -0
  84. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-source.ts +23 -0
  85. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/ensure-label.ts +28 -0
  86. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/file-classify.ts +51 -0
  87. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/first-push-dispatch-gate.ts +80 -0
  88. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/gate-audience.ts +171 -0
  89. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/index.ts +323 -0
  90. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-assignment.ts +102 -0
  91. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-validation.ts +746 -0
  92. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/leftover-detection.ts +68 -0
  93. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/local-anchor-coverage.ts +245 -0
  94. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +64 -0
  95. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/manifest-validity.ts +81 -0
  96. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/markdown-table.ts +76 -0
  97. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/metering-io-guard.ts +119 -0
  98. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/milestone-validation.ts +240 -0
  99. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/no-disk-state.ts +50 -0
  100. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-ledger.ts +77 -0
  101. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-registry.ts +105 -0
  102. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-token-report.ts +177 -0
  103. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-tranche.ts +183 -0
  104. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/pr-report-density.ts +116 -0
  105. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/pr-tier.ts +81 -0
  106. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/premise-check.ts +160 -0
  107. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/quoted-command.ts +275 -0
  108. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/reader-resolvable-prose.ts +320 -0
  109. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-checks.ts +255 -0
  110. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-parse.ts +164 -0
  111. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-scaffold.ts +216 -0
  112. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/report-tokens.ts +261 -0
  113. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/retired-vocabulary.ts +163 -0
  114. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-gate.ts +378 -0
  115. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-status.ts +200 -0
  116. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/single-plan-pr.ts +73 -0
  117. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-machine-model.ts +269 -0
  118. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-source.ts +14 -0
  119. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/status-block.ts +8 -0
  120. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/sum-ledger.ts +26 -0
  121. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/symbol-collisions.ts +78 -0
  122. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-gate.ts +117 -0
  123. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-section.ts +65 -0
  124. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/types.ts +174 -0
  125. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/verdict-extraction.ts +199 -0
  126. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/vocabulary-citation.ts +114 -0
  127. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/waiver-label.ts +52 -0
  128. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/workspace-escape.ts +172 -0
  129. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  130. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  131. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  132. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  133. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  134. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  135. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  136. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  137. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  138. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  139. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  140. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  141. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  142. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  143. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  144. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  145. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  147. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  148. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  149. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  150. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  151. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  152. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  153. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  154. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  155. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  156. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  157. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  158. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  159. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  160. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  161. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  162. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  163. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  164. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  165. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  166. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  167. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
  168. 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
  169. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  170. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  171. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  172. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  173. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  174. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__069-_41._.js → [root-of-the-server]__1pndh3_._.js} +1 -1
  175. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  176. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  177. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  178. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  179. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +2 -2
  180. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  181. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_projects_[name]_tranches_[slug]_05ufo8i._.js +3 -3
  182. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  183. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  184. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  185. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  186. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  187. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{37hnluhzj9bei.js → 0zebsmmk1bvnb.js} +1 -1
  188. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{267o-tsrnuwnv.js → 1uogpj5w2n5ju.js} +1 -1
  189. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1hufpuody6vjv.js → 3m1kgax7j2vgs.js} +1 -1
  190. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  191. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  192. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_buildManifest.js +0 -0
  193. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_clientMiddlewareManifest.js +0 -0
  194. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_ssgManifest.js +0 -0
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
2
4
 
3
5
  // src/checks/bin/check-doctrine-portability.ts
4
- import { execFileSync as execFileSync3 } from "node:child_process";
6
+ import { execFileSync as execFileSync4 } from "node:child_process";
5
7
  import { readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
6
- import { join as join2 } from "node:path";
8
+ import { join as join3 } from "node:path";
7
9
  // ../../packages/aeg-core/src/gate-audience.ts
8
10
  var GATE_AUDIENCE = {
9
11
  "check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
@@ -212,17 +214,18 @@ function splitIds(raw) {
212
214
  return [];
213
215
  return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
214
216
  }
215
- function resolveIds(raw, lastSlug) {
217
+ function resolveIds(raw) {
216
218
  const ids = splitIds(raw);
217
219
  const resolved = [];
220
+ let lastSlug = null;
218
221
  for (const id of ids) {
219
222
  const qualified = id.match(SLUG_QUALIFIED_ID);
220
223
  if (qualified) {
221
- lastSlug.current = qualified[1] ?? null;
224
+ lastSlug = qualified[1] ?? null;
222
225
  resolved.push(id);
223
- } else if (lastSlug.current) {
224
- resolved.push(`${lastSlug.current} ${id}`);
225
- lastSlug.current = null;
226
+ } else if (lastSlug) {
227
+ resolved.push(`${lastSlug} ${id}`);
228
+ lastSlug = null;
226
229
  } else {
227
230
  resolved.push(id);
228
231
  }
@@ -242,12 +245,7 @@ function parseRationaleDeps(body) {
242
245
  const section = extractSection(body);
243
246
  const dependsOn = [];
244
247
  const conflictsWith = [];
245
- let current = null;
246
248
  const assignedFields = new Set;
247
- const lastSlugByField = {
248
- dependsOn: { current: null },
249
- conflictsWith: { current: null }
250
- };
251
249
  const spanPattern = /`([^`]*)`/g;
252
250
  let match = spanPattern.exec(section);
253
251
  while (match !== null) {
@@ -257,13 +255,9 @@ function parseRationaleDeps(body) {
257
255
  const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
258
256
  if (!assignedFields.has(field)) {
259
257
  assignedFields.add(field);
260
- current = field;
261
- const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
258
+ const ids = resolveIds(labelMatch[2] ?? "");
262
259
  pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
263
260
  }
264
- } else if (current) {
265
- const ids = resolveIds(content, lastSlugByField[current]);
266
- pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
267
261
  }
268
262
  match = spanPattern.exec(section);
269
263
  }
@@ -1728,6 +1722,48 @@ function splitTableRow(row) {
1728
1722
  parts.pop();
1729
1723
  return parts.map((p) => p.split(PLACEHOLDER).join("|"));
1730
1724
  }
1725
+ // ../../packages/aeg-core/src/blast-radius-domains.ts
1726
+ function resolveWorkspaceEntry(entry2, listDirs) {
1727
+ if (entry2.endsWith("/*")) {
1728
+ const baseDir = entry2.slice(0, -2);
1729
+ return listDirs(baseDir).map((name) => `${baseDir}/${name}`);
1730
+ }
1731
+ if (entry2.includes("*"))
1732
+ return [];
1733
+ return [entry2];
1734
+ }
1735
+ // ../../packages/aeg-core/src/consumer-enumeration.ts
1736
+ function deriveWorkspaceMemberDirs(workspaces, listDirs) {
1737
+ const positive = workspaces.filter((entry2) => !entry2.startsWith("!"));
1738
+ const negative = workspaces.filter((entry2) => entry2.startsWith("!")).map((entry2) => entry2.slice(1));
1739
+ const excluded = new Set;
1740
+ for (const entry2 of negative) {
1741
+ for (const member of resolveWorkspaceEntry(entry2, listDirs))
1742
+ excluded.add(member);
1743
+ }
1744
+ const members = new Set;
1745
+ for (const entry2 of positive) {
1746
+ for (const member of resolveWorkspaceEntry(entry2, listDirs)) {
1747
+ if (!excluded.has(member))
1748
+ members.add(member);
1749
+ }
1750
+ }
1751
+ return [...members].sort();
1752
+ }
1753
+ function buildConsumersOf(workspaces, listDirs, readManifest) {
1754
+ const members = deriveWorkspaceMemberDirs(workspaces, listDirs);
1755
+ return (pkg) => {
1756
+ const target = `@attalabs/${pkg}`;
1757
+ return members.filter((dir) => {
1758
+ if (dir === `packages/${pkg}`)
1759
+ return false;
1760
+ const manifest = readManifest(dir);
1761
+ if (!manifest)
1762
+ return false;
1763
+ return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {});
1764
+ });
1765
+ };
1766
+ }
1731
1767
  // ../../packages/aeg-core/src/state-machine-model.ts
1732
1768
  var FORGE_FACT_INPUTS = [
1733
1769
  {
@@ -1832,6 +1868,22 @@ function isEmDashOrDash(s) {
1832
1868
  const t = s.trim();
1833
1869
  return t === "—" || t === "-" || t === "–";
1834
1870
  }
1871
+ // ../../packages/aeg-core/src/waiver-label.ts
1872
+ var WAIVER_LABEL = label("waiver-docs");
1873
+ var WAIVER_LABEL_REVIEW = label("waiver-review");
1874
+ var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
1875
+ function isPrincipal(login, principalAllowlist) {
1876
+ if (login === null)
1877
+ return false;
1878
+ const normalized = login.toLowerCase();
1879
+ return principalAllowlist.some((p) => p.toLowerCase() === normalized);
1880
+ }
1881
+ function isWaiverLabelActorVerified(opts) {
1882
+ if (!opts.labels.includes(opts.label))
1883
+ return false;
1884
+ return isPrincipal(opts.labelActor, opts.principalAllowlist);
1885
+ }
1886
+
1835
1887
  // ../../packages/aeg-core/src/parse-token-report.ts
1836
1888
  var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
1837
1889
  var INLINE_FIELD_LIST = /Phase:\s*([^|]+?)\s*\|\s*Role:\s*([^|]+?)\s*\|\s*Agent\/Model:\s*([^|]+?)\s*\|\s*Tokens in:\s*([^|]+?)\s*\|\s*Tokens out:\s*([^|]+?)\s*\|\s*Cost:\s*([^|]+?)\s*\|\s*Date:\s*(.+?)\s*$/im;
@@ -1886,6 +1938,47 @@ function parseInlineFieldList(section) {
1886
1938
  return null;
1887
1939
  return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
1888
1940
  }
1941
+ // ../../packages/aeg-core/src/verdict-extraction.ts
1942
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1943
+ function firstThreeLines(comment) {
1944
+ return comment.split(`
1945
+ `).slice(0, 3).join(`
1946
+ `);
1947
+ }
1948
+ function extractHeadSha(comment) {
1949
+ const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
1950
+ return m ? m[1].toLowerCase() : null;
1951
+ }
1952
+ function extractVerdict(comments, valuePattern, missingLabel) {
1953
+ const candidates = comments.filter((c) => valuePattern.test(c));
1954
+ if (candidates.length === 0) {
1955
+ return {
1956
+ value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1957
+ headSha: null,
1958
+ danglingNote: `no ${missingLabel} verdict comment found on this PR`
1959
+ };
1960
+ }
1961
+ const latest = candidates[candidates.length - 1];
1962
+ const m = firstThreeLines(latest).match(valuePattern);
1963
+ if (!m) {
1964
+ return {
1965
+ value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
1966
+ headSha: null,
1967
+ danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
1968
+ };
1969
+ }
1970
+ return {
1971
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
1972
+ headSha: extractHeadSha(latest),
1973
+ danglingNote: null
1974
+ };
1975
+ }
1976
+ function extractCodeReviewVerdict(comments) {
1977
+ return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
1978
+ }
1979
+ function extractSecurityReviewVerdict(comments) {
1980
+ return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
1981
+ }
1889
1982
  // ../../packages/aeg-core/src/report-tokens.ts
1890
1983
  var DASH_LOOKALIKES = {
1891
1984
  "-": "‑",
@@ -2050,22 +2143,6 @@ function isCodeFile(p) {
2050
2143
  return false;
2051
2144
  return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
2052
2145
  }
2053
- // ../../packages/aeg-core/src/waiver-label.ts
2054
- var WAIVER_LABEL = label("waiver-docs");
2055
- var WAIVER_LABEL_REVIEW = label("waiver-review");
2056
- var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
2057
- function isPrincipal(login, principalAllowlist) {
2058
- if (login === null)
2059
- return false;
2060
- const normalized = login.toLowerCase();
2061
- return principalAllowlist.some((p) => p.toLowerCase() === normalized);
2062
- }
2063
- function isWaiverLabelActorVerified(opts) {
2064
- if (!opts.labels.includes(opts.label))
2065
- return false;
2066
- return isPrincipal(opts.labelActor, opts.principalAllowlist);
2067
- }
2068
-
2069
2146
  // ../../packages/aeg-core/src/doc-owners.ts
2070
2147
  var DOC_OWNERS_PATH = ".vinaya/doc-owners";
2071
2148
  function parseDocOwners(content) {
@@ -2282,6 +2359,32 @@ function parsePremiseBlock(prBody) {
2282
2359
  }
2283
2360
  return assertions;
2284
2361
  }
2362
+ function premiseBlockText(prBody) {
2363
+ const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
2364
+ const lines = searchIn.split(/\r?\n/);
2365
+ let startIdx = -1;
2366
+ let endIdxExclusive = lines.length;
2367
+ for (let i = 0;i < lines.length; i++) {
2368
+ const line = lines[i].trim();
2369
+ if (startIdx === -1) {
2370
+ if (isPremiseHeader(line))
2371
+ startIdx = i;
2372
+ continue;
2373
+ }
2374
+ if (line === "") {
2375
+ endIdxExclusive = i;
2376
+ break;
2377
+ }
2378
+ if (!PREMISE_LINE.test(line)) {
2379
+ endIdxExclusive = i;
2380
+ break;
2381
+ }
2382
+ }
2383
+ if (startIdx === -1)
2384
+ return null;
2385
+ return lines.slice(startIdx, endIdxExclusive).join(`
2386
+ `);
2387
+ }
2285
2388
  function checkPremises(assertions, fileReader) {
2286
2389
  const failures = [];
2287
2390
  for (const a of assertions) {
@@ -2368,15 +2471,18 @@ function testPlanRegion(prBody) {
2368
2471
  return located.found ? located.section : prBody;
2369
2472
  }
2370
2473
  function checkTestPlan(prBody) {
2371
- const region = testPlanRegion(prBody);
2474
+ const located = locateTestPlanSection(prBody);
2475
+ const region = located.found ? located.section : prBody;
2372
2476
  if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
2373
2477
  return { status: "pass", errors: [] };
2374
2478
  if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
2375
2479
  return { status: "pass", errors: [] };
2480
+ if (located.found && extractFencedBlocks(region).length > 0)
2481
+ return { status: "pass", errors: [] };
2376
2482
  return {
2377
2483
  status: "fail",
2378
2484
  errors: [
2379
- "brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
2485
+ "brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, a fenced `[agent]` command list, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
2380
2486
  ]
2381
2487
  };
2382
2488
  }
@@ -2499,8 +2605,162 @@ function isBriefShaped(prBody) {
2499
2605
  const stripped = stripCode(prBody);
2500
2606
  return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
2501
2607
  }
2608
+ var COMMAND_WORDS = ["export", "bun", "gh", "git", "grep", "sed", "cat", "diff", "vinaya"];
2609
+ var BRIEF_RULES_SINCE_PR = 394;
2610
+ var AGENT_BOXES_REFUSED_SINCE_PR = 396;
2611
+ var AGENT_BOX_LINE_RE = /^-\s*\[[ xX]\]\s*\*{2}\[agent\]\*{2}/im;
2612
+ function checkNoAgentBoxes(prBody) {
2613
+ const region = testPlanRegion(prBody);
2614
+ if (!AGENT_BOX_LINE_RE.test(region))
2615
+ return { status: "pass", errors: [] };
2616
+ return {
2617
+ status: "fail",
2618
+ errors: [
2619
+ "brief-validation no agent boxes: the Test Plan carries a checkbox `[agent]` item — an agent never ticks a box or edits a PR body (Principal ruling, 2026-09-03, after PR #395). Render the `[agent]` half as a fenced command list instead; `vinaya pr report` runs each command from the PR head and writes its actual output into the AEG:EVIDENCE block. `[principal]` checkboxes are unaffected."
2620
+ ]
2621
+ };
2622
+ }
2623
+ function rolloutThresholdFor(message) {
2624
+ if (message.startsWith("brief-validation unpinned code claim:") || message.startsWith("brief-validation commands carry output:") || message.startsWith("brief-validation consumer tests:") || message.startsWith("brief-validation defeat cases:")) {
2625
+ return BRIEF_RULES_SINCE_PR;
2626
+ }
2627
+ if (message.startsWith("brief-validation no agent boxes:"))
2628
+ return AGENT_BOXES_REFUSED_SINCE_PR;
2629
+ return null;
2630
+ }
2631
+ function partitionBriefErrorsByRollout(errors, prNumber) {
2632
+ const blocking = [];
2633
+ const info = [];
2634
+ for (const e of errors) {
2635
+ const threshold = rolloutThresholdFor(e);
2636
+ const grandfathered = threshold !== null && prNumber !== null && prNumber < threshold;
2637
+ (grandfathered ? info : blocking).push(e);
2638
+ }
2639
+ return { blocking, info };
2640
+ }
2641
+ function extractFencedBlocks(text) {
2642
+ const re = /^[ \t]*(`{3,}|~{3,})([^\n]*)\n([\s\S]*?)^[ \t]*\1[ \t]*$/gm;
2643
+ const blocks = [];
2644
+ let m = re.exec(text);
2645
+ while (m !== null) {
2646
+ const lang = m[2].trim().toLowerCase().split(/\s+/)[0] ?? "";
2647
+ blocks.push({ start: m.index, end: m.index + m[0].length, lang, content: m[3] });
2648
+ m = re.exec(text);
2649
+ }
2650
+ return blocks;
2651
+ }
2652
+ function firstNonBlankLine(text) {
2653
+ const line = text.split(`
2654
+ `).find((l) => l.trim().length > 0);
2655
+ return line?.trim() ?? "";
2656
+ }
2657
+ var FILE_LINE_RE = /\b[\w./-]+\.(?:tsx?|jsx?|mjs|cjs|md|mdx|json|ya?ml|sh|py|toml):\d+\b/g;
2658
+ function checkNoUnpinnedCodeClaims(prBody) {
2659
+ const premiseText = premiseBlockText(prBody);
2660
+ const withoutPremise = premiseText ? prBody.replace(premiseText, "") : prBody;
2661
+ const scanned = stripCode(withoutPremise, { inlineSpans: "keep" });
2662
+ const matches = scanned.match(FILE_LINE_RE) ?? [];
2663
+ if (matches.length === 0)
2664
+ return { status: "pass", errors: [] };
2665
+ return {
2666
+ status: "fail",
2667
+ errors: matches.map((m) => `brief-validation unpinned code claim: "${m}" states a code fact by file-and-line reference outside a \`Premise:\` pin and outside a fenced code block — pin it in \`Premise:\`, or show the executed command and its output instead.`)
2668
+ };
2669
+ }
2670
+ function extractNumberedSection(prBody, num) {
2671
+ const startRe = new RegExp(`^#{1,4}\\s*(?:\\*\\*)?${num}[a-z]?\\.\\s`, "im");
2672
+ const start = startRe.exec(prBody);
2673
+ if (!start)
2674
+ return null;
2675
+ const afterStart = start.index + start[0].length;
2676
+ const rest = prBody.slice(afterStart);
2677
+ const next = /^#{1,4}\s/m.exec(rest);
2678
+ return next ? rest.slice(0, next.index) : rest;
2679
+ }
2680
+ function commandWordOf(line) {
2681
+ return line.trim().split(/\s+/)[0] ?? "";
2682
+ }
2683
+ function isCommandBlock(content) {
2684
+ return COMMAND_WORDS.includes(commandWordOf(firstNonBlankLine(content)));
2685
+ }
2686
+ function isStep0Block(content) {
2687
+ return firstNonBlankLine(content).startsWith("git worktree add");
2688
+ }
2689
+ function checkCommandsCarryOutput(prBody) {
2690
+ const errors = [];
2691
+ for (const [label2, num] of [
2692
+ ["5", 5],
2693
+ ["6", 6]
2694
+ ]) {
2695
+ const section = extractNumberedSection(prBody, num);
2696
+ if (!section)
2697
+ continue;
2698
+ const blocks = extractFencedBlocks(section);
2699
+ for (let i = 0;i < blocks.length; i++) {
2700
+ const block = blocks[i];
2701
+ if (isStep0Block(block.content) || !isCommandBlock(block.content))
2702
+ continue;
2703
+ const next = blocks[i + 1];
2704
+ if (!next || isCommandBlock(next.content)) {
2705
+ errors.push(`brief-validation commands carry output: §${label2} has a command block ("${firstNonBlankLine(block.content)}") not immediately followed by a non-command output block — the very next fenced block must hold that command's actual output, not another command.`);
2706
+ }
2707
+ }
2708
+ }
2709
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2710
+ }
2711
+ var CONSUMER_TESTS_SENTINEL_RE = /consumer-tests\s*:\s*none\s*[-—–]\s*\S/i;
2712
+ function packagesNamedIn(text) {
2713
+ const re = /\bpackages\/([A-Za-z0-9_-]+)\//g;
2714
+ const pkgs = new Set;
2715
+ let m = re.exec(text);
2716
+ while (m !== null) {
2717
+ pkgs.add(m[1]);
2718
+ m = re.exec(text);
2719
+ }
2720
+ return [...pkgs];
2721
+ }
2722
+ function hasTestPathForConsumer(text, consumerDir) {
2723
+ const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2724
+ const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2725
+ return re.test(text);
2726
+ }
2727
+ function checkConsumerTests(prBody, consumersOf) {
2728
+ const section4 = extractNumberedSection(prBody, 4);
2729
+ if (!section4)
2730
+ return { status: "pass", errors: [] };
2731
+ if (CONSUMER_TESTS_SENTINEL_RE.test(section4))
2732
+ return { status: "pass", errors: [] };
2733
+ const errors = [];
2734
+ for (const pkg of packagesNamedIn(section4)) {
2735
+ for (const consumerDir of consumersOf(pkg)) {
2736
+ if (hasTestPathForConsumer(section4, consumerDir))
2737
+ continue;
2738
+ errors.push(`brief-validation consumer tests: §4 names a path under packages/${pkg}/, and ${consumerDir} depends on @attalabs/${pkg}, but no test path under ${consumerDir} is named in §4 — name one, or add \`consumer-tests: none — <reason>\`.`);
2739
+ }
2740
+ }
2741
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2742
+ }
2743
+ var CHECK_NAME_RE = /\bcheck-[a-z0-9-]+(?:\.ts)?\b/;
2744
+ var FORGE_WRITE_COMMAND_RE = /\bgh\s+(?:pr|issue)\s+(?:create|merge|close|comment|edit|review)\b|\bgit\s+push\b|\bvinaya\s+pr\s+(?:create|report\s+--write)\b/i;
2745
+ var DEFEAT_CASES_RE = /defeat cases\s*:/i;
2746
+ function checkDefeatCases(prBody) {
2747
+ const section4 = extractNumberedSection(prBody, 4);
2748
+ if (!section4)
2749
+ return { status: "pass", errors: [] };
2750
+ if (!CHECK_NAME_RE.test(section4) && !FORGE_WRITE_COMMAND_RE.test(section4))
2751
+ return { status: "pass", errors: [] };
2752
+ const section6 = extractNumberedSection(prBody, 6) ?? "";
2753
+ if (DEFEAT_CASES_RE.test(section6))
2754
+ return { status: "pass", errors: [] };
2755
+ return {
2756
+ status: "fail",
2757
+ errors: [
2758
+ "brief-validation defeat cases: §4 names a check or a forge-writing command, but §6 carries no `Defeat cases:` line — name the inputs that would defeat this check, or that this command must not accidentally trigger on."
2759
+ ]
2760
+ };
2761
+ }
2502
2762
  function checkBriefSections(prBody, readTier, options = {}) {
2503
- const { requireClosesN = true } = options;
2763
+ const { requireClosesN = true, consumersOf = () => [] } = options;
2504
2764
  const results = [
2505
2765
  checkTierField(prBody, readTier),
2506
2766
  checkTestPlan(prBody),
@@ -2513,10 +2773,219 @@ function checkBriefSections(prBody, readTier, options = {}) {
2513
2773
  checkAutonomyClause(prBody),
2514
2774
  checkProjectField(prBody),
2515
2775
  checkForField(prBody),
2776
+ checkNoUnpinnedCodeClaims(prBody),
2777
+ checkNoAgentBoxes(prBody),
2778
+ checkCommandsCarryOutput(prBody),
2779
+ checkConsumerTests(prBody, consumersOf),
2780
+ checkDefeatCases(prBody),
2516
2781
  ...requireClosesN ? [checkClosesN(prBody)] : []
2517
2782
  ];
2518
2783
  return { errors: results.flatMap((r) => r.errors) };
2519
2784
  }
2785
+ // ../../packages/aeg-core/src/doctrine-portability.ts
2786
+ var DEFAULT_SHIPS_PREFIX = "aeg-root/";
2787
+ var STATIC_PORTABLE_PREFIXES = [
2788
+ "roles/",
2789
+ "contracts/",
2790
+ "skills/",
2791
+ ".github/",
2792
+ ".vinaya/",
2793
+ ".claude/",
2794
+ ".git/",
2795
+ ".husky/"
2796
+ ];
2797
+ var EXEMPT_LITERALS = new Set([
2798
+ "path/inside/the/shipped/diff.ts",
2799
+ "path/inside/the/surface.ts",
2800
+ "apps/x/specs/..."
2801
+ ]);
2802
+ var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
2803
+ var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
2804
+ var VENDOR_NAME_SOURCE = "\\bclaude code\\b|\\bclaude\\b|\\banthropic\\b|\\bchatgpt\\b|\\bopenai\\b|\\bgpt\\b|\\bgemini\\b|\\bcodex\\b|\\bgrok\\b|\\bdeepseek\\b|\\bopus\\b|\\bsonnet\\b|\\bhaiku\\b";
2805
+ var VENDOR_EXAMPLE_START = /<!--\s*AEG:VENDOR-EXAMPLE:START\s*-->/;
2806
+ var VENDOR_EXAMPLE_END = /<!--\s*AEG:VENDOR-EXAMPLE:END\s*-->/;
2807
+ function maskVendorExampleRegion(masked) {
2808
+ const start = VENDOR_EXAMPLE_START.exec(masked);
2809
+ if (!start)
2810
+ return masked;
2811
+ const afterStart = start.index + start[0].length;
2812
+ const end = VENDOR_EXAMPLE_END.exec(masked.slice(afterStart));
2813
+ if (!end)
2814
+ return masked;
2815
+ const regionEnd = afterStart + end.index + end[0].length;
2816
+ const region = masked.slice(start.index, regionEnd);
2817
+ const blanked = region.replace(/[^\n]/g, " ");
2818
+ return masked.slice(0, start.index) + blanked + masked.slice(regionEnd);
2819
+ }
2820
+ function extractVendorMentions(content) {
2821
+ const scoped = maskVendorExampleRegion(maskCode(content));
2822
+ const found = [];
2823
+ const pattern = new RegExp(VENDOR_NAME_SOURCE, "gi");
2824
+ let match = pattern.exec(scoped);
2825
+ while (match !== null) {
2826
+ found.push({ name: match[0], index: match.index });
2827
+ match = pattern.exec(scoped);
2828
+ }
2829
+ return found;
2830
+ }
2831
+ function extractCitedPaths(content) {
2832
+ const found = [];
2833
+ const spanPattern = /`([^`\n]+)`/g;
2834
+ let match = spanPattern.exec(content);
2835
+ while (match !== null) {
2836
+ const cited = match[1] ?? "";
2837
+ const topSegment = cited.slice(0, cited.indexOf("/"));
2838
+ if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
2839
+ found.push({ cited, index: match.index });
2840
+ }
2841
+ match = spanPattern.exec(content);
2842
+ }
2843
+ return found;
2844
+ }
2845
+ function lineAtIndex(content, index) {
2846
+ let line = 1;
2847
+ for (let i = 0;i < index; i++) {
2848
+ if (content.charCodeAt(i) === 10)
2849
+ line++;
2850
+ }
2851
+ return line;
2852
+ }
2853
+ function isPortable(cited, shipsPrefix) {
2854
+ if (cited.startsWith(shipsPrefix))
2855
+ return true;
2856
+ return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
2857
+ }
2858
+ function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
2859
+ const findings = [];
2860
+ for (const file of files) {
2861
+ if (!file.path.startsWith(shipsPrefix))
2862
+ continue;
2863
+ for (const { cited, index } of extractCitedPaths(file.content)) {
2864
+ if (EXEMPT_LITERALS.has(cited))
2865
+ continue;
2866
+ if (isPortable(cited, shipsPrefix))
2867
+ continue;
2868
+ findings.push({
2869
+ file: file.path,
2870
+ line: lineAtIndex(file.content, index),
2871
+ cited,
2872
+ message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`,
2873
+ kind: "path"
2874
+ });
2875
+ }
2876
+ for (const { name, index } of extractVendorMentions(file.content)) {
2877
+ findings.push({
2878
+ file: file.path,
2879
+ line: lineAtIndex(file.content, index),
2880
+ cited: name,
2881
+ message: `names "${name}" outside the one fenced vendor-example home (an <!-- AEG:VENDOR-EXAMPLE:START --> … <!-- AEG:VENDOR-EXAMPLE:END --> pair) — portable doctrine refers to a host generically everywhere else`,
2882
+ kind: "vendor-name"
2883
+ });
2884
+ }
2885
+ }
2886
+ return findings;
2887
+ }
2888
+
2889
+ // ../../packages/aeg-core/src/doctrine-no-procedures.ts
2890
+ function vendorExampleSpan(content) {
2891
+ const start = VENDOR_EXAMPLE_START.exec(content);
2892
+ if (!start)
2893
+ return null;
2894
+ const afterStart = start.index + start[0].length;
2895
+ const end = VENDOR_EXAMPLE_END.exec(content.slice(afterStart));
2896
+ if (!end)
2897
+ return null;
2898
+ return { start: start.index, end: afterStart + end.index + end[0].length };
2899
+ }
2900
+ function isCommandLine(line) {
2901
+ const word = line.trim().split(/\s+/)[0] ?? "";
2902
+ return COMMAND_WORDS.includes(word);
2903
+ }
2904
+ var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
2905
+ function procedureLineNumberAt(content, index) {
2906
+ return content.slice(0, index).split(`
2907
+ `).length;
2908
+ }
2909
+ function isTemplatePath(path) {
2910
+ return /(^|\/)templates\//.test(path);
2911
+ }
2912
+ function checkDoctrineNoProcedures(files) {
2913
+ const findings = [];
2914
+ for (const file of files) {
2915
+ if (isTemplatePath(file.path))
2916
+ continue;
2917
+ const span = vendorExampleSpan(file.content);
2918
+ for (const block of extractFencedBlocks(file.content)) {
2919
+ if (span && block.start >= span.start && block.end <= span.end)
2920
+ continue;
2921
+ if (!SHELL_LANGS.has(block.lang))
2922
+ continue;
2923
+ const commandLineCount = block.content.split(`
2924
+ `).filter(isCommandLine).length;
2925
+ if (commandLineCount >= 2) {
2926
+ findings.push({
2927
+ file: file.path,
2928
+ line: procedureLineNumberAt(file.content, block.start),
2929
+ message: `this fenced block has ${commandLineCount} shell-command lines — this sequence is a \`vinaya\` command, name it (or move it inside the \`AEG:VENDOR-EXAMPLE\` anchor / a \`templates/\` file).`
2930
+ });
2931
+ }
2932
+ }
2933
+ }
2934
+ return findings;
2935
+ }
2936
+ // ../../packages/aeg-core/src/pr-report-density.ts
2937
+ function headingSectionBody(prBody, heading) {
2938
+ const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "im");
2939
+ const m = headingRe.exec(prBody);
2940
+ if (!m)
2941
+ return null;
2942
+ const start = m.index + m[0].length;
2943
+ const rest = prBody.slice(start);
2944
+ const nextHeading = /^#{1,2}\s/m.exec(rest);
2945
+ return nextHeading ? rest.slice(0, nextHeading.index) : rest;
2946
+ }
2947
+ function stripAnchorBlocks(sectionText) {
2948
+ let result = sectionText;
2949
+ for (const field of ["CLOSES", "PROJECT", "TIER", "PREMISE", "TEST-PLAN", "EVIDENCE"]) {
2950
+ for (;; ) {
2951
+ const bounds = anchoredRegionBounds(result, field);
2952
+ if (!bounds)
2953
+ break;
2954
+ result = result.slice(0, bounds.outerStart) + result.slice(bounds.outerEnd);
2955
+ }
2956
+ }
2957
+ return result;
2958
+ }
2959
+ function paragraphCount(sectionText) {
2960
+ const stripped = stripCode(stripAnchorBlocks(sectionText)).trim();
2961
+ if (stripped.length === 0)
2962
+ return 0;
2963
+ return stripped.split(/\n[ \t]*\n/).map((block) => block.trim()).filter((block) => block.length > 0).length;
2964
+ }
2965
+ function checkSectionDensity(prBody, heading) {
2966
+ const body = headingSectionBody(prBody, heading);
2967
+ if (body === null)
2968
+ return { status: "pass", errors: [] };
2969
+ const count = paragraphCount(body);
2970
+ if (count <= 1)
2971
+ return { status: "pass", errors: [] };
2972
+ return {
2973
+ status: "fail",
2974
+ errors: [
2975
+ `pr-report-density ${heading}: "## ${heading}" holds ${count} paragraphs — the canonical PR-report form (aeg-root/roles/developer.md § PR body) requires exactly one. Collapse it to one paragraph; move the rest into a section of your own below the canonical four ("Add anything you want beneath the four sections", developer.md), a commit message, or the changeset — never into the AEG:EVIDENCE block, which is emitted only, never hand-typed.`
2976
+ ]
2977
+ };
2978
+ }
2979
+ function checkSummaryDensity(prBody) {
2980
+ return checkSectionDensity(prBody, "Summary");
2981
+ }
2982
+ function checkScopeDensity(prBody) {
2983
+ return checkSectionDensity(prBody, "Scope");
2984
+ }
2985
+ function checkPrReportDensity(prBody) {
2986
+ const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
2987
+ return { errors: results.flatMap((r) => r.errors) };
2988
+ }
2520
2989
  // ../../packages/aeg-core/src/issue-validation.ts
2521
2990
  function hasRationaleField(body, labelPattern) {
2522
2991
  const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
@@ -2768,6 +3237,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
2768
3237
  const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
2769
3238
  if (!depEntry)
2770
3239
  continue;
3240
+ const sameTask = depEntry.trancheSlug === e.trancheSlug && depEntry.task.id === e.task.id;
3241
+ const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3242
+ if (sameTask || sameIssue) {
3243
+ failures.push({
3244
+ issue: e.task.issue,
3245
+ tranche: e.trancheSlug,
3246
+ task: e.task.id,
3247
+ reason: `INTERNAL: parsed a self-dependency (depends-on ${dep}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream.`
3248
+ });
3249
+ continue;
3250
+ }
2771
3251
  const depFacts = depEntry.facts;
2772
3252
  const depClosed = depFacts?.issueState === "closed";
2773
3253
  if (!depClosed) {
@@ -2911,41 +3391,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2911
3391
  }
2912
3392
  return { ok: true, expectedIssue };
2913
3393
  }
2914
- // ../../packages/aeg-core/src/verdict-extraction.ts
2915
- var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2916
- function extractHeadSha(comment) {
2917
- const m = comment.match(HEAD_SHA_PATTERN);
2918
- return m ? m[1].toLowerCase() : null;
2919
- }
2920
- function extractVerdict(comments, valuePattern, missingLabel) {
2921
- const clearHits = comments.filter((c) => valuePattern.test(c));
2922
- if (clearHits.length > 0) {
2923
- const latest = clearHits[clearHits.length - 1];
2924
- const m = latest.match(valuePattern);
2925
- return {
2926
- value: m[1].toUpperCase().replace(/[_-]/g, " "),
2927
- headSha: extractHeadSha(latest),
2928
- danglingNote: null
2929
- };
2930
- }
2931
- return {
2932
- value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2933
- headSha: null,
2934
- danglingNote: `no ${missingLabel} verdict comment found on this PR`
2935
- };
2936
- }
2937
- function extractCodeReviewVerdict(comments) {
2938
- return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
2939
- }
2940
- function extractSecurityReviewVerdict(comments) {
2941
- return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2942
- }
2943
3394
  // ../../packages/aeg-core/src/review-gate.ts
2944
3395
  function isBoundToHead(extraction, headSha) {
2945
3396
  if (!extraction.headSha)
2946
3397
  return false;
2947
3398
  return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2948
3399
  }
3400
+ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3401
+ if (patchIdOf === undefined || !extraction.headSha)
3402
+ return false;
3403
+ const judged = patchIdOf(extraction.headSha);
3404
+ const current = patchIdOf(headSha);
3405
+ if (judged === null || current === null)
3406
+ return false;
3407
+ return judged === current;
3408
+ }
3409
+ function isBoundToPatch(extraction, headSha, patchIdOf) {
3410
+ return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3411
+ }
2949
3412
  function checkReviewGate(input) {
2950
3413
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
2951
3414
  const waived = isWaiverLabelActorVerified({
@@ -2961,6 +3424,8 @@ function checkReviewGate(input) {
2961
3424
  waived: true
2962
3425
  };
2963
3426
  }
3427
+ const reportedMechanicalChecks = input.mechanicalChecks.filter((c) => c.bucket !== "skipping" && c.bucket !== "neutral");
3428
+ const mechanicalChecksClean = reportedMechanicalChecks.length > 0 && reportedMechanicalChecks.every((c) => c.bucket === "pass");
2964
3429
  const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
2965
3430
  const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
2966
3431
  const verifiedBodies = verified.map((c) => c.body);
@@ -2968,12 +3433,12 @@ function checkReviewGate(input) {
2968
3433
  const security = extractSecurityReviewVerdict(verifiedBodies);
2969
3434
  const codeReviewClean = codeReview.value === "APPROVE";
2970
3435
  const securityClean = security.value === "PASS";
2971
- const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2972
- const securityBound = isBoundToHead(security, input.headSha);
2973
- if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
3436
+ const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3437
+ const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3438
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
2974
3439
  return {
2975
3440
  verdict: "pass",
2976
- reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
3441
+ 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.`,
2977
3442
  waived: false
2978
3443
  };
2979
3444
  }
@@ -2988,6 +3453,9 @@ function checkReviewGate(input) {
2988
3453
  } else if (!securityBound) {
2989
3454
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2990
3455
  }
3456
+ if (!mechanicalChecksClean) {
3457
+ 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(", ")}`);
3458
+ }
2991
3459
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2992
3460
  return {
2993
3461
  verdict: "fail",
@@ -3267,72 +3735,6 @@ function compareToBaseline(current, baseline) {
3267
3735
  perTool
3268
3736
  };
3269
3737
  }
3270
- // ../../packages/aeg-core/src/doctrine-portability.ts
3271
- var DEFAULT_SHIPS_PREFIX = "aeg-root/";
3272
- var STATIC_PORTABLE_PREFIXES = [
3273
- "roles/",
3274
- "contracts/",
3275
- "skills/",
3276
- ".github/",
3277
- ".vinaya/",
3278
- ".claude/",
3279
- ".git/",
3280
- ".husky/"
3281
- ];
3282
- var EXEMPT_LITERALS = new Set([
3283
- "path/inside/the/shipped/diff.ts",
3284
- "path/inside/the/surface.ts",
3285
- "apps/x/specs/..."
3286
- ]);
3287
- var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
3288
- var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
3289
- function extractCitedPaths(content) {
3290
- const found = [];
3291
- const spanPattern = /`([^`\n]+)`/g;
3292
- let match = spanPattern.exec(content);
3293
- while (match !== null) {
3294
- const cited = match[1] ?? "";
3295
- const topSegment = cited.slice(0, cited.indexOf("/"));
3296
- if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
3297
- found.push({ cited, index: match.index });
3298
- }
3299
- match = spanPattern.exec(content);
3300
- }
3301
- return found;
3302
- }
3303
- function lineAtIndex(content, index) {
3304
- let line = 1;
3305
- for (let i = 0;i < index; i++) {
3306
- if (content.charCodeAt(i) === 10)
3307
- line++;
3308
- }
3309
- return line;
3310
- }
3311
- function isPortable(cited, shipsPrefix) {
3312
- if (cited.startsWith(shipsPrefix))
3313
- return true;
3314
- return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
3315
- }
3316
- function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
3317
- const findings = [];
3318
- for (const file of files) {
3319
- if (!file.path.startsWith(shipsPrefix))
3320
- continue;
3321
- for (const { cited, index } of extractCitedPaths(file.content)) {
3322
- if (EXEMPT_LITERALS.has(cited))
3323
- continue;
3324
- if (isPortable(cited, shipsPrefix))
3325
- continue;
3326
- findings.push({
3327
- file: file.path,
3328
- line: lineAtIndex(file.content, index),
3329
- cited,
3330
- message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
3331
- });
3332
- }
3333
- }
3334
- return findings;
3335
- }
3336
3738
  // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3337
3739
  var SHIPS_PREFIX = "aeg-root/";
3338
3740
  function shipsArchivePrefix(shipsPrefix) {
@@ -3668,6 +4070,12 @@ function scanRetiredVocabulary(files) {
3668
4070
  function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
3669
4071
  return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
3670
4072
  }
4073
+ function isSelfDependency(dep, task, issue) {
4074
+ if (dep.issue !== null && issue !== null && dep.issue === issue.number)
4075
+ return true;
4076
+ const bare = dep.id.trim().replace(/^#/, "");
4077
+ return bare.toLowerCase() === task.id.trim().toLowerCase();
4078
+ }
3671
4079
  function checkDispatchReadiness(input) {
3672
4080
  const { trancheSlug, task } = input;
3673
4081
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
@@ -3682,6 +4090,11 @@ function checkDispatchReadiness(input) {
3682
4090
  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.`);
3683
4091
  }
3684
4092
  for (const dep of input.dependsOn) {
4093
+ if (isSelfDependency(dep, task, input.issue)) {
4094
+ const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4095
+ 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.`);
4096
+ continue;
4097
+ }
3685
4098
  if (dep.resolved === false) {
3686
4099
  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.`);
3687
4100
  continue;
@@ -3746,8 +4159,12 @@ function isNewDiskStateFile(path, status) {
3746
4159
  return ANY_DEPTH_TRANCHES_MD.test(path) || TOKENS_FILE.test(path);
3747
4160
  }
3748
4161
  // ../../packages/aeg-core/src/main-branch-refusal.ts
4162
+ function pushesAnyBranchRef(pushRefs) {
4163
+ return pushRefs.split(`
4164
+ `).map((line) => line.trim()).filter((line) => line.length > 0).some((line) => line.split(/\s+/)[2]?.startsWith("refs/heads/") === true);
4165
+ }
3749
4166
  function checkMainBranchRefusal(facts) {
3750
- const { currentSymbolicBranch, defaultBranch } = facts;
4167
+ const { currentSymbolicBranch, defaultBranch, pushRefs = null } = facts;
3751
4168
  if (currentSymbolicBranch === null)
3752
4169
  return null;
3753
4170
  if (defaultBranch === null) {
@@ -3759,6 +4176,8 @@ function checkMainBranchRefusal(facts) {
3759
4176
  };
3760
4177
  }
3761
4178
  if (currentSymbolicBranch === defaultBranch) {
4179
+ if (pushRefs !== null && !pushesAnyBranchRef(pushRefs))
4180
+ return null;
3762
4181
  return {
3763
4182
  reason: "on-default-branch",
3764
4183
  severity: "error",
@@ -3921,27 +4340,27 @@ function evaluateTestPlanGate(body, branch) {
3921
4340
  };
3922
4341
  }
3923
4342
  const checkboxLines = section.split(`
3924
- `).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]/.test(line));
4343
+ `).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
3925
4344
  if (checkboxLines.length === 0) {
3926
4345
  return {
3927
4346
  verdict: "pass",
3928
4347
  messages: [
3929
- "Test Plan section found but has no checkbox items.",
4348
+ "Test Plan section found but has no `[principal]` checkbox items.",
3930
4349
  "PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
3931
4350
  ]
3932
4351
  };
3933
4352
  }
3934
4353
  const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
3935
4354
  const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
3936
- const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
4355
+ const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
3937
4356
  if (unchecked.length > 0) {
3938
- messages.push("", "FAIL — the following Test Plan items are unticked:");
4357
+ messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
3939
4358
  for (const line of unchecked)
3940
4359
  messages.push(` ${line}`);
3941
- messages.push("", "Per aeg-root/roles/developer.md (Verification), a PR is not mergeable while any Test Plan box is unticked.", "- [agent] items: the Developer-agent posts the actual command output as evidence and ticks the box.", "- [principal] items: the Principal runs the item in a real signed-in browser and ticks the box.", "Note: editing the PR body does not retrigger most workflows. If your PR body changes do not surface here, push an empty commit to re-run.");
4360
+ messages.push("", "Per aeg-root/roles/developer.md (Verification), a PR is not mergeable while any [principal] Test Plan box is unticked.", "The Principal runs the item in a real signed-in browser and ticks the box.", "Note: editing the PR body does not retrigger most workflows. If your PR body changes do not surface here, push an empty commit to re-run.");
3942
4361
  return { verdict: "fail", messages };
3943
4362
  }
3944
- messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
4363
+ messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
3945
4364
  return { verdict: "pass", messages };
3946
4365
  }
3947
4366
  // ../../packages/aeg-core/src/workspace-escape.ts
@@ -4324,6 +4743,10 @@ function loadConfig() {
4324
4743
  }
4325
4744
  var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
4326
4745
 
4746
+ // src/lib/diff-evidence.ts
4747
+ import { execFileSync as execFileSync3 } from "node:child_process";
4748
+ import { join as join2, relative, resolve as resolve2 } from "node:path";
4749
+
4327
4750
  // src/checks/contract.ts
4328
4751
  var CHECK_SCHEMA_VERSION = 1;
4329
4752
  function emitCheckError(error) {
@@ -4331,13 +4754,132 @@ function emitCheckError(error) {
4331
4754
  `);
4332
4755
  }
4333
4756
 
4757
+ // src/commands/review-post.ts
4758
+ function parseChangedLineRanges(diffOutput) {
4759
+ const result = {};
4760
+ let currentFile = null;
4761
+ for (const line of diffOutput.split(`
4762
+ `)) {
4763
+ if (line.startsWith("+++ ")) {
4764
+ const path = line.slice(4).trim();
4765
+ currentFile = path === "/dev/null" ? null : path.replace(/^b\//, "");
4766
+ continue;
4767
+ }
4768
+ if (line.startsWith("@@") && currentFile) {
4769
+ const m = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
4770
+ if (m) {
4771
+ const start = Number(m[1]);
4772
+ const count = m[2] !== undefined ? Number(m[2]) : 1;
4773
+ if (count > 0) {
4774
+ if (!result[currentFile])
4775
+ result[currentFile] = [];
4776
+ result[currentFile].push([start, start + count - 1]);
4777
+ }
4778
+ }
4779
+ }
4780
+ }
4781
+ return result;
4782
+ }
4783
+
4784
+ // src/lib/diff-evidence.ts
4785
+ function fileDiffAgainst(ref, path) {
4786
+ let out;
4787
+ try {
4788
+ out = execFileSync3("git", ["diff", `${ref}...HEAD`, "--", path], {
4789
+ encoding: "utf8",
4790
+ stdio: ["ignore", "pipe", "pipe"]
4791
+ }).trim();
4792
+ } catch {
4793
+ return null;
4794
+ }
4795
+ return out === "" ? null : out;
4796
+ }
4797
+ function revParse(ref) {
4798
+ try {
4799
+ return execFileSync3("git", ["rev-parse", "--verify", ref], {
4800
+ encoding: "utf8",
4801
+ stdio: ["ignore", "pipe", "pipe"]
4802
+ }).trim();
4803
+ } catch {
4804
+ return null;
4805
+ }
4806
+ }
4807
+ function repoRoot() {
4808
+ try {
4809
+ return execFileSync3("git", ["rev-parse", "--show-toplevel"], {
4810
+ encoding: "utf8",
4811
+ stdio: ["ignore", "pipe", "pipe"]
4812
+ }).trim();
4813
+ } catch {
4814
+ return null;
4815
+ }
4816
+ }
4817
+ function changedFiles(base) {
4818
+ let out;
4819
+ try {
4820
+ out = execFileSync3("git", ["diff", "--name-only", `${base}...HEAD`], {
4821
+ encoding: "utf8",
4822
+ stdio: ["ignore", "pipe", "pipe"]
4823
+ }).trim();
4824
+ } catch {
4825
+ return null;
4826
+ }
4827
+ return out === "" ? [] : out.split(`
4828
+ `).map((s) => s.trim()).filter(Boolean);
4829
+ }
4830
+ function resolveChangedFiles(base = process.env.BASE_SHA || "origin/main") {
4831
+ return resolveDiff(base)?.files ?? null;
4832
+ }
4833
+ function resolveDiff(base = process.env.BASE_SHA || "origin/main") {
4834
+ const root = repoRoot();
4835
+ if (root === null)
4836
+ return null;
4837
+ const head = revParse("HEAD");
4838
+ for (const ref of [base, "main"]) {
4839
+ const resolved = revParse(ref);
4840
+ if (resolved === null || resolved === head)
4841
+ continue;
4842
+ const relPaths = changedFiles(ref);
4843
+ if (relPaths === null)
4844
+ continue;
4845
+ return { base: ref, root, files: relPaths.map((p) => join2(root, p)) };
4846
+ }
4847
+ return null;
4848
+ }
4849
+ function changedLineRanges(ref, path) {
4850
+ const diff = fileDiffAgainst(ref, path);
4851
+ if (diff === null)
4852
+ return null;
4853
+ return parseChangedLineRanges(diff)[path] ?? null;
4854
+ }
4855
+ function lineIsInRanges(line, ranges) {
4856
+ return ranges.some(([start, end]) => line >= start && line <= end);
4857
+ }
4858
+ function findingsInThisDiff(findings) {
4859
+ const diff = resolveDiff();
4860
+ if (diff === null)
4861
+ return [...findings];
4862
+ const changed = new Set(diff.files);
4863
+ const ranges = new Map;
4864
+ return findings.filter((f) => {
4865
+ const abs = resolve2(diff.root, f.file);
4866
+ if (!changed.has(abs))
4867
+ return false;
4868
+ const relPath = relative(diff.root, abs);
4869
+ if (!ranges.has(relPath))
4870
+ ranges.set(relPath, changedLineRanges(diff.base, relPath));
4871
+ const fileRanges = ranges.get(relPath) ?? null;
4872
+ return fileRanges === null ? true : lineIsInRanges(f.line, fileRanges);
4873
+ });
4874
+ }
4875
+
4334
4876
  // src/checks/bin/check-doctrine-portability.ts
4335
4877
  var CHECK_NAME = "doctrine-portability";
4336
4878
  var DOCTRINE_ROOT = loadConfig()?.proseGates?.doctrineRoot ?? "aeg-root";
4337
4879
  var SHIPS_PREFIX2 = `${DOCTRINE_ROOT}/`;
4338
4880
  function git(args) {
4339
4881
  try {
4340
- return execFileSync3("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4882
+ return execFileSync4("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4341
4883
  } catch {
4342
4884
  return "";
4343
4885
  }
@@ -4350,7 +4892,7 @@ function collectWorkingTree(dir, out = []) {
4350
4892
  return out;
4351
4893
  }
4352
4894
  for (const name of entries) {
4353
- const full = join2(dir, name);
4895
+ const full = join3(dir, name);
4354
4896
  let isDir;
4355
4897
  try {
4356
4898
  isDir = statSync(full).isDirectory();
@@ -4377,7 +4919,7 @@ function collectAtRef(ref, dir) {
4377
4919
  return paths.map((p) => ({ path: p, content: git(["show", `${ref}:${p}`]) }));
4378
4920
  }
4379
4921
  function findingKey(f) {
4380
- return `${f.file}:${f.cited}`;
4922
+ return `${f.file}:${f.kind}:${f.cited}`;
4381
4923
  }
4382
4924
  function newSince(baseline, current) {
4383
4925
  const remaining = new Map;
@@ -4403,10 +4945,12 @@ function main() {
4403
4945
  const currentFindings = checkDoctrinePortability(currentFiles, SHIPS_PREFIX2);
4404
4946
  const baselineFiles = collectAtRef(base, DOCTRINE_ROOT);
4405
4947
  const baselineFindings = checkDoctrinePortability(baselineFiles, SHIPS_PREFIX2);
4406
- const newFindings = newSince(baselineFindings, currentFindings);
4948
+ const newFindings = findingsInThisDiff(newSince(baselineFindings, currentFindings));
4407
4949
  const baseline = captureBaseline([{ tool: CHECK_NAME, findingCount: baselineFindings.length }], new Date().toISOString());
4408
4950
  const comparison = compareToBaseline([{ tool: CHECK_NAME, findingCount: currentFindings.length }], baseline);
4409
4951
  console.log(`${CHECK_NAME}: doctrine root "${DOCTRINE_ROOT}"; ${currentFiles.length} file(s) at HEAD, ${baselineFiles.length} at "${base}"; ` + `baseline ${baselineFindings.length}, current ${currentFindings.length}, delta ${comparison.delta}; ${newFindings.length} new finding(s)`);
4952
+ const PATH_RECOVERY_PROMPT = "This doctrine page cites a path that only resolves in the authoring repository, not in an adopter’s " + "install — an adopter cannot follow it. Rewrite the citation to a doctrine-relative path (`roles/...`, " + "`contracts/...`, `skills/...`, `aeg-root/...`) or an adopter-owned one (`.github/...`, `.vinaya/...`, " + "`.claude/...`), or state the fact without pointing at the file at all.";
4953
+ const VENDOR_NAME_RECOVERY_PROMPT = "This doctrine page names an AI vendor/product/agent by name outside the one sanctioned fenced home " + "(`<!-- AEG:VENDOR-EXAMPLE:START -->` … `<!-- AEG:VENDOR-EXAMPLE:END -->` in `tranche-model.md` §12). " + `Rewrite the mention to refer to the host/agent generically (e.g. "the coding agent," "this repo's ` + 'shipped reference host," "an external, independently-hosted model") instead of by product name.';
4410
4954
  for (const finding of newFindings) {
4411
4955
  emitCheckError({
4412
4956
  schema: CHECK_SCHEMA_VERSION,
@@ -4415,7 +4959,7 @@ function main() {
4415
4959
  message: `${finding.file}:${finding.line}: ${finding.message}`,
4416
4960
  file: finding.file,
4417
4961
  line: finding.line,
4418
- agent_recovery_prompt: "This doctrine page cites a path that only resolves in the authoring repository, not in an adopter’s " + "install — an adopter cannot follow it. Rewrite the citation to a doctrine-relative path (`roles/...`, " + "`contracts/...`, `skills/...`, `aeg-root/...`) or an adopter-owned one (`.github/...`, `.vinaya/...`, " + "`.claude/...`), or state the fact without pointing at the file at all."
4962
+ agent_recovery_prompt: finding.kind === "vendor-name" ? VENDOR_NAME_RECOVERY_PROMPT : PATH_RECOVERY_PROMPT
4419
4963
  });
4420
4964
  }
4421
4965
  process.exit(0);