@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,4 +1,6 @@
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-coherence.ts
4
6
  import { execFile as execFile4, execFileSync as execFileSync3 } from "node:child_process";
@@ -213,17 +215,18 @@ function splitIds(raw) {
213
215
  return [];
214
216
  return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
215
217
  }
216
- function resolveIds(raw, lastSlug) {
218
+ function resolveIds(raw) {
217
219
  const ids = splitIds(raw);
218
220
  const resolved = [];
221
+ let lastSlug = null;
219
222
  for (const id of ids) {
220
223
  const qualified = id.match(SLUG_QUALIFIED_ID);
221
224
  if (qualified) {
222
- lastSlug.current = qualified[1] ?? null;
225
+ lastSlug = qualified[1] ?? null;
223
226
  resolved.push(id);
224
- } else if (lastSlug.current) {
225
- resolved.push(`${lastSlug.current} ${id}`);
226
- lastSlug.current = null;
227
+ } else if (lastSlug) {
228
+ resolved.push(`${lastSlug} ${id}`);
229
+ lastSlug = null;
227
230
  } else {
228
231
  resolved.push(id);
229
232
  }
@@ -243,12 +246,7 @@ function parseRationaleDeps(body) {
243
246
  const section = extractSection(body);
244
247
  const dependsOn = [];
245
248
  const conflictsWith = [];
246
- let current = null;
247
249
  const assignedFields = new Set;
248
- const lastSlugByField = {
249
- dependsOn: { current: null },
250
- conflictsWith: { current: null }
251
- };
252
250
  const spanPattern = /`([^`]*)`/g;
253
251
  let match = spanPattern.exec(section);
254
252
  while (match !== null) {
@@ -258,13 +256,9 @@ function parseRationaleDeps(body) {
258
256
  const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
259
257
  if (!assignedFields.has(field)) {
260
258
  assignedFields.add(field);
261
- current = field;
262
- const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
259
+ const ids = resolveIds(labelMatch[2] ?? "");
263
260
  pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
264
261
  }
265
- } else if (current) {
266
- const ids = resolveIds(content, lastSlugByField[current]);
267
- pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
268
262
  }
269
263
  match = spanPattern.exec(section);
270
264
  }
@@ -1729,6 +1723,48 @@ function splitTableRow(row) {
1729
1723
  parts.pop();
1730
1724
  return parts.map((p) => p.split(PLACEHOLDER).join("|"));
1731
1725
  }
1726
+ // ../../packages/aeg-core/src/blast-radius-domains.ts
1727
+ function resolveWorkspaceEntry(entry2, listDirs) {
1728
+ if (entry2.endsWith("/*")) {
1729
+ const baseDir = entry2.slice(0, -2);
1730
+ return listDirs(baseDir).map((name) => `${baseDir}/${name}`);
1731
+ }
1732
+ if (entry2.includes("*"))
1733
+ return [];
1734
+ return [entry2];
1735
+ }
1736
+ // ../../packages/aeg-core/src/consumer-enumeration.ts
1737
+ function deriveWorkspaceMemberDirs(workspaces, listDirs) {
1738
+ const positive = workspaces.filter((entry2) => !entry2.startsWith("!"));
1739
+ const negative = workspaces.filter((entry2) => entry2.startsWith("!")).map((entry2) => entry2.slice(1));
1740
+ const excluded = new Set;
1741
+ for (const entry2 of negative) {
1742
+ for (const member of resolveWorkspaceEntry(entry2, listDirs))
1743
+ excluded.add(member);
1744
+ }
1745
+ const members = new Set;
1746
+ for (const entry2 of positive) {
1747
+ for (const member of resolveWorkspaceEntry(entry2, listDirs)) {
1748
+ if (!excluded.has(member))
1749
+ members.add(member);
1750
+ }
1751
+ }
1752
+ return [...members].sort();
1753
+ }
1754
+ function buildConsumersOf(workspaces, listDirs, readManifest) {
1755
+ const members = deriveWorkspaceMemberDirs(workspaces, listDirs);
1756
+ return (pkg) => {
1757
+ const target = `@attalabs/${pkg}`;
1758
+ return members.filter((dir) => {
1759
+ if (dir === `packages/${pkg}`)
1760
+ return false;
1761
+ const manifest = readManifest(dir);
1762
+ if (!manifest)
1763
+ return false;
1764
+ return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {});
1765
+ });
1766
+ };
1767
+ }
1732
1768
  // ../../packages/aeg-core/src/state-machine-model.ts
1733
1769
  var FORGE_FACT_INPUTS = [
1734
1770
  {
@@ -1833,6 +1869,22 @@ function isEmDashOrDash(s) {
1833
1869
  const t = s.trim();
1834
1870
  return t === "—" || t === "-" || t === "–";
1835
1871
  }
1872
+ // ../../packages/aeg-core/src/waiver-label.ts
1873
+ var WAIVER_LABEL = label("waiver-docs");
1874
+ var WAIVER_LABEL_REVIEW = label("waiver-review");
1875
+ var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
1876
+ function isPrincipal(login, principalAllowlist) {
1877
+ if (login === null)
1878
+ return false;
1879
+ const normalized = login.toLowerCase();
1880
+ return principalAllowlist.some((p) => p.toLowerCase() === normalized);
1881
+ }
1882
+ function isWaiverLabelActorVerified(opts) {
1883
+ if (!opts.labels.includes(opts.label))
1884
+ return false;
1885
+ return isPrincipal(opts.labelActor, opts.principalAllowlist);
1886
+ }
1887
+
1836
1888
  // ../../packages/aeg-core/src/parse-token-report.ts
1837
1889
  var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
1838
1890
  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;
@@ -1887,6 +1939,47 @@ function parseInlineFieldList(section) {
1887
1939
  return null;
1888
1940
  return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
1889
1941
  }
1942
+ // ../../packages/aeg-core/src/verdict-extraction.ts
1943
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1944
+ function firstThreeLines(comment) {
1945
+ return comment.split(`
1946
+ `).slice(0, 3).join(`
1947
+ `);
1948
+ }
1949
+ function extractHeadSha(comment) {
1950
+ const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
1951
+ return m ? m[1].toLowerCase() : null;
1952
+ }
1953
+ function extractVerdict(comments, valuePattern, missingLabel) {
1954
+ const candidates = comments.filter((c) => valuePattern.test(c));
1955
+ if (candidates.length === 0) {
1956
+ return {
1957
+ value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1958
+ headSha: null,
1959
+ danglingNote: `no ${missingLabel} verdict comment found on this PR`
1960
+ };
1961
+ }
1962
+ const latest = candidates[candidates.length - 1];
1963
+ const m = firstThreeLines(latest).match(valuePattern);
1964
+ if (!m) {
1965
+ return {
1966
+ value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
1967
+ headSha: null,
1968
+ danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
1969
+ };
1970
+ }
1971
+ return {
1972
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
1973
+ headSha: extractHeadSha(latest),
1974
+ danglingNote: null
1975
+ };
1976
+ }
1977
+ function extractCodeReviewVerdict(comments) {
1978
+ return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
1979
+ }
1980
+ function extractSecurityReviewVerdict(comments) {
1981
+ return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
1982
+ }
1890
1983
  // ../../packages/aeg-core/src/report-tokens.ts
1891
1984
  var DASH_LOOKALIKES = {
1892
1985
  "-": "‑",
@@ -2051,22 +2144,6 @@ function isCodeFile(p) {
2051
2144
  return false;
2052
2145
  return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
2053
2146
  }
2054
- // ../../packages/aeg-core/src/waiver-label.ts
2055
- var WAIVER_LABEL = label("waiver-docs");
2056
- var WAIVER_LABEL_REVIEW = label("waiver-review");
2057
- var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
2058
- function isPrincipal(login, principalAllowlist) {
2059
- if (login === null)
2060
- return false;
2061
- const normalized = login.toLowerCase();
2062
- return principalAllowlist.some((p) => p.toLowerCase() === normalized);
2063
- }
2064
- function isWaiverLabelActorVerified(opts) {
2065
- if (!opts.labels.includes(opts.label))
2066
- return false;
2067
- return isPrincipal(opts.labelActor, opts.principalAllowlist);
2068
- }
2069
-
2070
2147
  // ../../packages/aeg-core/src/doc-owners.ts
2071
2148
  var DOC_OWNERS_PATH = ".vinaya/doc-owners";
2072
2149
  function parseDocOwners(content) {
@@ -2283,6 +2360,32 @@ function parsePremiseBlock(prBody) {
2283
2360
  }
2284
2361
  return assertions;
2285
2362
  }
2363
+ function premiseBlockText(prBody) {
2364
+ const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
2365
+ const lines = searchIn.split(/\r?\n/);
2366
+ let startIdx = -1;
2367
+ let endIdxExclusive = lines.length;
2368
+ for (let i = 0;i < lines.length; i++) {
2369
+ const line = lines[i].trim();
2370
+ if (startIdx === -1) {
2371
+ if (isPremiseHeader(line))
2372
+ startIdx = i;
2373
+ continue;
2374
+ }
2375
+ if (line === "") {
2376
+ endIdxExclusive = i;
2377
+ break;
2378
+ }
2379
+ if (!PREMISE_LINE.test(line)) {
2380
+ endIdxExclusive = i;
2381
+ break;
2382
+ }
2383
+ }
2384
+ if (startIdx === -1)
2385
+ return null;
2386
+ return lines.slice(startIdx, endIdxExclusive).join(`
2387
+ `);
2388
+ }
2286
2389
  function checkPremises(assertions, fileReader) {
2287
2390
  const failures = [];
2288
2391
  for (const a of assertions) {
@@ -2369,15 +2472,18 @@ function testPlanRegion(prBody) {
2369
2472
  return located.found ? located.section : prBody;
2370
2473
  }
2371
2474
  function checkTestPlan(prBody) {
2372
- const region = testPlanRegion(prBody);
2475
+ const located = locateTestPlanSection(prBody);
2476
+ const region = located.found ? located.section : prBody;
2373
2477
  if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
2374
2478
  return { status: "pass", errors: [] };
2375
2479
  if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
2376
2480
  return { status: "pass", errors: [] };
2481
+ if (located.found && extractFencedBlocks(region).length > 0)
2482
+ return { status: "pass", errors: [] };
2377
2483
  return {
2378
2484
  status: "fail",
2379
2485
  errors: [
2380
- "brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
2486
+ "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."
2381
2487
  ]
2382
2488
  };
2383
2489
  }
@@ -2500,8 +2606,162 @@ function isBriefShaped(prBody) {
2500
2606
  const stripped = stripCode(prBody);
2501
2607
  return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
2502
2608
  }
2609
+ var COMMAND_WORDS = ["export", "bun", "gh", "git", "grep", "sed", "cat", "diff", "vinaya"];
2610
+ var BRIEF_RULES_SINCE_PR = 394;
2611
+ var AGENT_BOXES_REFUSED_SINCE_PR = 396;
2612
+ var AGENT_BOX_LINE_RE = /^-\s*\[[ xX]\]\s*\*{2}\[agent\]\*{2}/im;
2613
+ function checkNoAgentBoxes(prBody) {
2614
+ const region = testPlanRegion(prBody);
2615
+ if (!AGENT_BOX_LINE_RE.test(region))
2616
+ return { status: "pass", errors: [] };
2617
+ return {
2618
+ status: "fail",
2619
+ errors: [
2620
+ "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."
2621
+ ]
2622
+ };
2623
+ }
2624
+ function rolloutThresholdFor(message) {
2625
+ 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:")) {
2626
+ return BRIEF_RULES_SINCE_PR;
2627
+ }
2628
+ if (message.startsWith("brief-validation no agent boxes:"))
2629
+ return AGENT_BOXES_REFUSED_SINCE_PR;
2630
+ return null;
2631
+ }
2632
+ function partitionBriefErrorsByRollout(errors, prNumber) {
2633
+ const blocking = [];
2634
+ const info = [];
2635
+ for (const e of errors) {
2636
+ const threshold = rolloutThresholdFor(e);
2637
+ const grandfathered = threshold !== null && prNumber !== null && prNumber < threshold;
2638
+ (grandfathered ? info : blocking).push(e);
2639
+ }
2640
+ return { blocking, info };
2641
+ }
2642
+ function extractFencedBlocks(text) {
2643
+ const re = /^[ \t]*(`{3,}|~{3,})([^\n]*)\n([\s\S]*?)^[ \t]*\1[ \t]*$/gm;
2644
+ const blocks = [];
2645
+ let m = re.exec(text);
2646
+ while (m !== null) {
2647
+ const lang = m[2].trim().toLowerCase().split(/\s+/)[0] ?? "";
2648
+ blocks.push({ start: m.index, end: m.index + m[0].length, lang, content: m[3] });
2649
+ m = re.exec(text);
2650
+ }
2651
+ return blocks;
2652
+ }
2653
+ function firstNonBlankLine(text) {
2654
+ const line = text.split(`
2655
+ `).find((l) => l.trim().length > 0);
2656
+ return line?.trim() ?? "";
2657
+ }
2658
+ var FILE_LINE_RE = /\b[\w./-]+\.(?:tsx?|jsx?|mjs|cjs|md|mdx|json|ya?ml|sh|py|toml):\d+\b/g;
2659
+ function checkNoUnpinnedCodeClaims(prBody) {
2660
+ const premiseText = premiseBlockText(prBody);
2661
+ const withoutPremise = premiseText ? prBody.replace(premiseText, "") : prBody;
2662
+ const scanned = stripCode(withoutPremise, { inlineSpans: "keep" });
2663
+ const matches = scanned.match(FILE_LINE_RE) ?? [];
2664
+ if (matches.length === 0)
2665
+ return { status: "pass", errors: [] };
2666
+ return {
2667
+ status: "fail",
2668
+ 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.`)
2669
+ };
2670
+ }
2671
+ function extractNumberedSection(prBody, num) {
2672
+ const startRe = new RegExp(`^#{1,4}\\s*(?:\\*\\*)?${num}[a-z]?\\.\\s`, "im");
2673
+ const start = startRe.exec(prBody);
2674
+ if (!start)
2675
+ return null;
2676
+ const afterStart = start.index + start[0].length;
2677
+ const rest = prBody.slice(afterStart);
2678
+ const next = /^#{1,4}\s/m.exec(rest);
2679
+ return next ? rest.slice(0, next.index) : rest;
2680
+ }
2681
+ function commandWordOf(line) {
2682
+ return line.trim().split(/\s+/)[0] ?? "";
2683
+ }
2684
+ function isCommandBlock(content) {
2685
+ return COMMAND_WORDS.includes(commandWordOf(firstNonBlankLine(content)));
2686
+ }
2687
+ function isStep0Block(content) {
2688
+ return firstNonBlankLine(content).startsWith("git worktree add");
2689
+ }
2690
+ function checkCommandsCarryOutput(prBody) {
2691
+ const errors = [];
2692
+ for (const [label2, num] of [
2693
+ ["5", 5],
2694
+ ["6", 6]
2695
+ ]) {
2696
+ const section = extractNumberedSection(prBody, num);
2697
+ if (!section)
2698
+ continue;
2699
+ const blocks = extractFencedBlocks(section);
2700
+ for (let i = 0;i < blocks.length; i++) {
2701
+ const block = blocks[i];
2702
+ if (isStep0Block(block.content) || !isCommandBlock(block.content))
2703
+ continue;
2704
+ const next = blocks[i + 1];
2705
+ if (!next || isCommandBlock(next.content)) {
2706
+ 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.`);
2707
+ }
2708
+ }
2709
+ }
2710
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2711
+ }
2712
+ var CONSUMER_TESTS_SENTINEL_RE = /consumer-tests\s*:\s*none\s*[-—–]\s*\S/i;
2713
+ function packagesNamedIn(text) {
2714
+ const re = /\bpackages\/([A-Za-z0-9_-]+)\//g;
2715
+ const pkgs = new Set;
2716
+ let m = re.exec(text);
2717
+ while (m !== null) {
2718
+ pkgs.add(m[1]);
2719
+ m = re.exec(text);
2720
+ }
2721
+ return [...pkgs];
2722
+ }
2723
+ function hasTestPathForConsumer(text, consumerDir) {
2724
+ const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2725
+ const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2726
+ return re.test(text);
2727
+ }
2728
+ function checkConsumerTests(prBody, consumersOf) {
2729
+ const section4 = extractNumberedSection(prBody, 4);
2730
+ if (!section4)
2731
+ return { status: "pass", errors: [] };
2732
+ if (CONSUMER_TESTS_SENTINEL_RE.test(section4))
2733
+ return { status: "pass", errors: [] };
2734
+ const errors = [];
2735
+ for (const pkg of packagesNamedIn(section4)) {
2736
+ for (const consumerDir of consumersOf(pkg)) {
2737
+ if (hasTestPathForConsumer(section4, consumerDir))
2738
+ continue;
2739
+ 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>\`.`);
2740
+ }
2741
+ }
2742
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2743
+ }
2744
+ var CHECK_NAME_RE = /\bcheck-[a-z0-9-]+(?:\.ts)?\b/;
2745
+ 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;
2746
+ var DEFEAT_CASES_RE = /defeat cases\s*:/i;
2747
+ function checkDefeatCases(prBody) {
2748
+ const section4 = extractNumberedSection(prBody, 4);
2749
+ if (!section4)
2750
+ return { status: "pass", errors: [] };
2751
+ if (!CHECK_NAME_RE.test(section4) && !FORGE_WRITE_COMMAND_RE.test(section4))
2752
+ return { status: "pass", errors: [] };
2753
+ const section6 = extractNumberedSection(prBody, 6) ?? "";
2754
+ if (DEFEAT_CASES_RE.test(section6))
2755
+ return { status: "pass", errors: [] };
2756
+ return {
2757
+ status: "fail",
2758
+ errors: [
2759
+ "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."
2760
+ ]
2761
+ };
2762
+ }
2503
2763
  function checkBriefSections(prBody, readTier, options = {}) {
2504
- const { requireClosesN = true } = options;
2764
+ const { requireClosesN = true, consumersOf = () => [] } = options;
2505
2765
  const results = [
2506
2766
  checkTierField(prBody, readTier),
2507
2767
  checkTestPlan(prBody),
@@ -2514,10 +2774,219 @@ function checkBriefSections(prBody, readTier, options = {}) {
2514
2774
  checkAutonomyClause(prBody),
2515
2775
  checkProjectField(prBody),
2516
2776
  checkForField(prBody),
2777
+ checkNoUnpinnedCodeClaims(prBody),
2778
+ checkNoAgentBoxes(prBody),
2779
+ checkCommandsCarryOutput(prBody),
2780
+ checkConsumerTests(prBody, consumersOf),
2781
+ checkDefeatCases(prBody),
2517
2782
  ...requireClosesN ? [checkClosesN(prBody)] : []
2518
2783
  ];
2519
2784
  return { errors: results.flatMap((r) => r.errors) };
2520
2785
  }
2786
+ // ../../packages/aeg-core/src/doctrine-portability.ts
2787
+ var DEFAULT_SHIPS_PREFIX = "aeg-root/";
2788
+ var STATIC_PORTABLE_PREFIXES = [
2789
+ "roles/",
2790
+ "contracts/",
2791
+ "skills/",
2792
+ ".github/",
2793
+ ".vinaya/",
2794
+ ".claude/",
2795
+ ".git/",
2796
+ ".husky/"
2797
+ ];
2798
+ var EXEMPT_LITERALS = new Set([
2799
+ "path/inside/the/shipped/diff.ts",
2800
+ "path/inside/the/surface.ts",
2801
+ "apps/x/specs/..."
2802
+ ]);
2803
+ var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
2804
+ var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
2805
+ 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";
2806
+ var VENDOR_EXAMPLE_START = /<!--\s*AEG:VENDOR-EXAMPLE:START\s*-->/;
2807
+ var VENDOR_EXAMPLE_END = /<!--\s*AEG:VENDOR-EXAMPLE:END\s*-->/;
2808
+ function maskVendorExampleRegion(masked) {
2809
+ const start = VENDOR_EXAMPLE_START.exec(masked);
2810
+ if (!start)
2811
+ return masked;
2812
+ const afterStart = start.index + start[0].length;
2813
+ const end = VENDOR_EXAMPLE_END.exec(masked.slice(afterStart));
2814
+ if (!end)
2815
+ return masked;
2816
+ const regionEnd = afterStart + end.index + end[0].length;
2817
+ const region = masked.slice(start.index, regionEnd);
2818
+ const blanked = region.replace(/[^\n]/g, " ");
2819
+ return masked.slice(0, start.index) + blanked + masked.slice(regionEnd);
2820
+ }
2821
+ function extractVendorMentions(content) {
2822
+ const scoped = maskVendorExampleRegion(maskCode(content));
2823
+ const found = [];
2824
+ const pattern = new RegExp(VENDOR_NAME_SOURCE, "gi");
2825
+ let match = pattern.exec(scoped);
2826
+ while (match !== null) {
2827
+ found.push({ name: match[0], index: match.index });
2828
+ match = pattern.exec(scoped);
2829
+ }
2830
+ return found;
2831
+ }
2832
+ function extractCitedPaths(content) {
2833
+ const found = [];
2834
+ const spanPattern = /`([^`\n]+)`/g;
2835
+ let match = spanPattern.exec(content);
2836
+ while (match !== null) {
2837
+ const cited = match[1] ?? "";
2838
+ const topSegment = cited.slice(0, cited.indexOf("/"));
2839
+ if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
2840
+ found.push({ cited, index: match.index });
2841
+ }
2842
+ match = spanPattern.exec(content);
2843
+ }
2844
+ return found;
2845
+ }
2846
+ function lineAtIndex(content, index) {
2847
+ let line = 1;
2848
+ for (let i = 0;i < index; i++) {
2849
+ if (content.charCodeAt(i) === 10)
2850
+ line++;
2851
+ }
2852
+ return line;
2853
+ }
2854
+ function isPortable(cited, shipsPrefix) {
2855
+ if (cited.startsWith(shipsPrefix))
2856
+ return true;
2857
+ return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
2858
+ }
2859
+ function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
2860
+ const findings = [];
2861
+ for (const file of files) {
2862
+ if (!file.path.startsWith(shipsPrefix))
2863
+ continue;
2864
+ for (const { cited, index } of extractCitedPaths(file.content)) {
2865
+ if (EXEMPT_LITERALS.has(cited))
2866
+ continue;
2867
+ if (isPortable(cited, shipsPrefix))
2868
+ continue;
2869
+ findings.push({
2870
+ file: file.path,
2871
+ line: lineAtIndex(file.content, index),
2872
+ cited,
2873
+ message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`,
2874
+ kind: "path"
2875
+ });
2876
+ }
2877
+ for (const { name, index } of extractVendorMentions(file.content)) {
2878
+ findings.push({
2879
+ file: file.path,
2880
+ line: lineAtIndex(file.content, index),
2881
+ cited: name,
2882
+ 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`,
2883
+ kind: "vendor-name"
2884
+ });
2885
+ }
2886
+ }
2887
+ return findings;
2888
+ }
2889
+
2890
+ // ../../packages/aeg-core/src/doctrine-no-procedures.ts
2891
+ function vendorExampleSpan(content) {
2892
+ const start = VENDOR_EXAMPLE_START.exec(content);
2893
+ if (!start)
2894
+ return null;
2895
+ const afterStart = start.index + start[0].length;
2896
+ const end = VENDOR_EXAMPLE_END.exec(content.slice(afterStart));
2897
+ if (!end)
2898
+ return null;
2899
+ return { start: start.index, end: afterStart + end.index + end[0].length };
2900
+ }
2901
+ function isCommandLine(line) {
2902
+ const word = line.trim().split(/\s+/)[0] ?? "";
2903
+ return COMMAND_WORDS.includes(word);
2904
+ }
2905
+ var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
2906
+ function procedureLineNumberAt(content, index) {
2907
+ return content.slice(0, index).split(`
2908
+ `).length;
2909
+ }
2910
+ function isTemplatePath(path) {
2911
+ return /(^|\/)templates\//.test(path);
2912
+ }
2913
+ function checkDoctrineNoProcedures(files) {
2914
+ const findings = [];
2915
+ for (const file of files) {
2916
+ if (isTemplatePath(file.path))
2917
+ continue;
2918
+ const span = vendorExampleSpan(file.content);
2919
+ for (const block of extractFencedBlocks(file.content)) {
2920
+ if (span && block.start >= span.start && block.end <= span.end)
2921
+ continue;
2922
+ if (!SHELL_LANGS.has(block.lang))
2923
+ continue;
2924
+ const commandLineCount = block.content.split(`
2925
+ `).filter(isCommandLine).length;
2926
+ if (commandLineCount >= 2) {
2927
+ findings.push({
2928
+ file: file.path,
2929
+ line: procedureLineNumberAt(file.content, block.start),
2930
+ 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).`
2931
+ });
2932
+ }
2933
+ }
2934
+ }
2935
+ return findings;
2936
+ }
2937
+ // ../../packages/aeg-core/src/pr-report-density.ts
2938
+ function headingSectionBody(prBody, heading) {
2939
+ const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "im");
2940
+ const m = headingRe.exec(prBody);
2941
+ if (!m)
2942
+ return null;
2943
+ const start = m.index + m[0].length;
2944
+ const rest = prBody.slice(start);
2945
+ const nextHeading = /^#{1,2}\s/m.exec(rest);
2946
+ return nextHeading ? rest.slice(0, nextHeading.index) : rest;
2947
+ }
2948
+ function stripAnchorBlocks(sectionText) {
2949
+ let result = sectionText;
2950
+ for (const field of ["CLOSES", "PROJECT", "TIER", "PREMISE", "TEST-PLAN", "EVIDENCE"]) {
2951
+ for (;; ) {
2952
+ const bounds = anchoredRegionBounds(result, field);
2953
+ if (!bounds)
2954
+ break;
2955
+ result = result.slice(0, bounds.outerStart) + result.slice(bounds.outerEnd);
2956
+ }
2957
+ }
2958
+ return result;
2959
+ }
2960
+ function paragraphCount(sectionText) {
2961
+ const stripped = stripCode(stripAnchorBlocks(sectionText)).trim();
2962
+ if (stripped.length === 0)
2963
+ return 0;
2964
+ return stripped.split(/\n[ \t]*\n/).map((block) => block.trim()).filter((block) => block.length > 0).length;
2965
+ }
2966
+ function checkSectionDensity(prBody, heading) {
2967
+ const body = headingSectionBody(prBody, heading);
2968
+ if (body === null)
2969
+ return { status: "pass", errors: [] };
2970
+ const count = paragraphCount(body);
2971
+ if (count <= 1)
2972
+ return { status: "pass", errors: [] };
2973
+ return {
2974
+ status: "fail",
2975
+ errors: [
2976
+ `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.`
2977
+ ]
2978
+ };
2979
+ }
2980
+ function checkSummaryDensity(prBody) {
2981
+ return checkSectionDensity(prBody, "Summary");
2982
+ }
2983
+ function checkScopeDensity(prBody) {
2984
+ return checkSectionDensity(prBody, "Scope");
2985
+ }
2986
+ function checkPrReportDensity(prBody) {
2987
+ const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
2988
+ return { errors: results.flatMap((r) => r.errors) };
2989
+ }
2521
2990
  // ../../packages/aeg-core/src/issue-validation.ts
