@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,8 +1,10 @@
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-quoted-command.ts
4
6
  import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "node:fs";
5
- import { join as join5, relative, resolve as resolve2 } from "node:path";
7
+ import { join as join5, relative as relative2, resolve as resolve3 } from "node:path";
6
8
  // ../../packages/aeg-core/src/gate-audience.ts
7
9
  var GATE_AUDIENCE = {
8
10
  "check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
@@ -211,17 +213,18 @@ function splitIds(raw) {
211
213
  return [];
212
214
  return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
213
215
  }
214
- function resolveIds(raw, lastSlug) {
216
+ function resolveIds(raw) {
215
217
  const ids = splitIds(raw);
216
218
  const resolved = [];
219
+ let lastSlug = null;
217
220
  for (const id of ids) {
218
221
  const qualified = id.match(SLUG_QUALIFIED_ID);
219
222
  if (qualified) {
220
- lastSlug.current = qualified[1] ?? null;
223
+ lastSlug = qualified[1] ?? null;
221
224
  resolved.push(id);
222
- } else if (lastSlug.current) {
223
- resolved.push(`${lastSlug.current} ${id}`);
224
- lastSlug.current = null;
225
+ } else if (lastSlug) {
226
+ resolved.push(`${lastSlug} ${id}`);
227
+ lastSlug = null;
225
228
  } else {
226
229
  resolved.push(id);
227
230
  }
@@ -241,12 +244,7 @@ function parseRationaleDeps(body) {
241
244
  const section = extractSection(body);
242
245
  const dependsOn = [];
243
246
  const conflictsWith = [];
244
- let current = null;
245
247
  const assignedFields = new Set;
246
- const lastSlugByField = {
247
- dependsOn: { current: null },
248
- conflictsWith: { current: null }
249
- };
250
248
  const spanPattern = /`([^`]*)`/g;
251
249
  let match = spanPattern.exec(section);
252
250
  while (match !== null) {
@@ -256,13 +254,9 @@ function parseRationaleDeps(body) {
256
254
  const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
257
255
  if (!assignedFields.has(field)) {
258
256
  assignedFields.add(field);
259
- current = field;
260
- const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
257
+ const ids = resolveIds(labelMatch[2] ?? "");
261
258
  pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
262
259
  }
263
- } else if (current) {
264
- const ids = resolveIds(content, lastSlugByField[current]);
265
- pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
266
260
  }
267
261
  match = spanPattern.exec(section);
268
262
  }
@@ -1727,6 +1721,48 @@ function splitTableRow(row) {
1727
1721
  parts.pop();
1728
1722
  return parts.map((p) => p.split(PLACEHOLDER).join("|"));
1729
1723
  }
1724
+ // ../../packages/aeg-core/src/blast-radius-domains.ts
1725
+ function resolveWorkspaceEntry(entry2, listDirs) {
1726
+ if (entry2.endsWith("/*")) {
1727
+ const baseDir = entry2.slice(0, -2);
1728
+ return listDirs(baseDir).map((name) => `${baseDir}/${name}`);
1729
+ }
1730
+ if (entry2.includes("*"))
1731
+ return [];
1732
+ return [entry2];
1733
+ }
1734
+ // ../../packages/aeg-core/src/consumer-enumeration.ts
1735
+ function deriveWorkspaceMemberDirs(workspaces, listDirs) {
1736
+ const positive = workspaces.filter((entry2) => !entry2.startsWith("!"));
1737
+ const negative = workspaces.filter((entry2) => entry2.startsWith("!")).map((entry2) => entry2.slice(1));
1738
+ const excluded = new Set;
1739
+ for (const entry2 of negative) {
1740
+ for (const member of resolveWorkspaceEntry(entry2, listDirs))
1741
+ excluded.add(member);
1742
+ }
1743
+ const members = new Set;
1744
+ for (const entry2 of positive) {
1745
+ for (const member of resolveWorkspaceEntry(entry2, listDirs)) {
1746
+ if (!excluded.has(member))
1747
+ members.add(member);
1748
+ }
1749
+ }
1750
+ return [...members].sort();
1751
+ }
1752
+ function buildConsumersOf(workspaces, listDirs, readManifest) {
1753
+ const members = deriveWorkspaceMemberDirs(workspaces, listDirs);
1754
+ return (pkg) => {
1755
+ const target = `@attalabs/${pkg}`;
1756
+ return members.filter((dir) => {
1757
+ if (dir === `packages/${pkg}`)
1758
+ return false;
1759
+ const manifest = readManifest(dir);
1760
+ if (!manifest)
1761
+ return false;
1762
+ return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {});
1763
+ });
1764
+ };
1765
+ }
1730
1766
  // ../../packages/aeg-core/src/state-machine-model.ts
1731
1767
  var FORGE_FACT_INPUTS = [
1732
1768
  {
@@ -1831,6 +1867,22 @@ function isEmDashOrDash(s) {
1831
1867
  const t = s.trim();
1832
1868
  return t === "—" || t === "-" || t === "–";
1833
1869
  }
1870
+ // ../../packages/aeg-core/src/waiver-label.ts
1871
+ var WAIVER_LABEL = label("waiver-docs");
1872
+ var WAIVER_LABEL_REVIEW = label("waiver-review");
1873
+ var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
1874
+ function isPrincipal(login, principalAllowlist) {
1875
+ if (login === null)
1876
+ return false;
1877
+ const normalized = login.toLowerCase();
1878
+ return principalAllowlist.some((p) => p.toLowerCase() === normalized);
1879
+ }
1880
+ function isWaiverLabelActorVerified(opts) {
1881
+ if (!opts.labels.includes(opts.label))
1882
+ return false;
1883
+ return isPrincipal(opts.labelActor, opts.principalAllowlist);
1884
+ }
1885
+
1834
1886
  // ../../packages/aeg-core/src/parse-token-report.ts
1835
1887
  var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
1836
1888
  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;
@@ -1885,6 +1937,47 @@ function parseInlineFieldList(section) {
1885
1937
  return null;
1886
1938
  return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
1887
1939
  }
1940
+ // ../../packages/aeg-core/src/verdict-extraction.ts
1941
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1942
+ function firstThreeLines(comment) {
1943
+ return comment.split(`
1944
+ `).slice(0, 3).join(`
1945
+ `);
1946
+ }
1947
+ function extractHeadSha(comment) {
1948
+ const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
1949
+ return m ? m[1].toLowerCase() : null;
1950
+ }
1951
+ function extractVerdict(comments, valuePattern, missingLabel) {
1952
+ const candidates = comments.filter((c) => valuePattern.test(c));
1953
+ if (candidates.length === 0) {
1954
+ return {
1955
+ value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1956
+ headSha: null,
1957
+ danglingNote: `no ${missingLabel} verdict comment found on this PR`
1958
+ };
1959
+ }
1960
+ const latest = candidates[candidates.length - 1];
1961
+ const m = firstThreeLines(latest).match(valuePattern);
1962
+ if (!m) {
1963
+ return {
1964
+ value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
1965
+ headSha: null,
1966
+ danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
1967
+ };
1968
+ }
1969
+ return {
1970
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
1971
+ headSha: extractHeadSha(latest),
1972
+ danglingNote: null
1973
+ };
1974
+ }
1975
+ function extractCodeReviewVerdict(comments) {
1976
+ return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
1977
+ }
1978
+ function extractSecurityReviewVerdict(comments) {
1979
+ return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
1980
+ }
1888
1981
  // ../../packages/aeg-core/src/report-tokens.ts
1889
1982
  var DASH_LOOKALIKES = {
1890
1983
  "-": "‑",
@@ -2049,22 +2142,6 @@ function isCodeFile(p) {
2049
2142
  return false;
2050
2143
  return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
2051
2144
  }
2052
- // ../../packages/aeg-core/src/waiver-label.ts
2053
- var WAIVER_LABEL = label("waiver-docs");
2054
- var WAIVER_LABEL_REVIEW = label("waiver-review");
2055
- var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
2056
- function isPrincipal(login, principalAllowlist) {
2057
- if (login === null)
2058
- return false;
2059
- const normalized = login.toLowerCase();
2060
- return principalAllowlist.some((p) => p.toLowerCase() === normalized);
2061
- }
2062
- function isWaiverLabelActorVerified(opts) {
2063
- if (!opts.labels.includes(opts.label))
2064
- return false;
2065
- return isPrincipal(opts.labelActor, opts.principalAllowlist);
2066
- }
2067
-
2068
2145
  // ../../packages/aeg-core/src/doc-owners.ts
2069
2146
  var DOC_OWNERS_PATH = ".vinaya/doc-owners";
2070
2147
  function parseDocOwners(content) {
@@ -2281,6 +2358,32 @@ function parsePremiseBlock(prBody) {
2281
2358
  }
2282
2359
  return assertions;
2283
2360
  }
2361
+ function premiseBlockText(prBody) {
2362
+ const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
2363
+ const lines = searchIn.split(/\r?\n/);
2364
+ let startIdx = -1;
2365
+ let endIdxExclusive = lines.length;
2366
+ for (let i = 0;i < lines.length; i++) {
2367
+ const line = lines[i].trim();
2368
+ if (startIdx === -1) {
2369
+ if (isPremiseHeader(line))
2370
+ startIdx = i;
2371
+ continue;
2372
+ }
2373
+ if (line === "") {
2374
+ endIdxExclusive = i;
2375
+ break;
2376
+ }
2377
+ if (!PREMISE_LINE.test(line)) {
2378
+ endIdxExclusive = i;
2379
+ break;
2380
+ }
2381
+ }
2382
+ if (startIdx === -1)
2383
+ return null;
2384
+ return lines.slice(startIdx, endIdxExclusive).join(`
2385
+ `);
2386
+ }
2284
2387
  function checkPremises(assertions, fileReader) {
2285
2388
  const failures = [];
2286
2389
  for (const a of assertions) {
@@ -2367,15 +2470,18 @@ function testPlanRegion(prBody) {
2367
2470
  return located.found ? located.section : prBody;
2368
2471
  }
2369
2472
  function checkTestPlan(prBody) {
2370
- const region = testPlanRegion(prBody);
2473
+ const located = locateTestPlanSection(prBody);
2474
+ const region = located.found ? located.section : prBody;
2371
2475
  if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
2372
2476
  return { status: "pass", errors: [] };
2373
2477
  if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
2374
2478
  return { status: "pass", errors: [] };
2479
+ if (located.found && extractFencedBlocks(region).length > 0)
2480
+ return { status: "pass", errors: [] };
2375
2481
  return {
2376
2482
  status: "fail",
2377
2483
  errors: [
2378
- "brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
2484
+ "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."
2379
2485
  ]
2380
2486
  };
2381
2487
  }
@@ -2498,8 +2604,162 @@ function isBriefShaped(prBody) {
2498
2604
  const stripped = stripCode(prBody);
2499
2605
  return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
2500
2606
  }
2607
+ var COMMAND_WORDS = ["export", "bun", "gh", "git", "grep", "sed", "cat", "diff", "vinaya"];
2608
+ var BRIEF_RULES_SINCE_PR = 394;
2609
+ var AGENT_BOXES_REFUSED_SINCE_PR = 396;
2610
+ var AGENT_BOX_LINE_RE = /^-\s*\[[ xX]\]\s*\*{2}\[agent\]\*{2}/im;
2611
+ function checkNoAgentBoxes(prBody) {
2612
+ const region = testPlanRegion(prBody);
2613
+ if (!AGENT_BOX_LINE_RE.test(region))
2614
+ return { status: "pass", errors: [] };
2615
+ return {
2616
+ status: "fail",
2617
+ errors: [
2618
+ "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."
2619
+ ]
2620
+ };
2621
+ }
2622
+ function rolloutThresholdFor(message) {
2623
+ 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:")) {
2624
+ return BRIEF_RULES_SINCE_PR;
2625
+ }
2626
+ if (message.startsWith("brief-validation no agent boxes:"))
2627
+ return AGENT_BOXES_REFUSED_SINCE_PR;
2628
+ return null;
2629
+ }
2630
+ function partitionBriefErrorsByRollout(errors, prNumber) {
2631
+ const blocking = [];
2632
+ const info = [];
2633
+ for (const e of errors) {
2634
+ const threshold = rolloutThresholdFor(e);
2635
+ const grandfathered = threshold !== null && prNumber !== null && prNumber < threshold;
2636
+ (grandfathered ? info : blocking).push(e);
2637
+ }
2638
+ return { blocking, info };
2639
+ }
2640
+ function extractFencedBlocks(text) {
2641
+ const re = /^[ \t]*(`{3,}|~{3,})([^\n]*)\n([\s\S]*?)^[ \t]*\1[ \t]*$/gm;
2642
+ const blocks = [];
2643
+ let m = re.exec(text);
2644
+ while (m !== null) {
2645
+ const lang = m[2].trim().toLowerCase().split(/\s+/)[0] ?? "";
2646
+ blocks.push({ start: m.index, end: m.index + m[0].length, lang, content: m[3] });
2647
+ m = re.exec(text);
2648
+ }
2649
+ return blocks;
2650
+ }
2651
+ function firstNonBlankLine(text) {
2652
+ const line = text.split(`
2653
+ `).find((l) => l.trim().length > 0);
2654
+ return line?.trim() ?? "";
2655
+ }
2656
+ var FILE_LINE_RE = /\b[\w./-]+\.(?:tsx?|jsx?|mjs|cjs|md|mdx|json|ya?ml|sh|py|toml):\d+\b/g;
2657
+ function checkNoUnpinnedCodeClaims(prBody) {
2658
+ const premiseText = premiseBlockText(prBody);
2659
+ const withoutPremise = premiseText ? prBody.replace(premiseText, "") : prBody;
2660
+ const scanned = stripCode(withoutPremise, { inlineSpans: "keep" });
2661
+ const matches = scanned.match(FILE_LINE_RE) ?? [];
2662
+ if (matches.length === 0)
2663
+ return { status: "pass", errors: [] };
2664
+ return {
2665
+ status: "fail",
2666
+ 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.`)
2667
+ };
2668
+ }
2669
+ function extractNumberedSection(prBody, num) {
2670
+ const startRe = new RegExp(`^#{1,4}\\s*(?:\\*\\*)?${num}[a-z]?\\.\\s`, "im");
2671
+ const start = startRe.exec(prBody);
2672
+ if (!start)
2673
+ return null;
2674
+ const afterStart = start.index + start[0].length;
2675
+ const rest = prBody.slice(afterStart);
2676
+ const next = /^#{1,4}\s/m.exec(rest);
2677
+ return next ? rest.slice(0, next.index) : rest;
2678
+ }
2679
+ function commandWordOf(line) {
2680
+ return line.trim().split(/\s+/)[0] ?? "";
2681
+ }
2682
+ function isCommandBlock(content) {
2683
+ return COMMAND_WORDS.includes(commandWordOf(firstNonBlankLine(content)));
2684
+ }
2685
+ function isStep0Block(content) {
2686
+ return firstNonBlankLine(content).startsWith("git worktree add");
2687
+ }
2688
+ function checkCommandsCarryOutput(prBody) {
2689
+ const errors = [];
2690
+ for (const [label2, num] of [
2691
+ ["5", 5],
2692
+ ["6", 6]
2693
+ ]) {
2694
+ const section = extractNumberedSection(prBody, num);
2695
+ if (!section)
2696
+ continue;
2697
+ const blocks = extractFencedBlocks(section);
2698
+ for (let i = 0;i < blocks.length; i++) {
2699
+ const block = blocks[i];
2700
+ if (isStep0Block(block.content) || !isCommandBlock(block.content))
2701
+ continue;
2702
+ const next = blocks[i + 1];
2703
+ if (!next || isCommandBlock(next.content)) {
2704
+ 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.`);
2705
+ }
2706
+ }
2707
+ }
2708
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2709
+ }
2710
+ var CONSUMER_TESTS_SENTINEL_RE = /consumer-tests\s*:\s*none\s*[-—–]\s*\S/i;
2711
+ function packagesNamedIn(text) {
2712
+ const re = /\bpackages\/([A-Za-z0-9_-]+)\//g;
2713
+ const pkgs = new Set;
2714
+ let m = re.exec(text);
2715
+ while (m !== null) {
2716
+ pkgs.add(m[1]);
2717
+ m = re.exec(text);
2718
+ }
2719
+ return [...pkgs];
2720
+ }
2721
+ function hasTestPathForConsumer(text, consumerDir) {
2722
+ const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2723
+ const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2724
+ return re.test(text);
2725
+ }
2726
+ function checkConsumerTests(prBody, consumersOf) {
2727
+ const section4 = extractNumberedSection(prBody, 4);
2728
+ if (!section4)
2729
+ return { status: "pass", errors: [] };
2730
+ if (CONSUMER_TESTS_SENTINEL_RE.test(section4))
2731
+ return { status: "pass", errors: [] };
2732
+ const errors = [];
2733
+ for (const pkg of packagesNamedIn(section4)) {
2734
+ for (const consumerDir of consumersOf(pkg)) {
2735
+ if (hasTestPathForConsumer(section4, consumerDir))
2736
+ continue;
2737
+ 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>\`.`);
2738
+ }
2739
+ }
2740
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2741
+ }
2742
+ var CHECK_NAME_RE = /\bcheck-[a-z0-9-]+(?:\.ts)?\b/;
2743
+ 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;
2744
+ var DEFEAT_CASES_RE = /defeat cases\s*:/i;
2745
+ function checkDefeatCases(prBody) {
2746
+ const section4 = extractNumberedSection(prBody, 4);
2747
+ if (!section4)
2748
+ return { status: "pass", errors: [] };
2749
+ if (!CHECK_NAME_RE.test(section4) && !FORGE_WRITE_COMMAND_RE.test(section4))
2750
+ return { status: "pass", errors: [] };
2751
+ const section6 = extractNumberedSection(prBody, 6) ?? "";
2752
+ if (DEFEAT_CASES_RE.test(section6))
2753
+ return { status: "pass", errors: [] };
2754
+ return {
2755
+ status: "fail",
2756
+ errors: [
2757
+ "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."
2758
+ ]
2759
+ };
2760
+ }
2501
2761
  function checkBriefSections(prBody, readTier, options = {}) {
2502
- const { requireClosesN = true } = options;
2762
+ const { requireClosesN = true, consumersOf = () => [] } = options;
2503
2763
  const results = [
2504
2764
  checkTierField(prBody, readTier),
2505
2765
  checkTestPlan(prBody),
@@ -2512,10 +2772,219 @@ function checkBriefSections(prBody, readTier, options = {}) {
2512
2772
  checkAutonomyClause(prBody),
2513
2773
  checkProjectField(prBody),
2514
2774
  checkForField(prBody),
2775
+ checkNoUnpinnedCodeClaims(prBody),
2776
+ checkNoAgentBoxes(prBody),
2777
+ checkCommandsCarryOutput(prBody),
2778
+ checkConsumerTests(prBody, consumersOf),
2779
+ checkDefeatCases(prBody),
2515
2780
  ...requireClosesN ? [checkClosesN(prBody)] : []
2516
2781
  ];
2517
2782
  return { errors: results.flatMap((r) => r.errors) };
2518
2783
  }
2784
+ // ../../packages/aeg-core/src/doctrine-portability.ts
2785
+ var DEFAULT_SHIPS_PREFIX = "aeg-root/";
2786
+ var STATIC_PORTABLE_PREFIXES = [
2787
+ "roles/",
2788
+ "contracts/",
2789
+ "skills/",
2790
+ ".github/",
2791
+ ".vinaya/",
2792
+ ".claude/",
2793
+ ".git/",
2794
+ ".husky/"
2795
+ ];
2796
+ var EXEMPT_LITERALS = new Set([
2797
+ "path/inside/the/shipped/diff.ts",
2798
+ "path/inside/the/surface.ts",
2799
+ "apps/x/specs/..."
2800
+ ]);
2801
+ var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
2802
+ var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
2803
+ 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";
2804
+ var VENDOR_EXAMPLE_START = /<!--\s*AEG:VENDOR-EXAMPLE:START\s*-->/;
2805
+ var VENDOR_EXAMPLE_END = /<!--\s*AEG:VENDOR-EXAMPLE:END\s*-->/;
2806
+ function maskVendorExampleRegion(masked) {
2807
+ const start = VENDOR_EXAMPLE_START.exec(masked);
2808
+ if (!start)
2809
+ return masked;
2810
+ const afterStart = start.index + start[0].length;
2811
+ const end = VENDOR_EXAMPLE_END.exec(masked.slice(afterStart));
2812
+ if (!end)
2813
+ return masked;
2814
+ const regionEnd = afterStart + end.index + end[0].length;
2815
+ const region = masked.slice(start.index, regionEnd);
2816
+ const blanked = region.replace(/[^\n]/g, " ");
2817
+ return masked.slice(0, start.index) + blanked + masked.slice(regionEnd);
2818
+ }
2819
+ function extractVendorMentions(content) {
2820
+ const scoped = maskVendorExampleRegion(maskCode(content));
2821
+ const found = [];
2822
+ const pattern = new RegExp(VENDOR_NAME_SOURCE, "gi");
2823
+ let match = pattern.exec(scoped);
2824
+ while (match !== null) {
2825
+ found.push({ name: match[0], index: match.index });
2826
+ match = pattern.exec(scoped);
2827
+ }
2828
+ return found;
2829
+ }
2830
+ function extractCitedPaths(content) {
2831
+ const found = [];
2832
+ const spanPattern = /`([^`\n]+)`/g;
2833
+ let match = spanPattern.exec(content);
2834
+ while (match !== null) {
2835
+ const cited = match[1] ?? "";
2836
+ const topSegment = cited.slice(0, cited.indexOf("/"));
2837
+ if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
2838
+ found.push({ cited, index: match.index });
2839
+ }
2840
+ match = spanPattern.exec(content);
2841
+ }
2842
+ return found;
2843
+ }
2844
+ function lineAtIndex(content, index) {
2845
+ let line = 1;
2846
+ for (let i = 0;i < index; i++) {
2847
+ if (content.charCodeAt(i) === 10)
2848
+ line++;
2849
+ }
2850
+ return line;
2851
+ }
2852
+ function isPortable(cited, shipsPrefix) {
2853
+ if (cited.startsWith(shipsPrefix))
2854
+ return true;
2855
+ return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
2856
+ }
2857
+ function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
2858
+ const findings = [];
2859
+ for (const file of files) {
2860
+ if (!file.path.startsWith(shipsPrefix))
2861
+ continue;
2862
+ for (const { cited, index } of extractCitedPaths(file.content)) {
2863
+ if (EXEMPT_LITERALS.has(cited))
2864
+ continue;
2865
+ if (isPortable(cited, shipsPrefix))
2866
+ continue;
2867
+ findings.push({
2868
+ file: file.path,
2869
+ line: lineAtIndex(file.content, index),
2870
+ cited,
2871
+ message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`,
2872
+ kind: "path"
2873
+ });
2874
+ }
2875
+ for (const { name, index } of extractVendorMentions(file.content)) {
2876
+ findings.push({
2877
+ file: file.path,
2878
+ line: lineAtIndex(file.content, index),
2879
+ cited: name,
2880
+ 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`,
2881
+ kind: "vendor-name"
2882
+ });
2883
+ }
2884
+ }
2885
+ return findings;
2886
+ }
2887
+
2888
+ // ../../packages/aeg-core/src/doctrine-no-procedures.ts
2889
+ function vendorExampleSpan(content) {
2890
+ const start = VENDOR_EXAMPLE_START.exec(content);
2891
+ if (!start)
2892
+ return null;
2893
+ const afterStart = start.index + start[0].length;
2894
+ const end = VENDOR_EXAMPLE_END.exec(content.slice(afterStart));
2895
+ if (!end)
2896
+ return null;
2897
+ return { start: start.index, end: afterStart + end.index + end[0].length };
2898
+ }
2899
+ function isCommandLine(line) {
2900
+ const word = line.trim().split(/\s+/)[0] ?? "";
2901
+ return COMMAND_WORDS.includes(word);
2902
+ }
2903
+ var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
2904
+ function procedureLineNumberAt(content, index) {
2905
+ return content.slice(0, index).split(`
2906
+ `).length;
2907
+ }
2908
+ function isTemplatePath(path) {
2909
+ return /(^|\/)templates\//.test(path);
2910
+ }
2911
+ function checkDoctrineNoProcedures(files) {
2912
+ const findings = [];
2913
+ for (const file of files) {
2914
+ if (isTemplatePath(file.path))
2915
+ continue;
2916
+ const span = vendorExampleSpan(file.content);
2917
+ for (const block of extractFencedBlocks(file.content)) {
2918
+ if (span && block.start >= span.start && block.end <= span.end)
2919
+ continue;
2920
+ if (!SHELL_LANGS.has(block.lang))
2921
+ continue;
2922
+ const commandLineCount = block.content.split(`
2923
+ `).filter(isCommandLine).length;
2924
+ if (commandLineCount >= 2) {
2925
+ findings.push({
2926
+ file: file.path,
2927
+ line: procedureLineNumberAt(file.content, block.start),
2928
+ 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).`
2929
+ });
2930
+ }
2931
+ }
2932
+ }
2933
+ return findings;
2934
+ }
2935
+ // ../../packages/aeg-core/src/pr-report-density.ts
2936
+ function headingSectionBody(prBody, heading) {
2937
+ const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "im");
2938
+ const m = headingRe.exec(prBody);
2939
+ if (!m)
2940
+ return null;
2941
+ const start = m.index + m[0].length;
2942
+ const rest = prBody.slice(start);
2943
+ const nextHeading = /^#{1,2}\s/m.exec(rest);
2944
+ return nextHeading ? rest.slice(0, nextHeading.index) : rest;
2945
+ }
2946
+ function stripAnchorBlocks(sectionText) {
2947
+ let result = sectionText;
2948
+ for (const field of ["CLOSES", "PROJECT", "TIER", "PREMISE", "TEST-PLAN", "EVIDENCE"]) {
2949
+ for (;; ) {
2950
+ const bounds = anchoredRegionBounds(result, field);
2951
+ if (!bounds)
2952
+ break;
2953
+ result = result.slice(0, bounds.outerStart) + result.slice(bounds.outerEnd);
2954
+ }
2955
+ }
2956
+ return result;
2957
+ }
2958
+ function paragraphCount(sectionText) {
2959
+ const stripped = stripCode(stripAnchorBlocks(sectionText)).trim();
2960
+ if (stripped.length === 0)
2961
+ return 0;
2962
+ return stripped.split(/\n[ \t]*\n/).map((block) => block.trim()).filter((block) => block.length > 0).length;
2963
+ }
2964
+ function checkSectionDensity(prBody, heading) {
2965
+ const body = headingSectionBody(prBody, heading);
2966
+ if (body === null)
2967
+ return { status: "pass", errors: [] };
2968
+ const count = paragraphCount(body);
2969
+ if (count <= 1)
2970
+ return { status: "pass", errors: [] };
2971
+ return {
2972
+ status: "fail",
2973
+ errors: [
2974
+ `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.`
2975
+ ]
2976
+ };
2977
+ }
2978
+ function checkSummaryDensity(prBody) {
2979
+ return checkSectionDensity(prBody, "Summary");
2980
+ }
2981
+ function checkScopeDensity(prBody) {
2982
+ return checkSectionDensity(prBody, "Scope");
2983
+ }
2984
+ function checkPrReportDensity(prBody) {
2985
+ const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
2986
+ return { errors: results.flatMap((r) => r.errors) };
2987
+ }
2519
2988
  // ../../packages/aeg-core/src/issue-validation.ts
