@attalabs/vinaya 0.22.0 → 0.23.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 (188) hide show
  1. package/README.md +13 -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 +552 -139
  23. package/dist/checks/bin/check-branch-topology.js +603 -144
  24. package/dist/checks/bin/check-brief-shape.js +593 -142
  25. package/dist/checks/bin/check-changeset-coverage.js +935 -161
  26. package/dist/checks/bin/check-closes-n.js +603 -144
  27. package/dist/checks/bin/check-coherence.js +614 -147
  28. package/dist/checks/bin/check-dead-branch-push.js +552 -139
  29. package/dist/checks/bin/check-dispatch-readiness.js +612 -145
  30. package/dist/checks/bin/check-doc-coverage-push.js +689 -218
  31. package/dist/checks/bin/check-doc-coverage.js +689 -218
  32. package/dist/checks/bin/check-doctrine-no-procedures.js +4794 -0
  33. package/dist/checks/bin/check-doctrine-portability.js +684 -146
  34. package/dist/checks/bin/check-evidence-fresh.js +994 -174
  35. package/dist/checks/bin/check-exec-bits.js +4931 -0
  36. package/dist/checks/bin/check-first-push-dispatch.js +603 -144
  37. package/dist/checks/bin/check-issue-assignment.js +603 -144
  38. package/dist/checks/bin/check-main-branch-refusal.js +552 -139
  39. package/dist/checks/bin/check-no-disk-state.js +552 -139
  40. package/dist/checks/bin/check-pr-report-density.js +4461 -0
  41. package/dist/checks/bin/check-quoted-command.js +621 -155
  42. package/dist/checks/bin/check-reader-resolvable-prose.js +617 -154
  43. package/dist/checks/bin/check-registry-gates.js +595 -144
  44. package/dist/checks/bin/check-retired-vocabulary.js +617 -154
  45. package/dist/checks/bin/check-review-gate.js +670 -141
  46. package/dist/checks/bin/check-single-plan-pr.js +552 -139
  47. package/dist/checks/bin/check-test-plan.js +553 -140
  48. package/dist/checks/bin/check-token-collection-wired.js +552 -139
  49. package/dist/checks/bin/check-token-report.js +552 -139
  50. package/dist/checks/bin/check-workspace-escape.js +985 -145
  51. package/dist/index.js +8500 -5518
  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 +113 -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 +192 -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-validation.ts +511 -0
  60. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/claude-code-transcript.ts +460 -0
  61. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/coherence-checks.ts +787 -0
  62. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-audit.ts +55 -0
  63. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-guard.ts +77 -0
  64. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-section7.ts +66 -0
  65. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-tranche.ts +94 -0
  66. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/diagram-model.ts +372 -0
  67. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/direct-main-push.ts +39 -0
  68. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dispatch-gate.ts +224 -0
  69. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doc-owners.ts +314 -0
  70. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/build-doc-nav.ts +38 -0
  71. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/docs-coherence.ts +136 -0
  72. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/index.ts +29 -0
  73. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/legacy-anchors.ts +48 -0
  74. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/nav-helpers.ts +17 -0
  75. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/node-route.ts +134 -0
  76. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/parse-doc.ts +43 -0
  77. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/published-prose.ts +306 -0
  78. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/surfaced-manifest.ts +89 -0
  79. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/types.ts +33 -0
  80. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-portability.ts +170 -0
  81. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-source.ts +23 -0
  82. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/ensure-label.ts +28 -0
  83. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/file-classify.ts +51 -0
  84. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/first-push-dispatch-gate.ts +80 -0
  85. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/gate-audience.ts +168 -0
  86. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/index.ts +301 -0
  87. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-assignment.ts +102 -0
  88. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-validation.ts +746 -0
  89. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/leftover-detection.ts +68 -0
  90. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/local-anchor-coverage.ts +245 -0
  91. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +64 -0
  92. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/manifest-validity.ts +81 -0
  93. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/markdown-table.ts +76 -0
  94. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/metering-io-guard.ts +119 -0
  95. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/milestone-validation.ts +240 -0
  96. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/no-disk-state.ts +50 -0
  97. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-ledger.ts +77 -0
  98. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-registry.ts +105 -0
  99. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-token-report.ts +165 -0
  100. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-tranche.ts +183 -0
  101. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/pr-tier.ts +81 -0
  102. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/premise-check.ts +125 -0
  103. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/quoted-command.ts +275 -0
  104. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/reader-resolvable-prose.ts +320 -0
  105. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-checks.ts +255 -0
  106. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-parse.ts +164 -0
  107. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-scaffold.ts +216 -0
  108. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/report-tokens.ts +261 -0
  109. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/retired-vocabulary.ts +163 -0
  110. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-gate.ts +269 -0
  111. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/single-plan-pr.ts +73 -0
  112. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-machine-model.ts +269 -0
  113. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-source.ts +14 -0
  114. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/status-block.ts +8 -0
  115. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/sum-ledger.ts +26 -0
  116. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/symbol-collisions.ts +78 -0
  117. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-gate.ts +104 -0
  118. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-section.ts +65 -0
  119. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/types.ts +174 -0
  120. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/verdict-extraction.ts +140 -0
  121. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/vocabulary-citation.ts +114 -0
  122. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/waiver-label.ts +52 -0
  123. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/workspace-escape.ts +172 -0
  124. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  125. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  126. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  127. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  128. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  129. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  130. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  131. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  132. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  133. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  134. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  135. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  136. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  137. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  138. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  139. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  140. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  141. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  142. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  143. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  144. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  145. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  146. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  147. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  148. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  149. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  150. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  151. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  152. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  153. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  154. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  155. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  156. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  157. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  158. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  159. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  160. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  161. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  162. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
  163. 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
  164. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  165. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  166. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  167. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  168. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  169. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__069-_41._.js → [root-of-the-server]__1mcitst._.js} +1 -1
  170. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  171. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  172. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  173. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  174. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +1 -1
  175. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  176. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  177. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  178. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  179. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  180. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  181. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{37hnluhzj9bei.js → 0jer_drkl1e4r.js} +4 -4
  182. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{267o-tsrnuwnv.js → 20x-t0a0v129z.js} +1 -1
  183. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1hufpuody6vjv.js → 2ou_81ahqw31e.js} +1 -1
  184. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  185. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  186. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_buildManifest.js +0 -0
  187. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_clientMiddlewareManifest.js +0 -0
  188. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_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-no-disk-state.ts