2522
2991
  function hasRationaleField(body, labelPattern) {
2523
2992
  const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
@@ -2769,6 +3238,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
2769
3238
  const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
2770
3239
  if (!depEntry)
2771
3240
  continue;
3241
+ const sameTask = depEntry.trancheSlug === e.trancheSlug && depEntry.task.id === e.task.id;
3242
+ const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3243
+ if (sameTask || sameIssue) {
3244
+ failures.push({
3245
+ issue: e.task.issue,
3246
+ tranche: e.trancheSlug,
3247
+ task: e.task.id,
3248
+ reason: `INTERNAL: parsed a self-dependency (depends-on ${dep}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream.`
3249
+ });
3250
+ continue;
3251
+ }
2772
3252
  const depFacts = depEntry.facts;
2773
3253
  const depClosed = depFacts?.issueState === "closed";
2774
3254
  if (!depClosed) {
@@ -2912,41 +3392,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2912
3392
  }
2913
3393
  return { ok: true, expectedIssue };
2914
3394
  }
2915
- // ../../packages/aeg-core/src/verdict-extraction.ts
2916
- var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2917
- function extractHeadSha(comment) {
2918
- const m = comment.match(HEAD_SHA_PATTERN);
2919
- return m ? m[1].toLowerCase() : null;
2920
- }
2921
- function extractVerdict(comments, valuePattern, missingLabel) {
2922
- const clearHits = comments.filter((c) => valuePattern.test(c));
2923
- if (clearHits.length > 0) {
2924
- const latest = clearHits[clearHits.length - 1];
2925
- const m = latest.match(valuePattern);
2926
- return {
2927
- value: m[1].toUpperCase().replace(/[_-]/g, " "),
2928
- headSha: extractHeadSha(latest),
2929
- danglingNote: null
2930
- };
2931
- }
2932
- return {
2933
- value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2934
- headSha: null,
2935
- danglingNote: `no ${missingLabel} verdict comment found on this PR`
2936
- };
2937
- }
2938
- function extractCodeReviewVerdict(comments) {
2939
- return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
2940
- }
2941
- function extractSecurityReviewVerdict(comments) {
2942
- return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2943
- }
2944
3395
  // ../../packages/aeg-core/src/review-gate.ts