2520
2989
  function hasRationaleField(body, labelPattern) {
2521
2990
  const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
@@ -2767,6 +3236,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
2767
3236
  const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
2768
3237
  if (!depEntry)
2769
3238
  continue;
3239
+ const sameTask = depEntry.trancheSlug === e.trancheSlug && depEntry.task.id === e.task.id;
3240
+ const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3241
+ if (sameTask || sameIssue) {
3242
+ failures.push({
3243
+ issue: e.task.issue,
3244
+ tranche: e.trancheSlug,
3245
+ task: e.task.id,
3246
+ reason: `INTERNAL: parsed a self-dependency (depends-on ${dep}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream.`
3247
+ });
3248
+ continue;
3249
+ }
2770
3250
  const depFacts = depEntry.facts;
2771
3251
  const depClosed = depFacts?.issueState === "closed";
2772
3252
  if (!depClosed) {
@@ -2910,41 +3390,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2910
3390
  }
2911
3391
  return { ok: true, expectedIssue };
2912
3392
  }
2913
- // ../../packages/aeg-core/src/verdict-extraction.ts
2914
- var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2915
- function extractHeadSha(comment) {
2916
- const m = comment.match(HEAD_SHA_PATTERN);
2917
- return m ? m[1].toLowerCase() : null;
2918
- }
2919
- function extractVerdict(comments, valuePattern, missingLabel) {
2920
- const clearHits = comments.filter((c) => valuePattern.test(c));
2921
- if (clearHits.length > 0) {
2922
- const latest = clearHits[clearHits.length - 1];
2923
- const m = latest.match(valuePattern);
2924
- return {
2925
- value: m[1].toUpperCase().replace(/[_-]/g, " "),
2926
- headSha: extractHeadSha(latest),
2927
- danglingNote: null
2928
- };
2929
- }
2930
- return {
2931
- value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2932
- headSha: null,
2933
- danglingNote: `no ${missingLabel} verdict comment found on this PR`
2934
- };
2935
- }
2936
- function extractCodeReviewVerdict(comments) {
2937
- return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
2938
- }
2939
- function extractSecurityReviewVerdict(comments) {
2940
- return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2941
- }
2942
3393
  // ../../packages/aeg-core/src/review-gate.ts