4
6
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -210,17 +212,18 @@ function splitIds(raw) {
210
212
  return [];
211
213
  return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
212
214
  }
213
- function resolveIds(raw, lastSlug) {
215
+ function resolveIds(raw) {
214
216
  const ids = splitIds(raw);
215
217
  const resolved = [];
218
+ let lastSlug = null;
216
219
  for (const id of ids) {
217
220
  const qualified = id.match(SLUG_QUALIFIED_ID);
218
221
  if (qualified) {
219
- lastSlug.current = qualified[1] ?? null;
222
+ lastSlug = qualified[1] ?? null;
220
223
  resolved.push(id);
221
- } else if (lastSlug.current) {
222
- resolved.push(`${lastSlug.current} ${id}`);
223
- lastSlug.current = null;
224
+ } else if (lastSlug) {
225
+ resolved.push(`${lastSlug} ${id}`);
226
+ lastSlug = null;
224
227
  } else {
225
228
  resolved.push(id);
226
229
  }
@@ -240,12 +243,7 @@ function parseRationaleDeps(body) {
240
243
  const section = extractSection(body);
241
244
  const dependsOn = [];
242
245
  const conflictsWith = [];
243
- let current = null;
244
246
  const assignedFields = new Set;
245
- const lastSlugByField = {
246
- dependsOn: { current: null },
247
- conflictsWith: { current: null }
248
- };
249
247
  const spanPattern = /`([^`]*)`/g;
250
248
  let match = spanPattern.exec(section);
251
249
  while (match !== null) {
@@ -255,13 +253,9 @@ function parseRationaleDeps(body) {
255
253
  const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
256
254
  if (!assignedFields.has(field)) {
257
255
  assignedFields.add(field);
258
- current = field;
259
- const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
256
+ const ids = resolveIds(labelMatch[2] ?? "");
260
257
  pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
261
258
  }
262
- } else if (current) {
263
- const ids = resolveIds(content, lastSlugByField[current]);
264
- pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
265
259
  }
266
260
  match = spanPattern.exec(section);
267
261
  }
@@ -1726,6 +1720,48 @@ function splitTableRow(row) {
1726
1720
  parts.pop();
1727
1721
  return parts.map((p) => p.split(PLACEHOLDER).join("|"));
1728
1722
  }
1723
+ // ../../packages/aeg-core/src/blast-radius-domains.ts
1724
+ function resolveWorkspaceEntry(entry2, listDirs) {
1725
+ if (entry2.endsWith("/*")) {
1726
+ const baseDir = entry2.slice(0, -2);
1727
+ return listDirs(baseDir).map((name) => `${baseDir}/${name}`);
1728
+ }
1729
+ if (entry2.includes("*"))
1730
+ return [];
1731
+ return [entry2];
1732
+ }
1733
+ // ../../packages/aeg-core/src/consumer-enumeration.ts
1734
+ function deriveWorkspaceMemberDirs(workspaces, listDirs) {
1735
+ const positive = workspaces.filter((entry2) => !entry2.startsWith("!"));
1736
+ const negative = workspaces.filter((entry2) => entry2.startsWith("!")).map((entry2) => entry2.slice(1));
1737
+ const excluded = new Set;
1738
+ for (const entry2 of negative) {
1739
+ for (const member of resolveWorkspaceEntry(entry2, listDirs))
1740
+ excluded.add(member);
1741
+ }
1742
+ const members = new Set;
1743
+ for (const entry2 of positive) {
1744
+ for (const member of resolveWorkspaceEntry(entry2, listDirs)) {
1745
+ if (!excluded.has(member))
1746
+ members.add(member);
1747
+ }
1748
+ }
1749
+ return [...members].sort();
1750
+ }
1751
+ function buildConsumersOf(workspaces, listDirs, readManifest) {
1752
+ const members = deriveWorkspaceMemberDirs(workspaces, listDirs);
1753
+ return (pkg) => {
1754
+ const target = `@attalabs/${pkg}`;
1755
+ return members.filter((dir) => {
1756
+ if (dir === `packages/${pkg}`)
1757
+ return false;
1758
+ const manifest = readManifest(dir);
1759
+ if (!manifest)
1760
+ return false;
1761
+ return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {});
1762
+ });
1763
+ };
1764
+ }
1729
1765
  // ../../packages/aeg-core/src/state-machine-model.ts
1730
1766
  var FORGE_FACT_INPUTS = [
1731
1767
  {
@@ -1830,6 +1866,22 @@ function isEmDashOrDash(s) {
1830
1866
  const t = s.trim();
1831
1867
  return t === "—" || t === "-" || t === "–";
1832
1868
  }
1869
+ // ../../packages/aeg-core/src/waiver-label.ts
1870
+ var WAIVER_LABEL = label("waiver-docs");
1871
+ var WAIVER_LABEL_REVIEW = label("waiver-review");
1872
+ var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
1873
+ function isPrincipal(login, principalAllowlist) {
1874
+ if (login === null)
1875
+ return false;
1876
+ const normalized = login.toLowerCase();
1877
+ return principalAllowlist.some((p) => p.toLowerCase() === normalized);
1878
+ }
1879
+ function isWaiverLabelActorVerified(opts) {
1880
+ if (!opts.labels.includes(opts.label))
1881
+ return false;
1882
+ return isPrincipal(opts.labelActor, opts.principalAllowlist);
1883
+ }
1884
+
1833
1885
  // ../../packages/aeg-core/src/parse-token-report.ts
1834
1886
  var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
1835
1887
  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;
@@ -1884,6 +1936,47 @@ function parseInlineFieldList(section) {
1884
1936
  return null;
1885
1937
  return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
1886
1938
  }
1939
+ // ../../packages/aeg-core/src/verdict-extraction.ts
1940
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1941
+ function firstThreeLines(comment) {
1942
+ return comment.split(`
1943
+ `).slice(0, 3).join(`
1944
+ `);
1945
+ }
1946
+ function extractHeadSha(comment) {
1947
+ const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
1948
+ return m ? m[1].toLowerCase() : null;
1949
+ }
1950
+ function extractVerdict(comments, valuePattern, missingLabel) {
1951
+ const candidates = comments.filter((c) => valuePattern.test(c));
1952
+ if (candidates.length === 0) {
1953
+ return {
1954
+ value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1955
+ headSha: null,
1956
+ danglingNote: `no ${missingLabel} verdict comment found on this PR`
1957
+ };
1958
+ }
1959
+ const latest = candidates[candidates.length - 1];
1960
+ const m = firstThreeLines(latest).match(valuePattern);
1961
+ if (!m) {
1962
+ return {
1963
+ value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
1964
+ headSha: null,
1965
+ danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
1966
+ };
1967
+ }
1968
+ return {
1969
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
1970
+ headSha: extractHeadSha(latest),
1971
+ danglingNote: null
1972
+ };
1973
+ }
1974
+ function extractCodeReviewVerdict(comments) {
1975
+ return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
1976
+ }
1977
+ function extractSecurityReviewVerdict(comments) {
1978
+ return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
1979
+ }
1887
1980
  // ../../packages/aeg-core/src/report-tokens.ts
1888
1981
  var DASH_LOOKALIKES = {
1889
1982
  "-": "‑",
@@ -2048,22 +2141,6 @@ function isCodeFile(p) {
2048
2141
  return false;
2049
2142
  return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
2050
2143
  }
2051
- // ../../packages/aeg-core/src/waiver-label.ts
2052
- var WAIVER_LABEL = label("waiver-docs");
2053
- var WAIVER_LABEL_REVIEW = label("waiver-review");
2054
- var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
2055
- function isPrincipal(login, principalAllowlist) {
2056
- if (login === null)
2057
- return false;
2058
- const normalized = login.toLowerCase();
2059
- return principalAllowlist.some((p) => p.toLowerCase() === normalized);
2060
- }
2061
- function isWaiverLabelActorVerified(opts) {
2062
- if (!opts.labels.includes(opts.label))
2063
- return false;
2064
- return isPrincipal(opts.labelActor, opts.principalAllowlist);
2065
- }
2066
-
2067
2144
  // ../../packages/aeg-core/src/doc-owners.ts
2068
2145
  var DOC_OWNERS_PATH = ".vinaya/doc-owners";
2069
2146
  function parseDocOwners(content) {
@@ -2280,6 +2357,32 @@ function parsePremiseBlock(prBody) {
2280
2357
  }
2281
2358
  return assertions;
2282
2359
  }
2360
+ function premiseBlockText(prBody) {
2361
+ const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
2362
+ const lines = searchIn.split(/\r?\n/);
2363
+ let startIdx = -1;
2364
+ let endIdxExclusive = lines.length;
2365
+ for (let i = 0;i < lines.length; i++) {
2366
+ const line = lines[i].trim();
2367
+ if (startIdx === -1) {
2368
+ if (isPremiseHeader(line))
2369
+ startIdx = i;
2370
+ continue;
2371
+ }
2372
+ if (line === "") {
2373
+ endIdxExclusive = i;
2374
+ break;
2375
+ }
2376
+ if (!PREMISE_LINE.test(line)) {
2377
+ endIdxExclusive = i;
2378
+ break;
2379
+ }
2380
+ }
2381
+ if (startIdx === -1)
2382
+ return null;
2383
+ return lines.slice(startIdx, endIdxExclusive).join(`
2384
+ `);
2385
+ }
2283
2386
  function checkPremises(assertions, fileReader) {
2284
2387
  const failures = [];
2285
2388
  for (const a of assertions) {
@@ -2366,15 +2469,18 @@ function testPlanRegion(prBody) {
2366
2469
  return located.found ? located.section : prBody;
2367
2470
  }
2368
2471
  function checkTestPlan(prBody) {
2369
- const region = testPlanRegion(prBody);
2472
+ const located = locateTestPlanSection(prBody);
2473
+ const region = located.found ? located.section : prBody;
2370
2474
  if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
2371
2475
  return { status: "pass", errors: [] };
2372
2476
  if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
2373
2477
  return { status: "pass", errors: [] };
2478
+ if (located.found && extractFencedBlocks(region).length > 0)
2479
+ return { status: "pass", errors: [] };
2374
2480
  return {
2375
2481
  status: "fail",
2376
2482
  errors: [
2377
- "brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
2483
+ "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."
2378
2484
  ]
2379
2485
  };
2380
2486
  }
@@ -2497,8 +2603,162 @@ function isBriefShaped(prBody) {
2497
2603
  const stripped = stripCode(prBody);
2498
2604
  return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
2499
2605
  }
2606
+ var COMMAND_WORDS = ["export", "bun", "gh", "git", "grep", "sed", "cat", "diff", "vinaya"];
2607
+ var BRIEF_RULES_SINCE_PR = 394;
2608
+ var AGENT_BOXES_REFUSED_SINCE_PR = 396;
2609
+ var AGENT_BOX_LINE_RE = /^-\s*\[[ xX]\]\s*\*{2}\[agent\]\*{2}/im;
2610
+ function checkNoAgentBoxes(prBody) {
2611
+ const region = testPlanRegion(prBody);
2612
+ if (!AGENT_BOX_LINE_RE.test(region))
2613
+ return { status: "pass", errors: [] };
2614
+ return {
2615
+ status: "fail",
2616
+ errors: [
2617
+ "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."
2618
+ ]
2619
+ };
2620
+ }
2621
+ function rolloutThresholdFor(message) {
2622
+ 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:")) {
2623
+ return BRIEF_RULES_SINCE_PR;
2624
+ }
2625
+ if (message.startsWith("brief-validation no agent boxes:"))
2626
+ return AGENT_BOXES_REFUSED_SINCE_PR;
2627
+ return null;
2628
+ }
2629
+ function partitionBriefErrorsByRollout(errors, prNumber) {
2630
+ const blocking = [];
2631
+ const info = [];
2632
+ for (const e of errors) {
2633
+ const threshold = rolloutThresholdFor(e);
2634
+ const grandfathered = threshold !== null && prNumber !== null && prNumber < threshold;
2635
+ (grandfathered ? info : blocking).push(e);
2636
+ }
2637
+ return { blocking, info };
2638
+ }
2639
+ function extractFencedBlocks(text) {
2640
+ const re = /^[ \t]*(`{3,}|~{3,})([^\n]*)\n([\s\S]*?)^[ \t]*\1[ \t]*$/gm;
2641
+ const blocks = [];
2642
+ let m = re.exec(text);
2643
+ while (m !== null) {
2644
+ const lang = m[2].trim().toLowerCase().split(/\s+/)[0] ?? "";
2645
+ blocks.push({ start: m.index, end: m.index + m[0].length, lang, content: m[3] });
2646
+ m = re.exec(text);
2647
+ }
2648
+ return blocks;
2649
+ }
2650
+ function firstNonBlankLine(text) {
2651
+ const line = text.split(`
2652
+ `).find((l) => l.trim().length > 0);
2653
+ return line?.trim() ?? "";
2654
+ }
2655
+ var FILE_LINE_RE = /\b[\w./-]+\.(?:tsx?|jsx?|mjs|cjs|md|mdx|json|ya?ml|sh|py|toml):\d+\b/g;
2656
+ function checkNoUnpinnedCodeClaims(prBody) {
2657
+ const premiseText = premiseBlockText(prBody);
2658
+ const withoutPremise = premiseText ? prBody.replace(premiseText, "") : prBody;
2659
+ const scanned = stripCode(withoutPremise, { inlineSpans: "keep" });
2660
+ const matches = scanned.match(FILE_LINE_RE) ?? [];
2661
+ if (matches.length === 0)
2662
+ return { status: "pass", errors: [] };
2663
+ return {
2664
+ status: "fail",
2665
+ 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.`)
2666
+ };
2667
+ }
2668
+ function extractNumberedSection(prBody, num) {
2669
+ const startRe = new RegExp(`^#{1,4}\\s*(?:\\*\\*)?${num}[a-z]?\\.\\s`, "im");
2670
+ const start = startRe.exec(prBody);
2671
+ if (!start)
2672
+ return null;
2673
+ const afterStart = start.index + start[0].length;
2674
+ const rest = prBody.slice(afterStart);
2675
+ const next = /^#{1,4}\s/m.exec(rest);
2676
+ return next ? rest.slice(0, next.index) : rest;
2677
+ }
2678
+ function commandWordOf(line) {
2679
+ return line.trim().split(/\s+/)[0] ?? "";
2680
+ }
2681
+ function isCommandBlock(content) {
2682
+ return COMMAND_WORDS.includes(commandWordOf(firstNonBlankLine(content)));
2683
+ }
2684
+ function isStep0Block(content) {
2685
+ return firstNonBlankLine(content).startsWith("git worktree add");
2686
+ }
2687
+ function checkCommandsCarryOutput(prBody) {
2688
+ const errors = [];
2689
+ for (const [label2, num] of [
2690
+ ["5", 5],
2691
+ ["6", 6]
2692
+ ]) {
2693
+ const section = extractNumberedSection(prBody, num);
2694
+ if (!section)
2695
+ continue;
2696
+ const blocks = extractFencedBlocks(section);
2697
+ for (let i = 0;i < blocks.length; i++) {
2698
+ const block = blocks[i];
2699
+ if (isStep0Block(block.content) || !isCommandBlock(block.content))
2700
+ continue;
2701
+ const next = blocks[i + 1];
2702
+ if (!next || isCommandBlock(next.content)) {
2703
+ 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.`);
2704
+ }
2705
+ }
2706
+ }
2707
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2708
+ }
2709
+ var CONSUMER_TESTS_SENTINEL_RE = /consumer-tests\s*:\s*none\s*[-—–]\s*\S/i;
2710
+ function packagesNamedIn(text) {
2711
+ const re = /\bpackages\/([A-Za-z0-9_-]+)\//g;
2712
+ const pkgs = new Set;
2713
+ let m = re.exec(text);
2714
+ while (m !== null) {
2715
+ pkgs.add(m[1]);
2716
+ m = re.exec(text);
2717
+ }
2718
+ return [...pkgs];
2719
+ }
2720
+ function hasTestPathForConsumer(text, consumerDir) {
2721
+ const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2722
+ const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2723
+ return re.test(text);
2724
+ }
2725
+ function checkConsumerTests(prBody, consumersOf) {
2726
+ const section4 = extractNumberedSection(prBody, 4);
2727
+ if (!section4)
2728
+ return { status: "pass", errors: [] };
2729
+ if (CONSUMER_TESTS_SENTINEL_RE.test(section4))
2730
+ return { status: "pass", errors: [] };
2731
+ const errors = [];
2732
+ for (const pkg of packagesNamedIn(section4)) {
2733
+ for (const consumerDir of consumersOf(pkg)) {
2734
+ if (hasTestPathForConsumer(section4, consumerDir))
2735
+ continue;
2736
+ 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>\`.`);
2737
+ }
2738
+ }
2739
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2740
+ }
2741
+ var CHECK_NAME_RE = /\bcheck-[a-z0-9-]+(?:\.ts)?\b/;
2742
+ 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;
2743
+ var DEFEAT_CASES_RE = /defeat cases\s*:/i;
2744
+ function checkDefeatCases(prBody) {
2745
+ const section4 = extractNumberedSection(prBody, 4);
2746
+ if (!section4)
2747
+ return { status: "pass", errors: [] };
2748
+ if (!CHECK_NAME_RE.test(section4) && !FORGE_WRITE_COMMAND_RE.test(section4))
2749
+ return { status: "pass", errors: [] };
2750
+ const section6 = extractNumberedSection(prBody, 6) ?? "";
2751
+ if (DEFEAT_CASES_RE.test(section6))
2752
+ return { status: "pass", errors: [] };
2753
+ return {
2754
+ status: "fail",
2755
+ errors: [
2756
+ "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."
2757
+ ]
2758
+ };
2759
+ }
2500
2760
  function checkBriefSections(prBody, readTier, options = {}) {
2501
- const { requireClosesN = true } = options;
2761
+ const { requireClosesN = true, consumersOf = () => [] } = options;
2502
2762
  const results = [
2503
2763
  checkTierField(prBody, readTier),
2504
2764
  checkTestPlan(prBody),
@@ -2511,10 +2771,219 @@ function checkBriefSections(prBody, readTier, options = {}) {
2511
2771
  checkAutonomyClause(prBody),
2512
2772
  checkProjectField(prBody),
2513
2773
  checkForField(prBody),
2774
+ checkNoUnpinnedCodeClaims(prBody),
2775
+ checkNoAgentBoxes(prBody),
2776
+ checkCommandsCarryOutput(prBody),
2777
+ checkConsumerTests(prBody, consumersOf),
2778
+ checkDefeatCases(prBody),
2514
2779
  ...requireClosesN ? [checkClosesN(prBody)] : []
2515
2780
  ];
2516
2781
  return { errors: results.flatMap((r) => r.errors) };
2517
2782
  }
2783
+ // ../../packages/aeg-core/src/doctrine-portability.ts
2784
+ var DEFAULT_SHIPS_PREFIX = "aeg-root/";
2785
+ var STATIC_PORTABLE_PREFIXES = [
2786
+ "roles/",
2787
+ "contracts/",
2788
+ "skills/",
2789
+ ".github/",
2790
+ ".vinaya/",
2791
+ ".claude/",
2792
+ ".git/",
2793
+ ".husky/"
2794
+ ];
2795
+ var EXEMPT_LITERALS = new Set([
2796
+ "path/inside/the/shipped/diff.ts",
2797
+ "path/inside/the/surface.ts",
2798
+ "apps/x/specs/..."
2799
+ ]);
2800
+ var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
2801
+ var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
2802
+ 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";
2803
+ var VENDOR_EXAMPLE_START = /<!--\s*AEG:VENDOR-EXAMPLE:START\s*-->/;
2804
+ var VENDOR_EXAMPLE_END = /<!--\s*AEG:VENDOR-EXAMPLE:END\s*-->/;
2805
+ function maskVendorExampleRegion(masked) {
2806
+ const start = VENDOR_EXAMPLE_START.exec(masked);
2807
+ if (!start)
2808
+ return masked;
2809
+ const afterStart = start.index + start[0].length;
2810
+ const end = VENDOR_EXAMPLE_END.exec(masked.slice(afterStart));
2811
+ if (!end)
2812
+ return masked;
2813
+ const regionEnd = afterStart + end.index + end[0].length;
2814
+ const region = masked.slice(start.index, regionEnd);
2815
+ const blanked = region.replace(/[^\n]/g, " ");
2816
+ return masked.slice(0, start.index) + blanked + masked.slice(regionEnd);
2817
+ }
2818
+ function extractVendorMentions(content) {
2819
+ const scoped = maskVendorExampleRegion(maskCode(content));
2820
+ const found = [];
2821
+ const pattern = new RegExp(VENDOR_NAME_SOURCE, "gi");
2822
+ let match = pattern.exec(scoped);
2823
+ while (match !== null) {
2824
+ found.push({ name: match[0], index: match.index });
2825
+ match = pattern.exec(scoped);
2826
+ }
2827
+ return found;
2828
+ }
2829
+ function extractCitedPaths(content) {
2830
+ const found = [];
2831
+ const spanPattern = /`([^`\n]+)`/g;
2832
+ let match = spanPattern.exec(content);
2833
+ while (match !== null) {
2834
+ const cited = match[1] ?? "";
2835
+ const topSegment = cited.slice(0, cited.indexOf("/"));
2836
+ if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
2837
+ found.push({ cited, index: match.index });
2838
+ }
2839
+ match = spanPattern.exec(content);
2840
+ }
2841
+ return found;
2842
+ }
2843
+ function lineAtIndex(content, index) {
2844
+ let line = 1;
2845
+ for (let i = 0;i < index; i++) {
2846
+ if (content.charCodeAt(i) === 10)
2847
+ line++;
2848
+ }
2849
+ return line;
2850
+ }
2851
+ function isPortable(cited, shipsPrefix) {
2852
+ if (cited.startsWith(shipsPrefix))
2853
+ return true;
2854
+ return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
2855
+ }
2856
+ function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
2857
+ const findings = [];
2858
+ for (const file of files) {
2859
+ if (!file.path.startsWith(shipsPrefix))
2860
+ continue;
2861
+ for (const { cited, index } of extractCitedPaths(file.content)) {
2862
+ if (EXEMPT_LITERALS.has(cited))
2863
+ continue;
2864
+ if (isPortable(cited, shipsPrefix))
2865
+ continue;
2866
+ findings.push({
2867
+ file: file.path,
2868
+ line: lineAtIndex(file.content, index),
2869
+ cited,
2870
+ message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`,
2871
+ kind: "path"
2872
+ });
2873
+ }
2874
+ for (const { name, index } of extractVendorMentions(file.content)) {
2875
+ findings.push({
2876
+ file: file.path,
2877
+ line: lineAtIndex(file.content, index),
2878
+ cited: name,
2879
+ 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`,
2880
+ kind: "vendor-name"
2881
+ });
2882
+ }
2883
+ }
2884
+ return findings;
2885
+ }
2886
+
2887
+ // ../../packages/aeg-core/src/doctrine-no-procedures.ts
2888
+ function vendorExampleSpan(content) {
2889
+ const start = VENDOR_EXAMPLE_START.exec(content);
2890
+ if (!start)
2891
+ return null;
2892
+ const afterStart = start.index + start[0].length;
2893
+ const end = VENDOR_EXAMPLE_END.exec(content.slice(afterStart));
2894
+ if (!end)
2895
+ return null;
2896
+ return { start: start.index, end: afterStart + end.index + end[0].length };
2897
+ }
2898
+ function isCommandLine(line) {
2899
+ const word = line.trim().split(/\s+/)[0] ?? "";
2900
+ return COMMAND_WORDS.includes(word);
2901
+ }
2902
+ var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
2903
+ function procedureLineNumberAt(content, index) {
2904
+ return content.slice(0, index).split(`
2905
+ `).length;
2906
+ }
2907
+ function isTemplatePath(path) {
2908
+ return /(^|\/)templates\//.test(path);
2909
+ }
2910
+ function checkDoctrineNoProcedures(files) {
2911
+ const findings = [];
2912
+ for (const file of files) {
2913
+ if (isTemplatePath(file.path))
2914
+ continue;
2915
+ const span = vendorExampleSpan(file.content);
2916
+ for (const block of extractFencedBlocks(file.content)) {
2917
+ if (span && block.start >= span.start && block.end <= span.end)
2918
+ continue;
2919
+ if (!SHELL_LANGS.has(block.lang))
2920
+ continue;
2921
+ const commandLineCount = block.content.split(`
2922
+ `).filter(isCommandLine).length;
2923
+ if (commandLineCount >= 2) {
2924
+ findings.push({
2925
+ file: file.path,
2926
+ line: procedureLineNumberAt(file.content, block.start),
2927
+ 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).`
2928
+ });
2929
+ }
2930
+ }
2931
+ }
2932
+ return findings;
2933
+ }
2934
+ // ../../packages/aeg-core/src/pr-report-density.ts
2935
+ function headingSectionBody(prBody, heading) {
2936
+ const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "im");
2937
+ const m = headingRe.exec(prBody);
2938
+ if (!m)
2939
+ return null;
2940
+ const start = m.index + m[0].length;
2941
+ const rest = prBody.slice(start);
2942
+ const nextHeading = /^#{1,2}\s/m.exec(rest);
2943
+ return nextHeading ? rest.slice(0, nextHeading.index) : rest;
2944
+ }
2945
+ function stripAnchorBlocks(sectionText) {
2946
+ let result = sectionText;
2947
+ for (const field of ["CLOSES", "PROJECT", "TIER", "PREMISE", "TEST-PLAN", "EVIDENCE"]) {
2948
+ for (;; ) {
2949
+ const bounds = anchoredRegionBounds(result, field);
2950
+ if (!bounds)
2951
+ break;
2952
+ result = result.slice(0, bounds.outerStart) + result.slice(bounds.outerEnd);
2953
+ }
2954
+ }
2955
+ return result;
2956
+ }
2957
+ function paragraphCount(sectionText) {
2958
+ const stripped = stripCode(stripAnchorBlocks(sectionText)).trim();
2959
+ if (stripped.length === 0)
2960
+ return 0;
2961
+ return stripped.split(/\n[ \t]*\n/).map((block) => block.trim()).filter((block) => block.length > 0).length;
2962
+ }
2963
+ function checkSectionDensity(prBody, heading) {
2964
+ const body = headingSectionBody(prBody, heading);
2965
+ if (body === null)
2966
+ return { status: "pass", errors: [] };
2967
+ const count = paragraphCount(body);
2968
+ if (count <= 1)
2969
+ return { status: "pass", errors: [] };
2970
+ return {
2971
+ status: "fail",
2972
+ errors: [
2973
+ `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.`
2974
+ ]
2975
+ };
2976
+ }
2977
+ function checkSummaryDensity(prBody) {
2978
+ return checkSectionDensity(prBody, "Summary");
2979
+ }
2980
+ function checkScopeDensity(prBody) {
2981
+ return checkSectionDensity(prBody, "Scope");
2982
+ }
2983
+ function checkPrReportDensity(prBody) {
2984
+ const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
2985
+ return { errors: results.flatMap((r) => r.errors) };
2986
+ }
2518
2987
  // ../../packages/aeg-core/src/issue-validation.ts