2945
3396
  function isBoundToHead(extraction, headSha) {
2946
3397
  if (!extraction.headSha)
2947
3398
  return false;
2948
3399
  return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2949
3400
  }
3401
+ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3402
+ if (patchIdOf === undefined || !extraction.headSha)
3403
+ return false;
3404
+ const judged = patchIdOf(extraction.headSha);
3405
+ const current = patchIdOf(headSha);
3406
+ if (judged === null || current === null)
3407
+ return false;
3408
+ return judged === current;
3409
+ }
3410
+ function isBoundToPatch(extraction, headSha, patchIdOf) {
3411
+ return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3412
+ }
2950
3413
  function checkReviewGate(input) {
2951
3414
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
2952
3415
  const waived = isWaiverLabelActorVerified({
@@ -2962,6 +3425,8 @@ function checkReviewGate(input) {
2962
3425
  waived: true
2963
3426
  };
2964
3427
  }
3428
+ const reportedMechanicalChecks = input.mechanicalChecks.filter((c) => c.bucket !== "skipping" && c.bucket !== "neutral");
3429
+ const mechanicalChecksClean = reportedMechanicalChecks.length > 0 && reportedMechanicalChecks.every((c) => c.bucket === "pass");
2965
3430
  const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
2966
3431
  const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
2967
3432
  const verifiedBodies = verified.map((c) => c.body);
@@ -2969,12 +3434,12 @@ function checkReviewGate(input) {
2969
3434
  const security = extractSecurityReviewVerdict(verifiedBodies);
2970
3435
  const codeReviewClean = codeReview.value === "APPROVE";
2971
3436
  const securityClean = security.value === "PASS";
2972
- const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2973
- const securityBound = isBoundToHead(security, input.headSha);
2974
- if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
3437
+ const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3438
+ const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3439
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
2975
3440
  return {
2976
3441
  verdict: "pass",
2977
- reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
3442
+ 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.`,
2978
3443
  waived: false
2979
3444
  };
2980
3445
  }
@@ -2989,6 +3454,9 @@ function checkReviewGate(input) {
2989
3454
  } else if (!securityBound) {
2990
3455
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2991
3456
  }
3457
+ if (!mechanicalChecksClean) {
3458
+ 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(", ")}`);
3459
+ }
2992
3460
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2993
3461
  return {
2994
3462
  verdict: "fail",
@@ -3268,72 +3736,6 @@ function compareToBaseline(current, baseline) {
3268
3736
  perTool
3269
3737
  };
3270
3738
  }
3271
- // ../../packages/aeg-core/src/doctrine-portability.ts
3272
- var DEFAULT_SHIPS_PREFIX = "aeg-root/";
3273
- var STATIC_PORTABLE_PREFIXES = [
3274
- "roles/",
3275
- "contracts/",
3276
- "skills/",
3277
- ".github/",
3278
- ".vinaya/",
3279
- ".claude/",
3280
- ".git/",
3281
- ".husky/"
3282
- ];
3283
- var EXEMPT_LITERALS = new Set([
3284
- "path/inside/the/shipped/diff.ts",
3285
- "path/inside/the/surface.ts",
3286
- "apps/x/specs/..."
3287
- ]);
3288
- var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
3289
- var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
3290
- function extractCitedPaths(content) {
3291
- const found = [];
3292
- const spanPattern = /`([^`\n]+)`/g;
3293
- let match = spanPattern.exec(content);
3294
- while (match !== null) {
3295
- const cited = match[1] ?? "";
3296
- const topSegment = cited.slice(0, cited.indexOf("/"));
3297
- if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
3298
- found.push({ cited, index: match.index });
3299
- }
3300
- match = spanPattern.exec(content);
3301
- }
3302
- return found;
3303
- }
3304
- function lineAtIndex(content, index) {
3305
- let line = 1;
3306
- for (let i = 0;i < index; i++) {
3307
- if (content.charCodeAt(i) === 10)
3308
- line++;
3309
- }
3310
- return line;
3311
- }
3312
- function isPortable(cited, shipsPrefix) {
3313
- if (cited.startsWith(shipsPrefix))
3314
- return true;
3315
- return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
3316
- }
3317
- function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
3318
- const findings = [];
3319
- for (const file of files) {
3320
- if (!file.path.startsWith(shipsPrefix))
3321
- continue;
3322
- for (const { cited, index } of extractCitedPaths(file.content)) {
3323
- if (EXEMPT_LITERALS.has(cited))
3324
- continue;
3325
- if (isPortable(cited, shipsPrefix))
3326
- continue;
3327
- findings.push({
3328
- file: file.path,
3329
- line: lineAtIndex(file.content, index),
3330
- cited,
3331
- message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
3332
- });
3333
- }
3334
- }
3335
- return findings;
3336
- }
3337
3739
  // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3338
3740
  var SHIPS_PREFIX = "aeg-root/";
3339
3741
  function shipsArchivePrefix(shipsPrefix) {
@@ -3669,6 +4071,12 @@ function scanRetiredVocabulary(files) {
3669
4071
  function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
3670
4072
  return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
3671
4073
  }
4074
+ function isSelfDependency(dep, task, issue) {
4075
+ if (dep.issue !== null && issue !== null && dep.issue === issue.number)
4076
+ return true;
4077
+ const bare = dep.id.trim().replace(/^#/, "");
4078
+ return bare.toLowerCase() === task.id.trim().toLowerCase();
4079
+ }
3672
4080
  function checkDispatchReadiness(input) {
3673
4081
  const { trancheSlug, task } = input;
3674
4082
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
@@ -3683,6 +4091,11 @@ function checkDispatchReadiness(input) {
3683
4091
  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.`);