2943
3394
  function isBoundToHead(extraction, headSha) {
2944
3395
  if (!extraction.headSha)
2945
3396
  return false;
2946
3397
  return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2947
3398
  }
3399
+ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3400
+ if (patchIdOf === undefined || !extraction.headSha)
3401
+ return false;
3402
+ const judged = patchIdOf(extraction.headSha);
3403
+ const current = patchIdOf(headSha);
3404
+ if (judged === null || current === null)
3405
+ return false;
3406
+ return judged === current;
3407
+ }
3408
+ function isBoundToPatch(extraction, headSha, patchIdOf) {
3409
+ return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3410
+ }
2948
3411
  function checkReviewGate(input) {
2949
3412
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
2950
3413
  const waived = isWaiverLabelActorVerified({
@@ -2960,6 +3423,8 @@ function checkReviewGate(input) {
2960
3423
  waived: true
2961
3424
  };
2962
3425
  }
3426
+ const reportedMechanicalChecks = input.mechanicalChecks.filter((c) => c.bucket !== "skipping" && c.bucket !== "neutral");
3427
+ const mechanicalChecksClean = reportedMechanicalChecks.length > 0 && reportedMechanicalChecks.every((c) => c.bucket === "pass");
2963
3428
  const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
2964
3429
  const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
2965
3430
  const verifiedBodies = verified.map((c) => c.body);
@@ -2967,12 +3432,12 @@ function checkReviewGate(input) {
2967
3432
  const security = extractSecurityReviewVerdict(verifiedBodies);
2968
3433
  const codeReviewClean = codeReview.value === "APPROVE";
2969
3434
  const securityClean = security.value === "PASS";
2970
- const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2971
- const securityBound = isBoundToHead(security, input.headSha);
2972
- if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
3435
+ const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3436
+ const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3437
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
2973
3438
  return {
2974
3439
  verdict: "pass",
2975
- reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
3440
+ 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.`,
2976
3441
  waived: false
2977
3442
  };
2978
3443
  }
@@ -2987,6 +3452,9 @@ function checkReviewGate(input) {
2987
3452
  } else if (!securityBound) {
2988
3453
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2989
3454
  }
3455
+ if (!mechanicalChecksClean) {
3456
+ 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(", ")}`);
3457
+ }
2990
3458
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2991
3459
  return {
2992
3460
  verdict: "fail",
@@ -3266,72 +3734,6 @@ function compareToBaseline(current, baseline) {
3266
3734
  perTool
3267
3735
  };
3268
3736
  }
3269
- // ../../packages/aeg-core/src/doctrine-portability.ts
3270
- var DEFAULT_SHIPS_PREFIX = "aeg-root/";
3271
- var STATIC_PORTABLE_PREFIXES = [
3272
- "roles/",
3273
- "contracts/",
3274
- "skills/",
3275
- ".github/",
3276
- ".vinaya/",
3277
- ".claude/",
3278
- ".git/",
3279
- ".husky/"
3280
- ];
3281
- var EXEMPT_LITERALS = new Set([
3282
- "path/inside/the/shipped/diff.ts",
3283
- "path/inside/the/surface.ts",
3284
- "apps/x/specs/..."
3285
- ]);
3286
- var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
3287
- var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
3288
- function extractCitedPaths(content) {
3289
- const found = [];
3290
- const spanPattern = /`([^`\n]+)`/g;
3291
- let match = spanPattern.exec(content);
3292
- while (match !== null) {
3293
- const cited = match[1] ?? "";
3294
- const topSegment = cited.slice(0, cited.indexOf("/"));
3295
- if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
3296
- found.push({ cited, index: match.index });
3297
- }
3298
- match = spanPattern.exec(content);
3299
- }
3300
- return found;
3301
- }
3302
- function lineAtIndex(content, index) {
3303
- let line = 1;
3304
- for (let i = 0;i < index; i++) {
3305
- if (content.charCodeAt(i) === 10)
3306
- line++;
3307
- }
3308
- return line;
3309
- }
3310
- function isPortable(cited, shipsPrefix) {
3311
- if (cited.startsWith(shipsPrefix))
3312
- return true;
3313
- return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
3314
- }
3315
- function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
3316
- const findings = [];
3317
- for (const file of files) {
3318
- if (!file.path.startsWith(shipsPrefix))
3319
- continue;
3320
- for (const { cited, index } of extractCitedPaths(file.content)) {
3321
- if (EXEMPT_LITERALS.has(cited))
3322
- continue;
3323
- if (isPortable(cited, shipsPrefix))
3324
- continue;
3325
- findings.push({
3326
- file: file.path,
3327
- line: lineAtIndex(file.content, index),
3328
- cited,
3329
- message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
3330
- });
3331
- }
3332
- }
3333
- return findings;
3334
- }
3335
3737
  // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3336
3738
  var SHIPS_PREFIX = "aeg-root/";
3337
3739
  function shipsArchivePrefix(shipsPrefix) {
@@ -3667,6 +4069,12 @@ function scanRetiredVocabulary(files) {
3667
4069
  function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
3668
4070
  return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
3669
4071
  }
4072
+ function isSelfDependency(dep, task, issue) {
4073
+ if (dep.issue !== null && issue !== null && dep.issue === issue.number)
4074
+ return true;
4075
+ const bare = dep.id.trim().replace(/^#/, "");
4076
+ return bare.toLowerCase() === task.id.trim().toLowerCase();
4077
+ }
3670
4078
  function checkDispatchReadiness(input) {
3671
4079
  const { trancheSlug, task } = input;
3672
4080
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
@@ -3681,6 +4089,11 @@ function checkDispatchReadiness(input) {
3681
4089
  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.`);