2519
2988
  function hasRationaleField(body, labelPattern) {
2520
2989
  const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
@@ -2766,6 +3235,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
2766
3235
  const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
2767
3236
  if (!depEntry)
2768
3237
  continue;
3238
+ const sameTask = depEntry.trancheSlug === e.trancheSlug && depEntry.task.id === e.task.id;
3239
+ const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3240
+ if (sameTask || sameIssue) {
3241
+ failures.push({
3242
+ issue: e.task.issue,
3243
+ tranche: e.trancheSlug,
3244
+ task: e.task.id,
3245
+ reason: `INTERNAL: parsed a self-dependency (depends-on ${dep}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream.`
3246
+ });
3247
+ continue;
3248
+ }
2769
3249
  const depFacts = depEntry.facts;
2770
3250
  const depClosed = depFacts?.issueState === "closed";
2771
3251
  if (!depClosed) {
@@ -2909,41 +3389,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2909
3389
  }
2910
3390
  return { ok: true, expectedIssue };
2911
3391
  }
2912
- // ../../packages/aeg-core/src/verdict-extraction.ts
2913
- var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2914
- function extractHeadSha(comment) {
2915
- const m = comment.match(HEAD_SHA_PATTERN);
2916
- return m ? m[1].toLowerCase() : null;
2917
- }
2918
- function extractVerdict(comments, valuePattern, missingLabel) {
2919
- const clearHits = comments.filter((c) => valuePattern.test(c));
2920
- if (clearHits.length > 0) {
2921
- const latest = clearHits[clearHits.length - 1];
2922
- const m = latest.match(valuePattern);
2923
- return {
2924
- value: m[1].toUpperCase().replace(/[_-]/g, " "),
2925
- headSha: extractHeadSha(latest),
2926
- danglingNote: null
2927
- };
2928
- }
2929
- return {
2930
- value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2931
- headSha: null,
2932
- danglingNote: `no ${missingLabel} verdict comment found on this PR`
2933
- };
2934
- }
2935
- function extractCodeReviewVerdict(comments) {
2936
- return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
2937
- }
2938
- function extractSecurityReviewVerdict(comments) {
2939
- return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2940
- }
2941
3392
  // ../../packages/aeg-core/src/review-gate.ts