3684
4092
  }
3685
4093
  for (const dep of input.dependsOn) {
4094
+ if (isSelfDependency(dep, task, input.issue)) {
4095
+ const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4096
+ 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.`);
4097
+ continue;
4098
+ }
3686
4099
  if (dep.resolved === false) {
3687
4100
  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.`);
3688
4101
  continue;
@@ -3747,8 +4160,12 @@ function isNewDiskStateFile(path, status) {
3747
4160
  return ANY_DEPTH_TRANCHES_MD.test(path) || TOKENS_FILE.test(path);
3748
4161
  }
3749
4162
  // ../../packages/aeg-core/src/main-branch-refusal.ts
4163
+ function pushesAnyBranchRef(pushRefs) {
4164
+ return pushRefs.split(`
4165
+ `).map((line) => line.trim()).filter((line) => line.length > 0).some((line) => line.split(/\s+/)[2]?.startsWith("refs/heads/") === true);
4166
+ }
3750
4167
  function checkMainBranchRefusal(facts) {
3751
- const { currentSymbolicBranch, defaultBranch } = facts;
4168
+ const { currentSymbolicBranch, defaultBranch, pushRefs = null } = facts;
3752
4169
  if (currentSymbolicBranch === null)
3753
4170
  return null;
3754
4171
  if (defaultBranch === null) {
@@ -3760,6 +4177,8 @@ function checkMainBranchRefusal(facts) {
3760
4177
  };
3761
4178
  }
3762
4179
  if (currentSymbolicBranch === defaultBranch) {
4180
+ if (pushRefs !== null && !pushesAnyBranchRef(pushRefs))
4181
+ return null;
3763
4182
  return {
3764
4183
  reason: "on-default-branch",
3765
4184
  severity: "error",
@@ -3922,27 +4341,27 @@ function evaluateTestPlanGate(body, branch) {
3922
4341
  };
3923
4342
  }
3924
4343
  const checkboxLines = section.split(`
3925
- `).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]/.test(line));
4344
+ `).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
3926
4345
  if (checkboxLines.length === 0) {
3927
4346
  return {
3928
4347
  verdict: "pass",
3929
4348
  messages: [
3930
- "Test Plan section found but has no checkbox items.",
4349
+ "Test Plan section found but has no `[principal]` checkbox items.",
3931
4350
  "PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
3932
4351
  ]
3933
4352
  };
3934
4353
  }
3935
4354
  const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
3936
4355
  const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
3937
- const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
4356
+ const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
3938
4357
  if (unchecked.length > 0) {
3939
- messages.push("", "FAIL — the following Test Plan items are unticked:");
4358
+ messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
3940
4359
  for (const line of unchecked)
3941
4360
  messages.push(` ${line}`);
3942
- 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.");
4361
+ 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.");
3943
4362
  return { verdict: "fail", messages };
3944
4363
  }
3945
- messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
4364
+ messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
3946
4365
  return { verdict: "pass", messages };
3947
4366
  }
3948
4367
  // ../../packages/aeg-core/src/workspace-escape.ts
@@ -4127,6 +4546,19 @@ var COMMANDS = [
4127
4546
  ],
4128
4547
  status: "shipped"
4129
4548
  },
4549
+ {
4550
+ name: "brief render",
4551
+ description: "Emit the twelve-section brief skeleton from the forge and the tree, every derivable section filled",
4552
+ flags: [
4553
+ { flag: "--surfaces <glob1,glob2,...>", description: "Intended surface globs, expanded against tracked files" },
4554
+ { flag: "--out <path>", description: "Write the rendered brief to a file instead of stdout" }
4555
+ ],
4556
+ details: [
4557
+ "Reads the task Issue (`vinaya/tranche:<tranche>`-labeled, id `<n>`) and the tree, and fills every section a program can derive: the header `Project:`/`Tier:`/`Closes #N`, the Step 0 worktree line, the dispatch-gate status as the pre-flight line, §4's file list (with consumer packages and a `sha256` premise pin per file), §7 from the `.vinaya/doc-owners` derivation, and every remaining section from the Issue's eight-field Planner rationale. Refuses — naming the missing fact — when a derived section cannot be derived: no Issue, the dispatch gate not clear, or a `--surfaces` glob matching no tracked file.",
4558
+ "Never writes under `aeg-root/` or to the Issue — a brief is pasted to the Developer, never committed. Reads `aeg-root/templates/brief-template.md` at run time for its one fixed sentence and the standing autonomy clause; every other byte is generated from the forge/tree facts."
4559
+ ],
4560
+ status: "shipped"
4561
+ },
4130
4562
  {
4131
4563
  name: "pr create",
4132
4564
  description: "Open a pull request after full brief-schema validation",
@@ -4166,11 +4598,16 @@ var COMMANDS = [
4166
4598
  {
4167
4599
  flag: "--write",
4168
4600
  description: "Path to the PR body file; replaces the content between the AEG:EVIDENCE anchors in place"
4601
+ },
4602
+ {
4603
+ flag: "--push",
4604
+ description: "PR number; splices the same content into that PR's LIVE body (fetched from the forge) and pushes it via `gh pr edit`, self-verifying nothing outside the AEG:EVIDENCE/AEG:TOKENS regions changed. Mutually exclusive with --write."
4169
4605
  }
4170
4606
  ],
4171
4607
  details: [
4172
4608
  "Two groups: Group A (recomputable) is the head sha and a width-invariant `git diff --numstat` against the merge-base with `BASE_SHA` (else `origin/main`, then `main`) — `check-evidence-fresh` recomputes and byte-compares this exactly. Group B (attested) is the result of `vinaya check --all --diff-only`, this CLI's own portable gate suite — `check-evidence-fresh` can only check it for staleness (the block's recorded head still matches the PR's real head), never re-run it.",
4173
- "With no `--write`, prints the block to stdout instead of writing a file. Exits non-zero whenever the gate run failed, whether or not `--write` was given — the block records a failing result rather than hiding one, and a non-zero exit stops a scripted `--write && open-pr` from carrying a failing suite onto the forge."
4609
+ "With no `--write`/`--push`, prints the block to stdout instead of writing a file. Exits non-zero whenever the gate run failed, whether or not `--write`/`--push` was given — the block records a failing result rather than hiding one, and a non-zero exit stops a scripted `--write && open-pr` (or `--push`) from carrying a failing suite onto the forge.",
4610
+ "`--push` refuses before writing anything when the live body carries no real `AEG:EVIDENCE` pair (it never appends one, unlike `--write`), when the anchor resolves differently before and after normalisation, or when `<pr>` isn't a bare number. When the live body has no real `AEG:TOKENS` pair, the token splice is skipped (same reasoning, softer outcome) rather than creating one. After pushing, it re-reads the live body and restores the pre-edit body — exiting non-zero — if anything outside the two anchored regions changed."
4174
4611
  ],
4175
4612
  status: "shipped"
4176
4613
  },
