@attalabs/vinaya 0.21.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.
- package/README.md +15 -4
- package/aeg-root/contracts/archivist-tranche-archivist.md +2 -2
- package/aeg-root/contracts/brief-developer.md +1 -0
- package/aeg-root/contracts/developer-reviewer.md +3 -3
- package/aeg-root/contracts/reviewer-archivist.md +3 -3
- package/aeg-root/enforcement.md +28 -21
- package/aeg-root/process.md +8 -4
- package/aeg-root/roles/archivist.md +2 -2
- package/aeg-root/roles/brief-author.md +9 -3
- package/aeg-root/roles/developer.md +63 -31
- package/aeg-root/roles/planner.md +2 -2
- package/aeg-root/roles/principal.md +14 -0
- package/aeg-root/roles/reviewer.md +32 -15
- package/aeg-root/roles/security.md +15 -5
- package/aeg-root/skills/aeg/SKILL.md +9 -5
- package/aeg-root/skills/aeg-roles/SKILL.md +2 -2
- package/aeg-root/skills/brief-authoring/SKILL.md +39 -27
- package/aeg-root/state-machine.md +9 -11
- package/aeg-root/templates/brief-template.md +6 -6
- package/aeg-root/templates/pr-report-template.md +12 -7
- package/aeg-root/tranche-model.md +9 -5
- package/dist/checks/bin/check-body-bare-digits.js +667 -148
- package/dist/checks/bin/check-branch-topology.js +714 -149
- package/dist/checks/bin/check-brief-shape.js +704 -147
- package/dist/checks/bin/check-changeset-coverage.js +1046 -166
- package/dist/checks/bin/check-closes-n.js +714 -149
- package/dist/checks/bin/check-coherence.js +729 -156
- package/dist/checks/bin/check-dead-branch-push.js +663 -144
- package/dist/checks/bin/check-dispatch-readiness.js +728 -155
- package/dist/checks/bin/check-doc-coverage-push.js +838 -261
- package/dist/checks/bin/check-doc-coverage.js +838 -261
- package/dist/checks/bin/check-doctrine-no-procedures.js +4794 -0
- package/dist/checks/bin/check-doctrine-portability.js +799 -155
- package/dist/checks/bin/check-evidence-fresh.js +1132 -206
- package/dist/checks/bin/check-exec-bits.js +4931 -0
- package/dist/checks/bin/check-first-push-dispatch.js +716 -151
- package/dist/checks/bin/check-issue-assignment.js +714 -149
- package/dist/checks/bin/check-main-branch-refusal.js +663 -144
- package/dist/checks/bin/check-no-disk-state.js +663 -144
- package/dist/checks/bin/check-pr-report-density.js +4461 -0
- package/dist/checks/bin/check-quoted-command.js +741 -166
- package/dist/checks/bin/check-reader-resolvable-prose.js +768 -173
- package/dist/checks/bin/check-registry-gates.js +726 -154
- package/dist/checks/bin/check-retired-vocabulary.js +760 -165
- package/dist/checks/bin/check-review-gate.js +783 -148
- package/dist/checks/bin/check-single-plan-pr.js +663 -144
- package/dist/checks/bin/check-test-plan.js +664 -145
- package/dist/checks/bin/check-token-collection-wired.js +665 -146
- package/dist/checks/bin/check-token-report.js +4509 -0
- package/dist/checks/bin/check-workspace-escape.js +1129 -183
- package/dist/index.js +8458 -5403
- package/package.json +1 -1
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/actions.ts +173 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/anchored-region.ts +113 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/archive-task.ts +206 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/baseline-capture.ts +65 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/blast-radius-domains.ts +192 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/branch-topology-gate.ts +85 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-validation.ts +511 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/claude-code-transcript.ts +460 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/coherence-checks.ts +787 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-audit.ts +55 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-guard.ts +77 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-section7.ts +66 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-tranche.ts +94 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/diagram-model.ts +372 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/direct-main-push.ts +39 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dispatch-gate.ts +224 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doc-owners.ts +314 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/build-doc-nav.ts +38 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/docs-coherence.ts +136 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/index.ts +29 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/legacy-anchors.ts +48 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/nav-helpers.ts +17 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/node-route.ts +134 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/parse-doc.ts +43 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/published-prose.ts +306 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/surfaced-manifest.ts +89 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/types.ts +33 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-portability.ts +170 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-source.ts +23 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/ensure-label.ts +28 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/file-classify.ts +51 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/first-push-dispatch-gate.ts +80 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/gate-audience.ts +168 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/index.ts +301 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-assignment.ts +102 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-validation.ts +746 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/leftover-detection.ts +68 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/local-anchor-coverage.ts +245 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +64 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/manifest-validity.ts +81 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/markdown-table.ts +76 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/metering-io-guard.ts +119 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/milestone-validation.ts +240 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/no-disk-state.ts +50 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-ledger.ts +77 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-registry.ts +105 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-token-report.ts +165 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-tranche.ts +183 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/pr-tier.ts +81 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/premise-check.ts +125 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/quoted-command.ts +275 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/reader-resolvable-prose.ts +320 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-checks.ts +255 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-parse.ts +164 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-scaffold.ts +216 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/report-tokens.ts +261 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/retired-vocabulary.ts +163 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-gate.ts +269 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/single-plan-pr.ts +73 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-machine-model.ts +269 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-source.ts +14 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/status-block.ts +8 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/sum-ledger.ts +26 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/symbol-collisions.ts +78 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-gate.ts +104 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-section.ts +65 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/types.ts +174 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/verdict-extraction.ts +140 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/vocabulary-citation.ts +114 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/waiver-label.ts +52 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/workspace-escape.ts +172 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
- 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
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__02c71kv._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0ozbaq0._.js → [root-of-the-server]__1mcitst._.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0o54suy._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1-6rzip._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1mrtj0f._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_06it6s-._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_animate_components_index_ts_186fje1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_brutal_components_index_ts_0n_lcc4._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_retro_components_index_ts_0wvpm_z._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_retro_components_index_ts_1_cfi48._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/00pi8vhg8w6gi.js +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0r4i_ks9z02bw.js → 03q0q9n6-xah8.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2_k6j1o9q5nn4.js → 0jer_drkl1e4r.js} +4 -4
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0vc2u252q5q6o.js +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1k916ih3p4nsg.js → 13g16cu00g570.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2hmannd4bjhzy.js → 1sfxfunirkcr6.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1t2kft7bxzuxn.js → 206ajg8f1a-_h.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1hfhxjy577l5d.js → 20x-t0a0v129z.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{25kbt5k-xb2a5.js → 279qq4twinc97.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{112g08xpx3kro.js → 2ka8dg687auhj.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/2ou_81ahqw31e.js +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0up5x3jg-cwov.js → 2rn89uwtf2lwc.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3mqu_uhluy19i.css → 3qc67qhcvbg0o.css} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1yqs2zgvwa4p1.js → 3zugx9esh2x51.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0_jnwbarv-cqk.js +0 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1ybrihl5_aocy.js +0 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/3r5efmleneaco.js +0 -1
- /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
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{crNCHQ9uAgKOYC6DKkZma → 5xy_GA7G5KRuE3HDumIiw}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{crNCHQ9uAgKOYC6DKkZma → 5xy_GA7G5KRuE3HDumIiw}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{crNCHQ9uAgKOYC6DKkZma → 5xy_GA7G5KRuE3HDumIiw}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
// src/checks/bin/check-brief-shape.ts
|
|
6
|
+
import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
2
7
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
3
8
|
var GATE_AUDIENCE = {
|
|
4
9
|
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
@@ -207,17 +212,18 @@ function splitIds(raw) {
|
|
|
207
212
|
return [];
|
|
208
213
|
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
|
|
209
214
|
}
|
|
210
|
-
function resolveIds(raw
|
|
215
|
+
function resolveIds(raw) {
|
|
211
216
|
const ids = splitIds(raw);
|
|
212
217
|
const resolved = [];
|
|
218
|
+
let lastSlug = null;
|
|
213
219
|
for (const id of ids) {
|
|
214
220
|
const qualified = id.match(SLUG_QUALIFIED_ID);
|
|
215
221
|
if (qualified) {
|
|
216
|
-
lastSlug
|
|
222
|
+
lastSlug = qualified[1] ?? null;
|
|
217
223
|
resolved.push(id);
|
|
218
|
-
} else if (lastSlug
|
|
219
|
-
resolved.push(`${lastSlug
|
|
220
|
-
lastSlug
|
|
224
|
+
} else if (lastSlug) {
|
|
225
|
+
resolved.push(`${lastSlug} ${id}`);
|
|
226
|
+
lastSlug = null;
|
|
221
227
|
} else {
|
|
222
228
|
resolved.push(id);
|
|
223
229
|
}
|
|
@@ -237,12 +243,7 @@ function parseRationaleDeps(body) {
|
|
|
237
243
|
const section = extractSection(body);
|
|
238
244
|
const dependsOn = [];
|
|
239
245
|
const conflictsWith = [];
|
|
240
|
-
let current = null;
|
|
241
246
|
const assignedFields = new Set;
|
|
242
|
-
const lastSlugByField = {
|
|
243
|
-
dependsOn: { current: null },
|
|
244
|
-
conflictsWith: { current: null }
|
|
245
|
-
};
|
|
246
247
|
const spanPattern = /`([^`]*)`/g;
|
|
247
248
|
let match = spanPattern.exec(section);
|
|
248
249
|
while (match !== null) {
|
|
@@ -252,13 +253,9 @@ function parseRationaleDeps(body) {
|
|
|
252
253
|
const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
|
|
253
254
|
if (!assignedFields.has(field)) {
|
|
254
255
|
assignedFields.add(field);
|
|
255
|
-
|
|
256
|
-
const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
|
|
256
|
+
const ids = resolveIds(labelMatch[2] ?? "");
|
|
257
257
|
pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
258
258
|
}
|
|
259
|
-
} else if (current) {
|
|
260
|
-
const ids = resolveIds(content, lastSlugByField[current]);
|
|
261
|
-
pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
262
259
|
}
|
|
263
260
|
match = spanPattern.exec(section);
|
|
264
261
|
}
|
|
@@ -1723,6 +1720,48 @@ function splitTableRow(row) {
|
|
|
1723
1720
|
parts.pop();
|
|
1724
1721
|
return parts.map((p) => p.split(PLACEHOLDER).join("|"));
|
|
1725
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
|
+
}
|
|
1726
1765
|
// ../../packages/aeg-core/src/state-machine-model.ts
|
|
1727
1766
|
var FORGE_FACT_INPUTS = [
|
|
1728
1767
|
{
|
|
@@ -1788,6 +1827,156 @@ var DERIVED_STATUSES = [
|
|
|
1788
1827
|
"incoherent"
|
|
1789
1828
|
];
|
|
1790
1829
|
var DERIVABLE_STATUSES = DERIVED_STATUSES.filter((s) => s !== "backlog");
|
|
1830
|
+
// ../../packages/aeg-core/src/parse-ledger.ts
|
|
1831
|
+
function rowFromCells(cells) {
|
|
1832
|
+
if (cells.length < 7)
|
|
1833
|
+
return null;
|
|
1834
|
+
const phase = (cells[0] ?? "").trim();
|
|
1835
|
+
if (!phase)
|
|
1836
|
+
return null;
|
|
1837
|
+
return {
|
|
1838
|
+
phase,
|
|
1839
|
+
role: (cells[1] ?? "").trim(),
|
|
1840
|
+
agentModel: (cells[2] ?? "").trim(),
|
|
1841
|
+
tokensIn: parseIntCell(cells[3] ?? ""),
|
|
1842
|
+
tokensOut: parseIntCell(cells[4] ?? ""),
|
|
1843
|
+
cost: parseCostCell(cells[5] ?? ""),
|
|
1844
|
+
date: (cells[6] ?? "").trim()
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
function parseIntCell(cell) {
|
|
1848
|
+
const trimmed = cell.replace(/`/g, "").trim();
|
|
1849
|
+
if (!trimmed || isEmDashOrDash(trimmed))
|
|
1850
|
+
return null;
|
|
1851
|
+
const normalized = trimmed.replace(/[,_\s]/g, "");
|
|
1852
|
+
if (!/^-?\d+$/.test(normalized))
|
|
1853
|
+
return null;
|
|
1854
|
+
return Number(normalized);
|
|
1855
|
+
}
|
|
1856
|
+
function parseCostCell(cell) {
|
|
1857
|
+
const trimmed = cell.replace(/`/g, "").trim();
|
|
1858
|
+
if (!trimmed || isEmDashOrDash(trimmed))
|
|
1859
|
+
return null;
|
|
1860
|
+
const normalized = trimmed.replace(/^\$/, "").replace(/[,_\s]/g, "");
|
|
1861
|
+
if (!/^-?\d+(\.\d+)?$/.test(normalized))
|
|
1862
|
+
return null;
|
|
1863
|
+
return Number(normalized);
|
|
1864
|
+
}
|
|
1865
|
+
function isEmDashOrDash(s) {
|
|
1866
|
+
const t = s.trim();
|
|
1867
|
+
return t === "—" || t === "-" || t === "–";
|
|
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
|
+
|
|
1885
|
+
// ../../packages/aeg-core/src/parse-token-report.ts
|
|
1886
|
+
var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
|
|
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;
|
|
1888
|
+
function parseTokenReportEntries(body) {
|
|
1889
|
+
const lines = body.split(/\r?\n/);
|
|
1890
|
+
const headingIdxs = [];
|
|
1891
|
+
lines.forEach((line, i) => {
|
|
1892
|
+
if (TOKEN_REPORT_HEADING.test(line))
|
|
1893
|
+
headingIdxs.push(i);
|
|
1894
|
+
});
|
|
1895
|
+
const out = [];
|
|
1896
|
+
for (let h = 0;h < headingIdxs.length; h++) {
|
|
1897
|
+
const start = headingIdxs[h] + 1;
|
|
1898
|
+
const end = h + 1 < headingIdxs.length ? headingIdxs[h + 1] : lines.length;
|
|
1899
|
+
const section = lines.slice(start, end).join(`
|
|
1900
|
+
`);
|
|
1901
|
+
const tableRows = parseTableSection(section);
|
|
1902
|
+
if (tableRows.length > 0) {
|
|
1903
|
+
out.push(...tableRows);
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
const inlineRow = parseInlineFieldList(section);
|
|
1907
|
+
if (inlineRow)
|
|
1908
|
+
out.push(inlineRow);
|
|
1909
|
+
}
|
|
1910
|
+
return out;
|
|
1911
|
+
}
|
|
1912
|
+
function parseTableSection(section) {
|
|
1913
|
+
const lines = section.split(/\r?\n/);
|
|
1914
|
+
let i = 0;
|
|
1915
|
+
for (;i < lines.length; i++) {
|
|
1916
|
+
if ((lines[i] ?? "").trim().startsWith("|"))
|
|
1917
|
+
break;
|
|
1918
|
+
}
|
|
1919
|
+
if (i + 1 >= lines.length)
|
|
1920
|
+
return [];
|
|
1921
|
+
i += 2;
|
|
1922
|
+
const out = [];
|
|
1923
|
+
for (;i < lines.length; i++) {
|
|
1924
|
+
const trimmed = (lines[i] ?? "").trim();
|
|
1925
|
+
if (!trimmed.startsWith("|"))
|
|
1926
|
+
break;
|
|
1927
|
+
const row = rowFromCells(splitTableRow(trimmed));
|
|
1928
|
+
if (row)
|
|
1929
|
+
out.push(row);
|
|
1930
|
+
}
|
|
1931
|
+
return out;
|
|
1932
|
+
}
|
|
1933
|
+
function parseInlineFieldList(section) {
|
|
1934
|
+
const m = section.match(INLINE_FIELD_LIST);
|
|
1935
|
+
if (!m)
|
|
1936
|
+
return null;
|
|
1937
|
+
return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
|
|
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
|
+
}
|
|
1791
1980
|
// ../../packages/aeg-core/src/report-tokens.ts
|
|
1792
1981
|
var DASH_LOOKALIKES = {
|
|
1793
1982
|
"-": "‑",
|
|
@@ -1796,6 +1985,7 @@ var DASH_LOOKALIKES = {
|
|
|
1796
1985
|
};
|
|
1797
1986
|
var DASH_CHARS = new Set(Object.keys(DASH_LOOKALIKES));
|
|
1798
1987
|
// ../../packages/aeg-core/src/claude-code-transcript.ts
|
|
1988
|
+
import { createHash } from "node:crypto";
|
|
1799
1989
|
function summarizeTranscript(jsonl) {
|
|
1800
1990
|
const seen = new Set;
|
|
1801
1991
|
const components = {
|
|
@@ -1844,7 +2034,14 @@ function numberOr(value, fallback) {
|
|
|
1844
2034
|
function sanitizeKey(value) {
|
|
1845
2035
|
return value.replace(/[^A-Za-z0-9]+/g, "-");
|
|
1846
2036
|
}
|
|
2037
|
+
function collisionResistantKey(value) {
|
|
2038
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
2039
|
+
return `${sanitizeKey(value)}-${digest}`;
|
|
2040
|
+
}
|
|
1847
2041
|
function transcriptPointerPath(projectDir, tmpDir) {
|
|
2042
|
+
return `${tmpDir}/claude-transcript-${collisionResistantKey(projectDir)}.txt`;
|
|
2043
|
+
}
|
|
2044
|
+
function legacyTranscriptPointerPath(projectDir, tmpDir) {
|
|
1848
2045
|
return `${tmpDir}/claude-transcript-${sanitizeKey(projectDir)}.txt`;
|
|
1849
2046
|
}
|
|
1850
2047
|
function resolvePointer(explicitTranscriptPath, deps) {
|
|
@@ -1852,13 +2049,15 @@ function resolvePointer(explicitTranscriptPath, deps) {
|
|
|
1852
2049
|
return { path: explicitTranscriptPath, corroborated: true };
|
|
1853
2050
|
const projectDir = deps.env.CLAUDE_PROJECT_DIR ?? deps.cwd;
|
|
1854
2051
|
const tmpDir = deps.env.TMPDIR ?? "/tmp";
|
|
1855
|
-
const
|
|
2052
|
+
const primaryPointerPath = transcriptPointerPath(projectDir, tmpDir);
|
|
2053
|
+
const legacyPointerPath = legacyTranscriptPointerPath(projectDir, tmpDir);
|
|
1856
2054
|
const currentSessionId = deps.env.CLAUDE_CODE_SESSION_ID;
|
|
1857
|
-
|
|
2055
|
+
const pointerPath = deps.exists(primaryPointerPath) ? primaryPointerPath : deps.exists(legacyPointerPath) ? legacyPointerPath : undefined;
|
|
2056
|
+
if (!pointerPath) {
|
|
1858
2057
|
return {
|
|
1859
2058
|
pointerExisted: false,
|
|
1860
2059
|
corroborated: false,
|
|
1861
|
-
error: `No transcript pointer at ${
|
|
2060
|
+
error: `No transcript pointer at ${primaryPointerPath} (nor its pre-migration name ${legacyPointerPath}) ` + "and no --transcript given. Either this repo installs no track-transcript.sh Stop hook (lacking one " + "is not a defect — name the transcript directly instead), or no session has completed a turn yet."
|
|
1862
2061
|
};
|
|
1863
2062
|
}
|
|
1864
2063
|
let contents;
|
|
@@ -1933,28 +2132,15 @@ function isTokenCollectionWiringBroken(capability) {
|
|
|
1933
2132
|
return false;
|
|
1934
2133
|
return capability.reason !== "no-transcript-resolved";
|
|
1935
2134
|
}
|
|
2135
|
+
// ../../packages/aeg-core/src/metering-io-guard.ts
|
|
2136
|
+
import { closeSync, constants as fsConstants, fstatSync, openSync, readFileSync } from "node:fs";
|
|
2137
|
+
var GUARD_OPEN_FLAGS = fsConstants.O_RDONLY | fsConstants.O_NONBLOCK | fsConstants.O_NOFOLLOW;
|
|
1936
2138
|
// ../../packages/aeg-core/src/file-classify.ts
|
|
1937
2139
|
function isCodeFile(p) {
|
|
1938
2140
|
if (p.endsWith(".md"))
|
|
1939
2141
|
return false;
|
|
1940
2142
|
return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
|
|
1941
2143
|
}
|
|
1942
|
-
// ../../packages/aeg-core/src/waiver-label.ts
|
|
1943
|
-
var WAIVER_LABEL = label("waiver-docs");
|
|
1944
|
-
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
1945
|
-
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
1946
|
-
function isPrincipal(login, principalAllowlist) {
|
|
1947
|
-
if (login === null)
|
|
1948
|
-
return false;
|
|
1949
|
-
const normalized = login.toLowerCase();
|
|
1950
|
-
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
1951
|
-
}
|
|
1952
|
-
function isWaiverLabelActorVerified(opts) {
|
|
1953
|
-
if (!opts.labels.includes(opts.label))
|
|
1954
|
-
return false;
|
|
1955
|
-
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
2144
|
// ../../packages/aeg-core/src/doc-owners.ts
|
|
1959
2145
|
var DOC_OWNERS_PATH = ".vinaya/doc-owners";
|
|
1960
2146
|
function parseDocOwners(content) {
|
|
@@ -2139,7 +2325,7 @@ function overrideActive(opts) {
|
|
|
2139
2325
|
return false;
|
|
2140
2326
|
}
|
|
2141
2327
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2142
|
-
import { createHash } from "node:crypto";
|
|
2328
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2143
2329
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2144
2330
|
function isPremiseHeader(line) {
|
|
2145
2331
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2171,6 +2357,32 @@ function parsePremiseBlock(prBody) {
|
|
|
2171
2357
|
}
|
|
2172
2358
|
return assertions;
|
|
2173
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
|
+
}
|
|
2174
2386
|
function checkPremises(assertions, fileReader) {
|
|
2175
2387
|
const failures = [];
|
|
2176
2388
|
for (const a of assertions) {
|
|
@@ -2184,7 +2396,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2184
2396
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2185
2397
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2186
2398
|
} else if (a.kind === "sha256") {
|
|
2187
|
-
const actual =
|
|
2399
|
+
const actual = createHash2("sha256").update(content).digest("hex");
|
|
2188
2400
|
if (actual !== a.value.toLowerCase()) {
|
|
2189
2401
|
failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
|
|
2190
2402
|
}
|
|
@@ -2257,15 +2469,18 @@ function testPlanRegion(prBody) {
|
|
|
2257
2469
|
return located.found ? located.section : prBody;
|
|
2258
2470
|
}
|
|
2259
2471
|
function checkTestPlan(prBody) {
|
|
2260
|
-
const
|
|
2472
|
+
const located = locateTestPlanSection(prBody);
|
|
2473
|
+
const region = located.found ? located.section : prBody;
|
|
2261
2474
|
if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
|
|
2262
2475
|
return { status: "pass", errors: [] };
|
|
2263
2476
|
if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
|
|
2264
2477
|
return { status: "pass", errors: [] };
|
|
2478
|
+
if (located.found && extractFencedBlocks(region).length > 0)
|
|
2479
|
+
return { status: "pass", errors: [] };
|
|
2265
2480
|
return {
|
|
2266
2481
|
status: "fail",
|
|
2267
2482
|
errors: [
|
|
2268
|
-
"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."
|
|
2269
2484
|
]
|
|
2270
2485
|
};
|
|
2271
2486
|
}
|
|
@@ -2388,8 +2603,162 @@ function isBriefShaped(prBody) {
|
|
|
2388
2603
|
const stripped = stripCode(prBody);
|
|
2389
2604
|
return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
|
|
2390
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
|
+
}
|
|
2391
2760
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2392
|
-
const { requireClosesN = true } = options;
|
|
2761
|
+
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2393
2762
|
const results = [
|
|
2394
2763
|
checkTierField(prBody, readTier),
|
|
2395
2764
|
checkTestPlan(prBody),
|
|
@@ -2402,10 +2771,219 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2402
2771
|
checkAutonomyClause(prBody),
|
|
2403
2772
|
checkProjectField(prBody),
|
|
2404
2773
|
checkForField(prBody),
|
|
2774
|
+
checkNoUnpinnedCodeClaims(prBody),
|
|
2775
|
+
checkNoAgentBoxes(prBody),
|
|
2776
|
+
checkCommandsCarryOutput(prBody),
|
|
2777
|
+
checkConsumerTests(prBody, consumersOf),
|
|
2778
|
+
checkDefeatCases(prBody),
|
|
2405
2779
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2406
2780
|
];
|
|
2407
2781
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2408
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
|
+
}
|
|
2409
2987
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
2410
2988
|
function hasRationaleField(body, labelPattern) {
|
|
2411
2989
|
const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
|
|
@@ -2657,6 +3235,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
2657
3235
|
const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
|
|
2658
3236
|
if (!depEntry)
|
|
2659
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
|
+
}
|
|
2660
3249
|
const depFacts = depEntry.facts;
|
|
2661
3250
|
const depClosed = depFacts?.issueState === "closed";
|
|
2662
3251
|
if (!depClosed) {
|
|
@@ -2800,41 +3389,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2800
3389
|
}
|
|
2801
3390
|
return { ok: true, expectedIssue };
|
|
2802
3391
|
}
|
|
2803
|
-
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2804
|
-
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2805
|
-
function extractHeadSha(comment) {
|
|
2806
|
-
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2807
|
-
return m ? m[1].toLowerCase() : null;
|
|
2808
|
-
}
|
|
2809
|
-
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2810
|
-
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2811
|
-
if (clearHits.length > 0) {
|
|
2812
|
-
const latest = clearHits[clearHits.length - 1];
|
|
2813
|
-
const m = latest.match(valuePattern);
|
|
2814
|
-
return {
|
|
2815
|
-
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2816
|
-
headSha: extractHeadSha(latest),
|
|
2817
|
-
danglingNote: null
|
|
2818
|
-
};
|
|
2819
|
-
}
|
|
2820
|
-
return {
|
|
2821
|
-
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2822
|
-
headSha: null,
|
|
2823
|
-
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2824
|
-
};
|
|
2825
|
-
}
|
|
2826
|
-
function extractCodeReviewVerdict(comments) {
|
|
2827
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
2828
|
-
}
|
|
2829
|
-
function extractSecurityReviewVerdict(comments) {
|
|
2830
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
2831
|
-
}
|
|
2832
3392
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
2833
3393
|
function isBoundToHead(extraction, headSha) {
|
|
2834
3394
|
if (!extraction.headSha)
|
|
2835
3395
|
return false;
|
|
2836
3396
|
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2837
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
|
+
}
|
|
2838
3410
|
function checkReviewGate(input) {
|
|
2839
3411
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2840
3412
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2850,6 +3422,8 @@ function checkReviewGate(input) {
|
|
|
2850
3422
|
waived: true
|
|
2851
3423
|
};
|
|
2852
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");
|
|
2853
3427
|
const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
|
|
2854
3428
|
const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
|
|
2855
3429
|
const verifiedBodies = verified.map((c) => c.body);
|
|
@@ -2857,12 +3431,12 @@ function checkReviewGate(input) {
|
|
|
2857
3431
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2858
3432
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2859
3433
|
const securityClean = security.value === "PASS";
|
|
2860
|
-
const codeReviewBound =
|
|
2861
|
-
const securityBound =
|
|
2862
|
-
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) {
|
|
2863
3437
|
return {
|
|
2864
3438
|
verdict: "pass",
|
|
2865
|
-
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.`,
|
|
2866
3440
|
waived: false
|
|
2867
3441
|
};
|
|
2868
3442
|
}
|
|
@@ -2877,6 +3451,9 @@ function checkReviewGate(input) {
|
|
|
2877
3451
|
} else if (!securityBound) {
|
|
2878
3452
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2879
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
|
+
}
|
|
2880
3457
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2881
3458
|
return {
|
|
2882
3459
|
verdict: "fail",
|
|
@@ -3156,72 +3733,6 @@ function compareToBaseline(current, baseline) {
|
|
|
3156
3733
|
perTool
|
|
3157
3734
|
};
|
|
3158
3735
|
}
|
|
3159
|
-
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
3160
|
-
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
3161
|
-
var STATIC_PORTABLE_PREFIXES = [
|
|
3162
|
-
"roles/",
|
|
3163
|
-
"contracts/",
|
|
3164
|
-
"skills/",
|
|
3165
|
-
".github/",
|
|
3166
|
-
".vinaya/",
|
|
3167
|
-
".claude/",
|
|
3168
|
-
".git/",
|
|
3169
|
-
".husky/"
|
|
3170
|
-
];
|
|
3171
|
-
var EXEMPT_LITERALS = new Set([
|
|
3172
|
-
"path/inside/the/shipped/diff.ts",
|
|
3173
|
-
"path/inside/the/surface.ts",
|
|
3174
|
-
"apps/x/specs/..."
|
|
3175
|
-
]);
|
|
3176
|
-
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
3177
|
-
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
3178
|
-
function extractCitedPaths(content) {
|
|
3179
|
-
const found = [];
|
|
3180
|
-
const spanPattern = /`([^`\n]+)`/g;
|
|
3181
|
-
let match = spanPattern.exec(content);
|
|
3182
|
-
while (match !== null) {
|
|
3183
|
-
const cited = match[1] ?? "";
|
|
3184
|
-
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
3185
|
-
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
3186
|
-
found.push({ cited, index: match.index });
|
|
3187
|
-
}
|
|
3188
|
-
match = spanPattern.exec(content);
|
|
3189
|
-
}
|
|
3190
|
-
return found;
|
|
3191
|
-
}
|
|
3192
|
-
function lineAtIndex(content, index) {
|
|
3193
|
-
let line = 1;
|
|
3194
|
-
for (let i = 0;i < index; i++) {
|
|
3195
|
-
if (content.charCodeAt(i) === 10)
|
|
3196
|
-
line++;
|
|
3197
|
-
}
|
|
3198
|
-
return line;
|
|
3199
|
-
}
|
|
3200
|
-
function isPortable(cited, shipsPrefix) {
|
|
3201
|
-
if (cited.startsWith(shipsPrefix))
|
|
3202
|
-
return true;
|
|
3203
|
-
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
3204
|
-
}
|
|
3205
|
-
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
3206
|
-
const findings = [];
|
|
3207
|
-
for (const file of files) {
|
|
3208
|
-
if (!file.path.startsWith(shipsPrefix))
|
|
3209
|
-
continue;
|
|
3210
|
-
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
3211
|
-
if (EXEMPT_LITERALS.has(cited))
|
|
3212
|
-
continue;
|
|
3213
|
-
if (isPortable(cited, shipsPrefix))
|
|
3214
|
-
continue;
|
|
3215
|
-
findings.push({
|
|
3216
|
-
file: file.path,
|
|
3217
|
-
line: lineAtIndex(file.content, index),
|
|
3218
|
-
cited,
|
|
3219
|
-
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
|
|
3220
|
-
});
|
|
3221
|
-
}
|
|
3222
|
-
}
|
|
3223
|
-
return findings;
|
|
3224
|
-
}
|
|
3225
3736
|
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3226
3737
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3227
3738
|
function shipsArchivePrefix(shipsPrefix) {
|
|
@@ -3557,6 +4068,12 @@ function scanRetiredVocabulary(files) {
|
|
|
3557
4068
|
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
3558
4069
|
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
3559
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
|
+
}
|
|
3560
4077
|
function checkDispatchReadiness(input) {
|
|
3561
4078
|
const { trancheSlug, task } = input;
|
|
3562
4079
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
@@ -3571,6 +4088,11 @@ function checkDispatchReadiness(input) {
|
|
|
3571
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.`);
|
|
3572
4089
|
}
|
|
3573
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
|
+
}
|
|
3574
4096
|
if (dep.resolved === false) {
|
|
3575
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.`);
|
|
3576
4098
|
continue;
|
|
@@ -3810,27 +4332,27 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
3810
4332
|
};
|
|
3811
4333
|
}
|
|
3812
4334
|
const checkboxLines = section.split(`
|
|
3813
|
-
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]
|
|
4335
|
+
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
|
|
3814
4336
|
if (checkboxLines.length === 0) {
|
|
3815
4337
|
return {
|
|
3816
4338
|
verdict: "pass",
|
|
3817
4339
|
messages: [
|
|
3818
|
-
"Test Plan section found but has no checkbox items.",
|
|
4340
|
+
"Test Plan section found but has no `[principal]` checkbox items.",
|
|
3819
4341
|
"PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
|
|
3820
4342
|
]
|
|
3821
4343
|
};
|
|
3822
4344
|
}
|
|
3823
4345
|
const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
|
|
3824
4346
|
const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
|
|
3825
|
-
const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
4347
|
+
const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
3826
4348
|
if (unchecked.length > 0) {
|
|
3827
|
-
messages.push("", "FAIL — the following Test Plan items are unticked:");
|
|
4349
|
+
messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
|
|
3828
4350
|
for (const line of unchecked)
|
|
3829
4351
|
messages.push(` ${line}`);
|
|
3830
|
-
messages.push("", "Per aeg-root/roles/developer.md (Verification), a PR is not mergeable while any Test Plan box is unticked.", "
|
|
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.");
|
|
3831
4353
|
return { verdict: "fail", messages };
|
|
3832
4354
|
}
|
|
3833
|
-
messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
4355
|
+
messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
3834
4356
|
return { verdict: "pass", messages };
|
|
3835
4357
|
}
|
|
3836
4358
|
// ../../packages/aeg-core/src/workspace-escape.ts
|
|
@@ -3919,6 +4441,29 @@ function emitCheckError(error) {
|
|
|
3919
4441
|
|
|
3920
4442
|
// src/checks/bin/check-brief-shape.ts
|
|
3921
4443
|
var CHECK_NAME = "brief-shape";
|
|
4444
|
+
function listDirs(dir) {
|
|
4445
|
+
try {
|
|
4446
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
4447
|
+
} catch {
|
|
4448
|
+
return [];
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
function readJson(path) {
|
|
4452
|
+
try {
|
|
4453
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
4454
|
+
} catch {
|
|
4455
|
+
return {};
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
function readManifest(dir) {
|
|
4459
|
+
const manifest = readJson(`${dir}/package.json`);
|
|
4460
|
+
return Object.keys(manifest).length === 0 ? null : manifest;
|
|
4461
|
+
}
|
|
4462
|
+
function buildConsumersOf2() {
|
|
4463
|
+
const root = readJson("package.json");
|
|
4464
|
+
const workspaces = Array.isArray(root.workspaces) ? root.workspaces : [];
|
|
4465
|
+
return buildConsumersOf(workspaces, listDirs, readManifest);
|
|
4466
|
+
}
|
|
3922
4467
|
function main() {
|
|
3923
4468
|
const prBody = process.env.PR_BODY ?? "";
|
|
3924
4469
|
if (!prBody) {
|
|
@@ -3929,9 +4474,21 @@ function main() {
|
|
|
3929
4474
|
if (branch && !taskBranch && !isBriefShaped(prBody)) {
|
|
3930
4475
|
process.exit(0);
|
|
3931
4476
|
}
|
|
3932
|
-
const { errors } = checkBriefSections(prBody, readTierFromPrBody, {
|
|
3933
|
-
|
|
3934
|
-
|
|
4477
|
+
const { errors } = checkBriefSections(prBody, readTierFromPrBody, {
|
|
4478
|
+
requireClosesN: taskBranch,
|
|
4479
|
+
consumersOf: buildConsumersOf2()
|
|
4480
|
+
});
|
|
4481
|
+
const parsedPrNumber = Number.parseInt(process.env.PR_NUMBER ?? "", 10);
|
|
4482
|
+
const prNumber = Number.isInteger(parsedPrNumber) ? parsedPrNumber : null;
|
|
4483
|
+
const { blocking, info } = partitionBriefErrorsByRollout(errors, prNumber);
|
|
4484
|
+
if (info.length > 0) {
|
|
4485
|
+
process.stdout.write(`${CHECK_NAME}: PR #${prNumber} is below BRIEF_RULES_SINCE_PR #${BRIEF_RULES_SINCE_PR} — ${info.length} finding(s) grandfathered, not a failure:
|
|
4486
|
+
${info.join(`
|
|
4487
|
+
`)}
|
|
4488
|
+
`);
|
|
4489
|
+
}
|
|
4490
|
+
if (blocking.length > 0) {
|
|
4491
|
+
for (const message of blocking) {
|
|
3935
4492
|
emitCheckError({
|
|
3936
4493
|
schema: CHECK_SCHEMA_VERSION,
|
|
3937
4494
|
check: CHECK_NAME,
|