2942
3393
  function isBoundToHead(extraction, headSha) {
2943
3394
  if (!extraction.headSha)
2944
3395
  return false;
2945
3396
  return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2946
3397
  }
3398
+ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3399
+ if (patchIdOf === undefined || !extraction.headSha)
3400
+ return false;
3401
+ const judged = patchIdOf(extraction.headSha);
3402
+ const current = patchIdOf(headSha);
3403
+ if (judged === null || current === null)
3404
+ return false;
3405
+ return judged === current;
3406
+ }
3407
+ function isBoundToPatch(extraction, headSha, patchIdOf) {
3408
+ return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3409
+ }
2947
3410
  function checkReviewGate(input) {
2948
3411
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
2949
3412
  const waived = isWaiverLabelActorVerified({
@@ -2959,6 +3422,8 @@ function checkReviewGate(input) {
2959
3422
  waived: true
2960
3423
  };
2961
3424
  }
3425
+ const reportedMechanicalChecks = input.mechanicalChecks.filter((c) => c.bucket !== "skipping" && c.bucket !== "neutral");
3426
+ const mechanicalChecksClean = reportedMechanicalChecks.length > 0 && reportedMechanicalChecks.every((c) => c.bucket === "pass");
2962
3427
  const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
2963
3428
  const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
2964
3429
  const verifiedBodies = verified.map((c) => c.body);
@@ -2966,12 +3431,12 @@ function checkReviewGate(input) {
2966
3431
  const security = extractSecurityReviewVerdict(verifiedBodies);
2967
3432
  const codeReviewClean = codeReview.value === "APPROVE";
2968
3433
  const securityClean = security.value === "PASS";
2969
- const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2970
- const securityBound = isBoundToHead(security, input.headSha);
2971
- if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
3434
+ const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3435
+ const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3436
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
2972
3437
  return {
2973
3438
  verdict: "pass",
2974
- reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
3439
+ 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.`,
2975
3440
  waived: false
2976
3441
  };
2977
3442
  }
@@ -2986,6 +3451,9 @@ function checkReviewGate(input) {
2986
3451
  } else if (!securityBound) {
2987
3452
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2988
3453
  }
3454
+ if (!mechanicalChecksClean) {
3455
+ 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(", ")}`);
3456
+ }
2989
3457
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2990
3458
  return {
2991
3459
  verdict: "fail",
@@ -3265,72 +3733,6 @@ function compareToBaseline(current, baseline) {
3265
3733
  perTool
3266
3734
  };
3267
3735
  }
3268
- // ../../packages/aeg-core/src/doctrine-portability.ts
3269
- var DEFAULT_SHIPS_PREFIX = "aeg-root/";
3270
- var STATIC_PORTABLE_PREFIXES = [
3271
- "roles/",
3272
- "contracts/",
3273
- "skills/",
3274
- ".github/",
3275
- ".vinaya/",
3276
- ".claude/",
3277
- ".git/",
3278
- ".husky/"
3279
- ];
3280
- var EXEMPT_LITERALS = new Set([
3281
- "path/inside/the/shipped/diff.ts",
3282
- "path/inside/the/surface.ts",
3283
- "apps/x/specs/..."
3284
- ]);
3285
- var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
3286
- var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
3287
- function extractCitedPaths(content) {
3288
- const found = [];
3289
- const spanPattern = /`([^`\n]+)`/g;
3290
- let match = spanPattern.exec(content);
3291
- while (match !== null) {
3292
- const cited = match[1] ?? "";
3293
- const topSegment = cited.slice(0, cited.indexOf("/"));
3294
- if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
3295
- found.push({ cited, index: match.index });
3296
- }
3297
- match = spanPattern.exec(content);
3298
- }
3299
- return found;
3300
- }
3301
- function lineAtIndex(content, index) {
3302
- let line = 1;
3303
- for (let i = 0;i < index; i++) {
3304
- if (content.charCodeAt(i) === 10)
3305
- line++;
3306
- }
3307
- return line;
3308
- }
3309
- function isPortable(cited, shipsPrefix) {
3310
- if (cited.startsWith(shipsPrefix))
3311
- return true;
3312
- return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
3313
- }
3314
- function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
3315
- const findings = [];
3316
- for (const file of files) {
3317
- if (!file.path.startsWith(shipsPrefix))
3318
- continue;
3319
- for (const { cited, index } of extractCitedPaths(file.content)) {
3320
- if (EXEMPT_LITERALS.has(cited))
3321
- continue;
3322
- if (isPortable(cited, shipsPrefix))
3323
- continue;
3324
- findings.push({
3325
- file: file.path,
3326
- line: lineAtIndex(file.content, index),
3327
- cited,
3328
- message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
3329
- });
3330
- }
3331
- }
3332
- return findings;
3333
- }
3334
3736
  // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3335
3737
  var SHIPS_PREFIX = "aeg-root/";
3336
3738
  function shipsArchivePrefix(shipsPrefix) {
@@ -3666,6 +4068,12 @@ function scanRetiredVocabulary(files) {
3666
4068
  function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
3667
4069
  return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
3668
4070
  }
4071
+ function isSelfDependency(dep, task, issue) {
4072
+ if (dep.issue !== null && issue !== null && dep.issue === issue.number)
4073
+ return true;
4074
+ const bare = dep.id.trim().replace(/^#/, "");
4075
+ return bare.toLowerCase() === task.id.trim().toLowerCase();
4076
+ }
3669
4077
  function checkDispatchReadiness(input) {
3670
4078
  const { trancheSlug, task } = input;
3671
4079
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
@@ -3680,6 +4088,11 @@ function checkDispatchReadiness(input) {
3680
4088
  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.`);