@@ -4275,6 +4712,16 @@ var COMMANDS = [
4275
4712
  ],
4276
4713
  status: "shipped"
4277
4714
  },
4715
+ {
4716
+ name: "review status",
4717
+ description: "Print the review loop's own state for a PR, and its branch's distance from the base",
4718
+ details: [
4719
+ "Two lines at most. The first is `CONTINUE`, `PAUSE: <reason>[ <id>]` for `reappearance`, `zero-deaths` or `max-rounds` — or, for `stale`, the actionable fact itself: `push after verdict — re-review required`. The second reads `behind main by <n> — merge first` when the branch is behind its base, or `behind main: unknown — fetch origin/<base> first` when git cannot measure the distance; it is absent only when the branch is measurably not behind.",
4720
+ "Rounds are derived from the PR's own verdict comments — one round per `Judged head:` value, read through the same extractors the merge gate blocks on, and only from comments an allowlisted principal authored. A Developer round comment is recognised by its `<!-- aeg:developer:round-<n> -->` marker, at that fixed position, never by scanning its prose.",
4721
+ "Exit `0` only when the state is `CONTINUE` and the branch is not behind; `1` otherwise, so a script can gate on the exit code without parsing the text."
4722
+ ],
4723
+ status: "shipped"
4724
+ },
4278
4725
  {
4279
4726
  name: "review post",
4280
4727
  description: "Render, post, and self-verify a code-reviewer or security-review verdict comment on a PR",
@@ -4286,15 +4733,24 @@ var COMMANDS = [
4286
4733
  },
4287
4734
  {
4288
4735
  flag: "--verdict",
4289
- description: "code-reviewer: `APPROVE` | `REQUEST_CHANGES`. security: `PASS` | `FAIL`"
4736
+ description: "Optional — derived from the findings file (code-reviewer: `APPROVE`/`REQUEST_CHANGES`; security: `PASS`/`FAIL`). Refused before posting if it disagrees with the derivation."
4737
+ },
4738
+ {
4739
+ flag: "--escalate",
4740
+ description: "`authority` | `strategy` | `product` — posts an `ESCALATE:` comment instead of a verdict. Refused together with `--verdict` or a blocking finding."
4290
4741
  },
4742
+ { flag: "--summary", description: "Required with `--escalate` — the escalation body text" },
4291
4743
  {
4292
4744
  flag: "--findings-file",
4293
- description: "One finding per line: `SEVERITY|file:line|description` (`|`-delimited). Omit for zero findings."
4745
+ description: "One finding per line: `SEVERITY|file:line|description` (`|`-delimited). A re-review names a prior id in the description as `F<n> <class> <state>:`. Omit for zero findings."
4294
4746
  },
4295
4747
  { flag: "--brief-conformance", description: "code-reviewer only: the BRIEF CONFORMANCE line" },
4296
4748
  { flag: "--spec-conformance", description: "code-reviewer only: the SPEC CONFORMANCE line" },
4297
4749
  { flag: "--scope", description: "code-reviewer only: the SCOPE line" },
4750
+ {
4751
+ flag: "--scope-evidence-file",
4752
+ description: "code-reviewer only: pasted diff-stat output, rendered as a fence directly below the verdict block"
4753
+ },
4298
4754
  { flag: "--tests", description: "code-reviewer only: the TESTS line" },
4299
4755
  { flag: "--docs", description: "code-reviewer only: the DOCS line" },
4300
4756
  { flag: "--config-scan", description: "security only: the CONFIG SCAN line" },
@@ -4308,12 +4764,18 @@ var COMMANDS = [
4308
4764
  { flag: "--tokens-in", description: "a non-negative integer, or `-` if unknown" },
4309
4765
  { flag: "--tokens-out", description: "a non-negative integer, or `-` if unknown" },
4310
4766
  { flag: "--cost", description: "free text, or `-` if unknown" },
4767
+ {
4768
+ flag: "--print-only",
4769
+ description: "Render and self-check the comment, print it, and exit — never calls `gh pr comment`"
4770
+ },
4311
4771
  { flag: "--json", description: "Enveloped JSON output (schema: 1)" }
4312
4772
  ],
4313
4773
  details: [
4774
+ "`--print-only` runs the exact same render-then-self-check path as a real post, then returns before `gh pr comment` — closes atta-labs/vinaya#184, where the old command silently posted a verdict anyway after a reviewer guessed this flag existed.",
4314
4775
  "Every structural line (`VERDICT:`, `Judged head:`) is rendered from this command's own validated enum/sha inputs — never from a caller-supplied string — so a Reviewer's free-typed prose can no longer produce a shape the merge gate's line-anchored regex fails to see.",
4315
- "Refuses a contradictory verdict before posting anything: a BLOCKER finding with `--verdict APPROVE`, or a CRITICAL/HIGH finding with `--verdict PASS`, is rejected outright, mirroring each role doc's own consistency rule.",
4316
- "After posting, re-fetches the PR's comments and runs them through the exact `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` functions `checkReviewGate` calls the same functions, not a second implementation and exits non-zero naming precisely what failed to re-parse if the post does not come back clean and bound to the resolved head. There is no `--skip-verify` escape."
4776
+ "The verdict is derived, not typed: a BLOCKER (or CRITICAL/HIGH) finding forces REQUEST_CHANGES/FAIL and its absence forces APPROVE/PASS, before posting anything — an explicit `--verdict` that disagrees is refused naming the derived value. When a same-role verdict comment already exists on the PR, a new findings file must carry every prior id with a state and no non-blocking finding outside the diff since that comment's judged head, or the post is refused.",
4777
+ "Before the post ever reaches the forge, runs the exact `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` functions `checkReviewGate` calls over its own rendered text and refuses (exit 2) unless exactly the intended verdict extracts and the other role extracts none an escalation requires both to extract none. Both extractors read only a comment's first three lines, which are always this command's own structural lines, so no caller-supplied field can smuggle a line the gate would misread.",
4778
+ "After posting, re-fetches the PR's comments and runs them through the same extractors `checkReviewGate` calls — the same functions, not a second implementation — and exits non-zero naming precisely what failed to re-parse if the post does not come back clean, bound to the resolved head, and free of the other role's verdict. There is no `--skip-verify` escape."
4317
4779
  ],
4318
4780
  status: "shipped"
4319
4781
  },