3682
4090
  }
3683
4091
  for (const dep of input.dependsOn) {
4092
+ if (isSelfDependency(dep, task, input.issue)) {
4093
+ const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4094
+ 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.`);
4095
+ continue;
4096
+ }
3684
4097
  if (dep.resolved === false) {
3685
4098
  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.`);
3686
4099
  continue;
@@ -3745,8 +4158,12 @@ function isNewDiskStateFile(path, status) {
3745
4158
  return ANY_DEPTH_TRANCHES_MD.test(path) || TOKENS_FILE.test(path);
3746
4159
  }
3747
4160
  // ../../packages/aeg-core/src/main-branch-refusal.ts
4161
+ function pushesAnyBranchRef(pushRefs) {
4162
+ return pushRefs.split(`
4163
+ `).map((line) => line.trim()).filter((line) => line.length > 0).some((line) => line.split(/\s+/)[2]?.startsWith("refs/heads/") === true);
4164
+ }
3748
4165
  function checkMainBranchRefusal(facts) {
3749
- const { currentSymbolicBranch, defaultBranch } = facts;
4166
+ const { currentSymbolicBranch, defaultBranch, pushRefs = null } = facts;
3750
4167
  if (currentSymbolicBranch === null)
3751
4168
  return null;
3752
4169
  if (defaultBranch === null) {
@@ -3758,6 +4175,8 @@ function checkMainBranchRefusal(facts) {
3758
4175
  };
3759
4176
  }
3760
4177
  if (currentSymbolicBranch === defaultBranch) {
4178
+ if (pushRefs !== null && !pushesAnyBranchRef(pushRefs))
4179
+ return null;
3761
4180
  return {
3762
4181
  reason: "on-default-branch",
3763
4182
  severity: "error",
@@ -3920,27 +4339,27 @@ function evaluateTestPlanGate(body, branch) {
3920
4339
  };
3921
4340
  }
3922
4341
  const checkboxLines = section.split(`
3923
- `).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]/.test(line));
4342
+ `).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
3924
4343
  if (checkboxLines.length === 0) {
3925
4344
  return {
3926
4345
  verdict: "pass",
3927
4346
  messages: [
3928
- "Test Plan section found but has no checkbox items.",
4347
+ "Test Plan section found but has no `[principal]` checkbox items.",
3929
4348
  "PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
3930
4349
  ]
3931
4350
  };
3932
4351
  }
3933
4352
  const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
3934
4353
  const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
3935
- const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
4354
+ const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
3936
4355
  if (unchecked.length > 0) {
3937
- messages.push("", "FAIL — the following Test Plan items are unticked:");
4356
+ messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
3938
4357
  for (const line of unchecked)
3939
4358
  messages.push(` ${line}`);
3940
- 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.");
4359
+ 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.");
3941
4360
  return { verdict: "fail", messages };
3942
4361
  }
3943
- messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
4362
+ messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
3944
4363
  return { verdict: "pass", messages };
3945
4364
  }
3946
4365
  // ../../packages/aeg-core/src/workspace-escape.ts
@@ -4021,6 +4440,7 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4021
4440
  return findings;
4022
4441
  }
4023
4442
  // src/commands/doctrine.ts
4443
+ import { execFileSync as execFileSync2 } from "node:child_process";
4024
4444
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
4025
4445
  import { dirname as dirname2, join as join2, sep } from "node:path";
4026
4446
  import matter2 from "gray-matter";
@@ -4044,22 +4464,36 @@ function packageRoot(moduleUrl) {
4044
4464
 
4045
4465
  // src/commands/doctrine.ts
4046
4466
  var ENTRY_SEGMENTS = ["skills", "aeg", "SKILL.md"];
4047
- function resolveDoctrineRoot(pkg = packageRoot(import.meta.url)) {
4048
- const candidates = [join2(pkg, "aeg-root")];
4049
- if (!pkg.split(sep).includes("node_modules"))
4050
- candidates.push(join2(dirname2(dirname2(pkg)), "aeg-root"));
4051
- for (const root of candidates) {
4052
- if (hasDoctrineEntry(root))
4053
- return root;
4467
+ function resolveDoctrineRoot(pkg = packageRoot(import.meta.url), cwd = process.cwd()) {
4468
+ return resolveDoctrineRootInfo(pkg, cwd)?.root ?? null;
4469
+ }
4470
+ function gitToplevel(cwd) {
4471
+ try {
4472
+ return execFileSync2("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
4473
+ encoding: "utf8",
4474
+ stdio: ["ignore", "pipe", "pipe"]
4475
+ }).trim();
4476
+ } catch {
4477
+ return null;
4054
4478
  }
4055
- return null;
4479
+ }
4480
+ function resolveDoctrineRootInfo(pkg = packageRoot(import.meta.url), cwd = process.cwd()) {
4481
+ const toplevel = gitToplevel(cwd);
4482
+ if (toplevel !== null) {
4483
+ const treeRoot = join2(toplevel, "aeg-root");
4484
+ if (existsSync2(join2(treeRoot, "roles")))
4485
+ return { root: treeRoot, source: "tree" };
4486
+ }
4487
+ const fromSource = !pkg.split(sep).includes("node_modules");
4488
+ const bundleRoot = fromSource ? join2(dirname2(dirname2(pkg)), "aeg-root") : join2(pkg, "aeg-root");
4489
+ return hasDoctrineEntry(bundleRoot) ? { root: bundleRoot, source: "bundle" } : null;
4056
4490
  }
4057
4491
  function hasDoctrineEntry(root) {
4058
4492
  return existsSync2(join2(root, ...ENTRY_SEGMENTS));
4059
4493
  }
4060
4494
 
4061
4495
  // src/lib/config.ts
4062
- import { execFileSync as execFileSync2 } from "node:child_process";
4496
+ import { execFileSync as execFileSync3 } from "node:child_process";
4063
4497
  import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, realpathSync, writeFileSync } from "node:fs";
4064
4498
  import { homedir } from "node:os";
4065
4499
  import { dirname as dirname3, join as join3, resolve } from "node:path";
@@ -4291,7 +4725,7 @@ function trustAnchorRepo() {
4291
4725
  return validated;
4292
4726
  }
4293
4727
  try {
4294
- const url = execFileSync2("git", ["remote", "get-url", "origin"], {
4728
+ const url = execFileSync3("git", ["remote", "get-url", "origin"], {
4295
4729
  encoding: "utf-8",
4296
4730
  stdio: ["ignore", "pipe", "pipe"]
4297
4731
  }).trim();
@@ -4305,7 +4739,7 @@ function ghFetchTrustAnchorConfig() {
4305
4739
  const repo = trustAnchorRepo();
4306
4740
  if (!repo)
4307
4741
  throw new Error("could not resolve repo identity for the trust-anchor read");
4308
- return execFileSync2("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
4742
+ return execFileSync3("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
4309
4743
  encoding: "utf-8",
4310
4744
  stdio: ["ignore", "pipe", "pipe"]
4311
4745
  });
@@ -4369,12 +4803,41 @@ function emitCheckError(error) {
4369
4803
  }
4370
4804
 
4371
4805
  // src/lib/diff-evidence.ts
4372
- import { execFileSync as execFileSync3 } from "node:child_process";
4373
- import { join as join4 } from "node:path";
4806
+ import { execFileSync as execFileSync4 } from "node:child_process";
4807
+ import { join as join4, relative, resolve as resolve2 } from "node:path";
4808
+
4809
+ // src/commands/review-post.ts
4810
+ function parseChangedLineRanges(diffOutput) {
4811
+ const result = {};
4812
+ let currentFile = null;
4813
+ for (const line of diffOutput.split(`
4814
+ `)) {
4815
+ if (line.startsWith("+++ ")) {
4816
+ const path = line.slice(4).trim();
4817
+ currentFile = path === "/dev/null" ? null : path.replace(/^b\//, "");
4818
+ continue;
4819
+ }
4820
+ if (line.startsWith("@@") && currentFile) {
4821
+ const m = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
4822
+ if (m) {
4823
+ const start = Number(m[1]);
4824
+ const count = m[2] !== undefined ? Number(m[2]) : 1;
4825
+ if (count > 0) {
4826
+ if (!result[currentFile])
4827
+ result[currentFile] = [];
4828
+ result[currentFile].push([start, start + count - 1]);
4829
+ }
4830
+ }
4831
+ }
4832
+ }
4833
+ return result;
4834
+ }
4835
+
4836
+ // src/lib/diff-evidence.ts
4374
4837
  function fileDiffAgainst(ref, path) {
4375
4838
  let out;
4376
4839
  try {
4377
- out = execFileSync3("git", ["diff", `${ref}...HEAD`, "--", path], {
4840
+ out = execFileSync4("git", ["diff", `${ref}...HEAD`, "--", path], {
4378
4841
  encoding: "utf8",
4379
4842
  stdio: ["ignore", "pipe", "pipe"]
4380
4843
  }).trim();
@@ -4385,7 +4848,7 @@ function fileDiffAgainst(ref, path) {
4385
4848
  }
4386
4849
  function revParse(ref) {
4387
4850
  try {
4388
- return execFileSync3("git", ["rev-parse", "--verify", ref], {
4851
+ return execFileSync4("git", ["rev-parse", "--verify", ref], {
4389
4852
  encoding: "utf8",
4390
4853
  stdio: ["ignore", "pipe", "pipe"]
4391
4854
  }).trim();
@@ -4395,7 +4858,7 @@ function revParse(ref) {
4395
4858
  }
4396
4859
  function repoRoot() {
4397
4860
  try {
4398
- return execFileSync3("git", ["rev-parse", "--show-toplevel"], {
4861
+ return execFileSync4("git", ["rev-parse", "--show-toplevel"], {
4399
4862
  encoding: "utf8",
4400
4863
  stdio: ["ignore", "pipe", "pipe"]
4401
4864
  }).trim();
@@ -4406,7 +4869,7 @@ function repoRoot() {
4406
4869
  function changedFiles(base) {
4407
4870
  let out;
4408
4871
  try {
4409
- out = execFileSync3("git", ["diff", "--name-only", `${base}...HEAD`], {
4872
+ out = execFileSync4("git", ["diff", "--name-only", `${base}...HEAD`], {
4410
4873
  encoding: "utf8",
4411
4874
  stdio: ["ignore", "pipe", "pipe"]
4412
4875
  }).trim();
@@ -4417,6 +4880,9 @@ function changedFiles(base) {
4417
4880
  `).map((s) => s.trim()).filter(Boolean);