3681
4089
  }
3682
4090
  for (const dep of input.dependsOn) {
4091
+ if (isSelfDependency(dep, task, input.issue)) {
4092
+ const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4093
+ 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.`);
4094
+ continue;
4095
+ }
3683
4096
  if (dep.resolved === false) {
3684
4097
  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.`);
3685
4098
  continue;
@@ -3919,27 +4332,27 @@ function evaluateTestPlanGate(body, branch) {
3919
4332
  };
3920
4333
  }
3921
4334
  const checkboxLines = section.split(`
3922
- `).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]/.test(line));
4335
+ `).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
3923
4336
  if (checkboxLines.length === 0) {
3924
4337
  return {
3925
4338
  verdict: "pass",
3926
4339
  messages: [
3927
- "Test Plan section found but has no checkbox items.",
4340
+ "Test Plan section found but has no `[principal]` checkbox items.",
3928
4341
  "PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
3929
4342
  ]
3930
4343
  };
3931
4344
  }
3932
4345
  const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
3933
4346
  const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
3934
- const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
4347
+ const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
3935
4348
  if (unchecked.length > 0) {
3936
- messages.push("", "FAIL — the following Test Plan items are unticked:");
4349
+ messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
3937
4350
  for (const line of unchecked)
3938
4351
  messages.push(` ${line}`);
3939
- 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.");
4352
+ 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.");
3940
4353
  return { verdict: "fail", messages };
3941
4354
  }
3942
- messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
4355
+ messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
3943
4356
  return { verdict: "pass", messages };
3944
4357
  }
3945
4358
  // ../../packages/aeg-core/src/workspace-escape.ts