@@ -4366,6 +4828,9 @@ var COMMANDS = [
4366
4828
  { flag: "--dry-run", description: "Print the full diff without regenerating anything" },
4367
4829
  { flag: "--yes", description: "Skip the confirmation prompt" }
4368
4830
  ],
4831
+ details: [
4832
+ "When `claude` is a selected agent vendor, also retrofits the Claude Code `Stop` hook (`.claude/hooks/track-transcript.sh`, `.claude/settings.json`) the same way `init` installs it on a fresh repo — for a repo that ran `init` before this hook existed, closing the gap that left token rows reading `—/—/—` with no operator ever told to re-run `init`. Never overwrites a foreign `.claude/settings.json` (refuse-if-foreign, same as `init`); appends to a foreign Stop-hook script rather than replacing it (managed-block, same discipline as the git hooks)."
4833
+ ],
4369
4834
  status: "shipped"
4370
4835
  },
4371
4836
  {
@@ -4459,6 +4924,26 @@ var COMMANDS = [
4459
4924
  "Never reimplements or edits `init`/`init product`/`demo break`/`doctor` — it only calls their existing, unmodified entry points in sequence."
4460
4925
  ],
4461
4926
  status: "shipped"
4927
+ },
4928
+ {
4929
+ name: "release",
4930
+ description: "Run this repo's own publish sequence in one command, refusing to start unless every precondition holds",
4931
+ flags: [
4932
+ {
4933
+ flag: "--dry-run",
4934
+ description: "Run the preconditions only and print the plan; publishes nothing"
4935
+ },
4936
+ {
4937
+ flag: "--allow-any-commit",
4938
+ description: "Skip the check that HEAD's commit is a Version Packages commit"
4939
+ }
4940
+ ],
4941
+ details: [
4942
+ "Refuses unless HEAD is the default branch, the working tree is clean, HEAD equals `origin/<default>` (after `git fetch origin`), HEAD's commit subject starts with `Chore(release): Version packages` (unless `--allow-any-commit`), and `npm whoami` exits `0` — each its own refusal naming the fix.",
4943
+ "Then streams `bun install --frozen-lockfile`, `bun run build`, `bun run changeset:publish`, and `git push origin --tags` — a real push, so the repo's own generated pre-push hook sees it exactly as any other push would. After it, prints `npm view <pkg> version` for every tag now on HEAD, noting registry lag on `@attalabs/vinaya` (observed ~20 minutes) when it still shows the previous version.",
4944
+ 'This is the one procedure named in `apps/cli/specs/self-hosting.md`, "How the published version is produced" — publishing itself stays manual and human-triggered; this command only removes the hand-typed four-step recipe.'
4945
+ ],
4946
+ status: "shipped"
4462
4947
  }