4418
4881
  }
4419
4882
  function resolveChangedFiles(base = process.env.BASE_SHA || "origin/main") {
4883
+ return resolveDiff(base)?.files ?? null;
4884
+ }
4885
+ function resolveDiff(base = process.env.BASE_SHA || "origin/main") {
4420
4886
  const root = repoRoot();
4421
4887
  if (root === null)
4422
4888
  return null;
@@ -4428,10 +4894,36 @@ function resolveChangedFiles(base = process.env.BASE_SHA || "origin/main") {
4428
4894
  const relPaths = changedFiles(ref);
4429
4895
  if (relPaths === null)
4430
4896
  continue;
4431
- return relPaths.map((p) => join4(root, p));
4897
+ return { base: ref, root, files: relPaths.map((p) => join4(root, p)) };
4432
4898
  }
4433
4899
  return null;
4434
4900
  }
4901
+ function changedLineRanges(ref, path) {
4902
+ const diff = fileDiffAgainst(ref, path);
4903
+ if (diff === null)
4904
+ return null;
4905
+ return parseChangedLineRanges(diff)[path] ?? null;
4906
+ }
4907
+ function lineIsInRanges(line, ranges) {
4908
+ return ranges.some(([start, end]) => line >= start && line <= end);
4909
+ }
4910
+ function findingsInThisDiff(findings) {
4911
+ const diff = resolveDiff();
4912
+ if (diff === null)
4913
+ return [...findings];
4914
+ const changed = new Set(diff.files);
4915
+ const ranges = new Map;
4916
+ return findings.filter((f) => {
4917
+ const abs = resolve2(diff.root, f.file);
4918
+ if (!changed.has(abs))
4919
+ return false;
4920
+ const relPath = relative(diff.root, abs);
4921
+ if (!ranges.has(relPath))
4922
+ ranges.set(relPath, changedLineRanges(diff.base, relPath));
4923
+ const fileRanges = ranges.get(relPath) ?? null;
4924
+ return fileRanges === null ? true : lineIsInRanges(f.line, fileRanges);
4925
+ });
4926
+ }
4435
4927
 