4463
4948
  ];
4464
4949
  // ../../packages/sources/src/forge-adapter.ts
@@ -4851,7 +5336,10 @@ function taskToEntry(entries, slug) {
4851
5336
  m.set(`${slug}/${e.task.id}`, e);
4852
5337
  return m;
4853
5338
  }
4854
- function recoveryPromptFor(checkCode) {
5339
+ function recoveryPromptFor(checkCode, detail = "") {
5340
+ if (detail.includes("INTERNAL:")) {
5341
+ return "This is an INTERNAL parser-bug report, not a real coherence failure — a task cannot depend on itself, so there is nothing here to close or resolve. Do NOT work around it. Report it upstream, and re-run once the rationale text is corrected or the parser fix ships.";
5342
+ }
4855
5343
  switch (checkCode) {
4856
5344
  case "A1":
4857
5345
  return "The task's Issue is closed but its closing PR is not merged. Verify the PR actually merged (or reopen the Issue if it was closed in error), then re-run `vinaya check coherence`.";
@@ -4878,7 +5366,7 @@ function emitFailure(result) {
4878
5366
  check: CHECK_NAME,
4879
5367
  severity: "error",
4880
5368
  message: `${result.check}: ${detail}`,
4881
- agent_recovery_prompt: recoveryPromptFor(result.check)
5369
+ agent_recovery_prompt: recoveryPromptFor(result.check, detail)
4882
5370
  });
4883
5371
  }
4884
5372
  async function main() {
@@ -4955,4 +5443,9 @@ async function main() {
4955
5443
  }
4956
5444
  process.exit(0);
4957
5445
  }
4958
- main();
5446
+ if (__require.main == __require.module) {
5447
+ main();
5448
+ }
5449
+ export {
5450
+ recoveryPromptFor
5451
+ };