4436
4928
  // src/checks/bin/check-quoted-command.ts
4437
4929
  var CHECK_NAME = "quoted-command";
@@ -4473,16 +4965,16 @@ function toPosix(p) {
4473
4965
  }
4474
4966
  function main() {
4475
4967
  const root = repoRoot() ?? process.cwd();
4476
- const docRootAbs = resolve2(root, DOCTRINE_ROOT);
4477
- const shipsPrefix = `${toPosix(relative(root, docRootAbs))}/`;
4968
+ const docRootAbs = resolve3(root, DOCTRINE_ROOT);
4969
+ const shipsPrefix = `${toPosix(relative2(root, docRootAbs))}/`;
4478
4970
  const shipsPaths = collect(docRootAbs).filter((p) => p.endsWith(".md"));
4479
- const readerFacingRootAbs = READER_FACING_ACTIVE && READER_FACING_PREFIX !== null ? resolve2(root, READER_FACING_PREFIX) : null;
4971
+ const readerFacingRootAbs = READER_FACING_ACTIVE && READER_FACING_PREFIX !== null ? resolve3(root, READER_FACING_PREFIX) : null;
4480
4972
  const readerFacingPaths = readerFacingRootAbs !== null && READER_FACING_SUFFIX !== null ? collect(readerFacingRootAbs).filter((p) => p.endsWith(READER_FACING_SUFFIX)) : [];
4481
4973
  const docs = readAll([...shipsPaths, ...readerFacingPaths]).map((f) => ({
4482
4974
  ...f,
4483
- path: toPosix(relative(root, f.path))
4975
+ path: toPosix(relative2(root, f.path))
4484
4976
  }));
4485
- const readerFacingPrefix = readerFacingRootAbs !== null ? `${toPosix(relative(root, readerFacingRootAbs))}/` : "/no-reader-facing-surface";
4977
+ const readerFacingPrefix = readerFacingRootAbs !== null ? `${toPosix(relative2(root, readerFacingRootAbs))}/` : "/no-reader-facing-surface";
4486
4978
  const readerFacingSuffix = READER_FACING_ACTIVE && READER_FACING_SUFFIX !== null ? READER_FACING_SUFFIX : "/page.tsx";
4487
4979
  const citedQuotes = findCitedQuotes(docs, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
4488
4980
  const citedFileContents = new Map;