@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,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
2
4
|
|
|
3
5
|
// src/checks/bin/check-doc-coverage.ts
|
|
4
6
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
5
|
-
import { existsSync as existsSync2, readFileSync as
|
|
7
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
6
8
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
7
9
|
var GATE_AUDIENCE = {
|
|
8
10
|
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
@@ -211,17 +213,18 @@ function splitIds(raw) {
|
|
|
211
213
|
return [];
|
|
212
214
|
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
|
|
213
215
|
}
|
|
214
|
-
function resolveIds(raw
|
|
216
|
+
function resolveIds(raw) {
|
|
215
217
|
const ids = splitIds(raw);
|
|
216
218
|
const resolved = [];
|
|
219
|
+
let lastSlug = null;
|
|
217
220
|
for (const id of ids) {
|
|
218
221
|
const qualified = id.match(SLUG_QUALIFIED_ID);
|
|
219
222
|
if (qualified) {
|
|
220
|
-
lastSlug
|
|
223
|
+
lastSlug = qualified[1] ?? null;
|
|
221
224
|
resolved.push(id);
|
|
222
|
-
} else if (lastSlug
|
|
223
|
-
resolved.push(`${lastSlug
|
|
224
|
-
lastSlug
|
|
225
|
+
} else if (lastSlug) {
|
|
226
|
+
resolved.push(`${lastSlug} ${id}`);
|
|
227
|
+
lastSlug = null;
|
|
225
228
|
} else {
|
|
226
229
|
resolved.push(id);
|
|
227
230
|
}
|
|
@@ -241,12 +244,7 @@ function parseRationaleDeps(body) {
|
|
|
241
244
|
const section = extractSection(body);
|
|
242
245
|
const dependsOn = [];
|
|
243
246
|
const conflictsWith = [];
|
|
244
|
-
let current = null;
|
|
245
247
|
const assignedFields = new Set;
|
|
246
|
-
const lastSlugByField = {
|
|
247
|
-
dependsOn: { current: null },
|
|
248
|
-
conflictsWith: { current: null }
|
|
249
|
-
};
|
|
250
248
|
const spanPattern = /`([^`]*)`/g;
|
|
251
249
|
let match = spanPattern.exec(section);
|
|
252
250
|
while (match !== null) {
|
|
@@ -256,13 +254,9 @@ function parseRationaleDeps(body) {
|
|
|
256
254
|
const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
|
|
257
255
|
if (!assignedFields.has(field)) {
|
|
258
256
|
assignedFields.add(field);
|
|
259
|
-
|
|
260
|
-
const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
|
|
257
|
+
const ids = resolveIds(labelMatch[2] ?? "");
|
|
261
258
|
pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
262
259
|
}
|
|
263
|
-
} else if (current) {
|
|
264
|
-
const ids = resolveIds(content, lastSlugByField[current]);
|
|
265
|
-
pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
266
260
|
}
|
|
267
261
|
match = spanPattern.exec(section);
|
|
268
262
|
}
|
|
@@ -1727,6 +1721,48 @@ function splitTableRow(row) {
|
|
|
1727
1721
|
parts.pop();
|
|
1728
1722
|
return parts.map((p) => p.split(PLACEHOLDER).join("|"));
|
|
1729
1723
|
}
|
|
1724
|
+
// ../../packages/aeg-core/src/blast-radius-domains.ts
|
|
1725
|
+
function resolveWorkspaceEntry(entry2, listDirs) {
|
|
1726
|
+
if (entry2.endsWith("/*")) {
|
|
1727
|
+
const baseDir = entry2.slice(0, -2);
|
|
1728
|
+
return listDirs(baseDir).map((name) => `${baseDir}/${name}`);
|
|
1729
|
+
}
|
|
1730
|
+
if (entry2.includes("*"))
|
|
1731
|
+
return [];
|
|
1732
|
+
return [entry2];
|
|
1733
|
+
}
|
|
1734
|
+
// ../../packages/aeg-core/src/consumer-enumeration.ts
|
|
1735
|
+
function deriveWorkspaceMemberDirs(workspaces, listDirs) {
|
|
1736
|
+
const positive = workspaces.filter((entry2) => !entry2.startsWith("!"));
|
|
1737
|
+
const negative = workspaces.filter((entry2) => entry2.startsWith("!")).map((entry2) => entry2.slice(1));
|
|
1738
|
+
const excluded = new Set;
|
|
1739
|
+
for (const entry2 of negative) {
|
|
1740
|
+
for (const member of resolveWorkspaceEntry(entry2, listDirs))
|
|
1741
|
+
excluded.add(member);
|
|
1742
|
+
}
|
|
1743
|
+
const members = new Set;
|
|
1744
|
+
for (const entry2 of positive) {
|
|
1745
|
+
for (const member of resolveWorkspaceEntry(entry2, listDirs)) {
|
|
1746
|
+
if (!excluded.has(member))
|
|
1747
|
+
members.add(member);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
return [...members].sort();
|
|
1751
|
+
}
|
|
1752
|
+
function buildConsumersOf(workspaces, listDirs, readManifest) {
|
|
1753
|
+
const members = deriveWorkspaceMemberDirs(workspaces, listDirs);
|
|
1754
|
+
return (pkg) => {
|
|
1755
|
+
const target = `@attalabs/${pkg}`;
|
|
1756
|
+
return members.filter((dir) => {
|
|
1757
|
+
if (dir === `packages/${pkg}`)
|
|
1758
|
+
return false;
|
|
1759
|
+
const manifest = readManifest(dir);
|
|
1760
|
+
if (!manifest)
|
|
1761
|
+
return false;
|
|
1762
|
+
return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {});
|
|
1763
|
+
});
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1730
1766
|
// ../../packages/aeg-core/src/state-machine-model.ts
|
|
1731
1767
|
var FORGE_FACT_INPUTS = [
|
|
1732
1768
|
{
|
|
@@ -1792,6 +1828,156 @@ var DERIVED_STATUSES = [
|
|
|
1792
1828
|
"incoherent"
|
|
1793
1829
|
];
|
|
1794
1830
|
var DERIVABLE_STATUSES = DERIVED_STATUSES.filter((s) => s !== "backlog");
|
|
1831
|
+
// ../../packages/aeg-core/src/parse-ledger.ts
|
|
1832
|
+
function rowFromCells(cells) {
|
|
1833
|
+
if (cells.length < 7)
|
|
1834
|
+
return null;
|
|
1835
|
+
const phase = (cells[0] ?? "").trim();
|
|
1836
|
+
if (!phase)
|
|
1837
|
+
return null;
|
|
1838
|
+
return {
|
|
1839
|
+
phase,
|
|
1840
|
+
role: (cells[1] ?? "").trim(),
|
|
1841
|
+
agentModel: (cells[2] ?? "").trim(),
|
|
1842
|
+
tokensIn: parseIntCell(cells[3] ?? ""),
|
|
1843
|
+
tokensOut: parseIntCell(cells[4] ?? ""),
|
|
1844
|
+
cost: parseCostCell(cells[5] ?? ""),
|
|
1845
|
+
date: (cells[6] ?? "").trim()
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
function parseIntCell(cell) {
|
|
1849
|
+
const trimmed = cell.replace(/`/g, "").trim();
|
|
1850
|
+
if (!trimmed || isEmDashOrDash(trimmed))
|
|
1851
|
+
return null;
|
|
1852
|
+
const normalized = trimmed.replace(/[,_\s]/g, "");
|
|
1853
|
+
if (!/^-?\d+$/.test(normalized))
|
|
1854
|
+
return null;
|
|
1855
|
+
return Number(normalized);
|
|
1856
|
+
}
|
|
1857
|
+
function parseCostCell(cell) {
|
|
1858
|
+
const trimmed = cell.replace(/`/g, "").trim();
|
|
1859
|
+
if (!trimmed || isEmDashOrDash(trimmed))
|
|
1860
|
+
return null;
|
|
1861
|
+
const normalized = trimmed.replace(/^\$/, "").replace(/[,_\s]/g, "");
|
|
1862
|
+
if (!/^-?\d+(\.\d+)?$/.test(normalized))
|
|
1863
|
+
return null;
|
|
1864
|
+
return Number(normalized);
|
|
1865
|
+
}
|
|
1866
|
+
function isEmDashOrDash(s) {
|
|
1867
|
+
const t = s.trim();
|
|
1868
|
+
return t === "—" || t === "-" || t === "–";
|
|
1869
|
+
}
|
|
1870
|
+
// ../../packages/aeg-core/src/waiver-label.ts
|
|
1871
|
+
var WAIVER_LABEL = label("waiver-docs");
|
|
1872
|
+
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
1873
|
+
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
1874
|
+
function isPrincipal(login, principalAllowlist) {
|
|
1875
|
+
if (login === null)
|
|
1876
|
+
return false;
|
|
1877
|
+
const normalized = login.toLowerCase();
|
|
1878
|
+
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
1879
|
+
}
|
|
1880
|
+
function isWaiverLabelActorVerified(opts) {
|
|
1881
|
+
if (!opts.labels.includes(opts.label))
|
|
1882
|
+
return false;
|
|
1883
|
+
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// ../../packages/aeg-core/src/parse-token-report.ts
|
|
1887
|
+
var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
|
|
1888
|
+
var INLINE_FIELD_LIST = /Phase:\s*([^|]+?)\s*\|\s*Role:\s*([^|]+?)\s*\|\s*Agent\/Model:\s*([^|]+?)\s*\|\s*Tokens in:\s*([^|]+?)\s*\|\s*Tokens out:\s*([^|]+?)\s*\|\s*Cost:\s*([^|]+?)\s*\|\s*Date:\s*(.+?)\s*$/im;
|
|
1889
|
+
function parseTokenReportEntries(body) {
|
|
1890
|
+
const lines = body.split(/\r?\n/);
|
|
1891
|
+
const headingIdxs = [];
|
|
1892
|
+
lines.forEach((line, i) => {
|
|
1893
|
+
if (TOKEN_REPORT_HEADING.test(line))
|
|
1894
|
+
headingIdxs.push(i);
|
|
1895
|
+
});
|
|
1896
|
+
const out = [];
|
|
1897
|
+
for (let h = 0;h < headingIdxs.length; h++) {
|
|
1898
|
+
const start = headingIdxs[h] + 1;
|
|
1899
|
+
const end = h + 1 < headingIdxs.length ? headingIdxs[h + 1] : lines.length;
|
|
1900
|
+
const section = lines.slice(start, end).join(`
|
|
1901
|
+
`);
|
|
1902
|
+
const tableRows = parseTableSection(section);
|
|
1903
|
+
if (tableRows.length > 0) {
|
|
1904
|
+
out.push(...tableRows);
|
|
1905
|
+
continue;
|
|
1906
|
+
}
|
|
1907
|
+
const inlineRow = parseInlineFieldList(section);
|
|
1908
|
+
if (inlineRow)
|
|
1909
|
+
out.push(inlineRow);
|
|
1910
|
+
}
|
|
1911
|
+
return out;
|
|
1912
|
+
}
|
|
1913
|
+
function parseTableSection(section) {
|
|
1914
|
+
const lines = section.split(/\r?\n/);
|
|
1915
|
+
let i = 0;
|
|
1916
|
+
for (;i < lines.length; i++) {
|
|
1917
|
+
if ((lines[i] ?? "").trim().startsWith("|"))
|
|
1918
|
+
break;
|
|
1919
|
+
}
|
|
1920
|
+
if (i + 1 >= lines.length)
|
|
1921
|
+
return [];
|
|
1922
|
+
i += 2;
|
|
1923
|
+
const out = [];
|
|
1924
|
+
for (;i < lines.length; i++) {
|
|
1925
|
+
const trimmed = (lines[i] ?? "").trim();
|
|
1926
|
+
if (!trimmed.startsWith("|"))
|
|
1927
|
+
break;
|
|
1928
|
+
const row = rowFromCells(splitTableRow(trimmed));
|
|
1929
|
+
if (row)
|
|
1930
|
+
out.push(row);
|
|
1931
|
+
}
|
|
1932
|
+
return out;
|
|
1933
|
+
}
|
|
1934
|
+
function parseInlineFieldList(section) {
|
|
1935
|
+
const m = section.match(INLINE_FIELD_LIST);
|
|
1936
|
+
if (!m)
|
|
1937
|
+
return null;
|
|
1938
|
+
return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
|
|
1939
|
+
}
|
|
1940
|
+
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1941
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1942
|
+
function firstThreeLines(comment) {
|
|
1943
|
+
return comment.split(`
|
|
1944
|
+
`).slice(0, 3).join(`
|
|
1945
|
+
`);
|
|
1946
|
+
}
|
|
1947
|
+
function extractHeadSha(comment) {
|
|
1948
|
+
const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
|
|
1949
|
+
return m ? m[1].toLowerCase() : null;
|
|
1950
|
+
}
|
|
1951
|
+
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1952
|
+
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1953
|
+
if (candidates.length === 0) {
|
|
1954
|
+
return {
|
|
1955
|
+
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1956
|
+
headSha: null,
|
|
1957
|
+
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
const latest = candidates[candidates.length - 1];
|
|
1961
|
+
const m = firstThreeLines(latest).match(valuePattern);
|
|
1962
|
+
if (!m) {
|
|
1963
|
+
return {
|
|
1964
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
|
|
1965
|
+
headSha: null,
|
|
1966
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
return {
|
|
1970
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1971
|
+
headSha: extractHeadSha(latest),
|
|
1972
|
+
danglingNote: null
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
function extractCodeReviewVerdict(comments) {
|
|
1976
|
+
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
1977
|
+
}
|
|
1978
|
+
function extractSecurityReviewVerdict(comments) {
|
|
1979
|
+
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
1980
|
+
}
|
|
1795
1981
|
// ../../packages/aeg-core/src/report-tokens.ts
|
|
1796
1982
|
var DASH_LOOKALIKES = {
|
|
1797
1983
|
"-": "‑",
|
|
@@ -1800,6 +1986,7 @@ var DASH_LOOKALIKES = {
|
|
|
1800
1986
|
};
|
|
1801
1987
|
var DASH_CHARS = new Set(Object.keys(DASH_LOOKALIKES));
|
|
1802
1988
|
// ../../packages/aeg-core/src/claude-code-transcript.ts
|
|
1989
|
+
import { createHash } from "node:crypto";
|
|
1803
1990
|
function summarizeTranscript(jsonl) {
|
|
1804
1991
|
const seen = new Set;
|
|
1805
1992
|
const components = {
|
|
@@ -1848,7 +2035,14 @@ function numberOr(value, fallback) {
|
|
|
1848
2035
|
function sanitizeKey(value) {
|
|
1849
2036
|
return value.replace(/[^A-Za-z0-9]+/g, "-");
|
|
1850
2037
|
}
|
|
2038
|
+
function collisionResistantKey(value) {
|
|
2039
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
2040
|
+
return `${sanitizeKey(value)}-${digest}`;
|
|
2041
|
+
}
|
|
1851
2042
|
function transcriptPointerPath(projectDir, tmpDir) {
|
|
2043
|
+
return `${tmpDir}/claude-transcript-${collisionResistantKey(projectDir)}.txt`;
|
|
2044
|
+
}
|
|
2045
|
+
function legacyTranscriptPointerPath(projectDir, tmpDir) {
|
|
1852
2046
|
return `${tmpDir}/claude-transcript-${sanitizeKey(projectDir)}.txt`;
|
|
1853
2047
|
}
|
|
1854
2048
|
function resolvePointer(explicitTranscriptPath, deps) {
|
|
@@ -1856,13 +2050,15 @@ function resolvePointer(explicitTranscriptPath, deps) {
|
|
|
1856
2050
|
return { path: explicitTranscriptPath, corroborated: true };
|
|
1857
2051
|
const projectDir = deps.env.CLAUDE_PROJECT_DIR ?? deps.cwd;
|
|
1858
2052
|
const tmpDir = deps.env.TMPDIR ?? "/tmp";
|
|
1859
|
-
const
|
|
2053
|
+
const primaryPointerPath = transcriptPointerPath(projectDir, tmpDir);
|
|
2054
|
+
const legacyPointerPath = legacyTranscriptPointerPath(projectDir, tmpDir);
|
|
1860
2055
|
const currentSessionId = deps.env.CLAUDE_CODE_SESSION_ID;
|
|
1861
|
-
|
|
2056
|
+
const pointerPath = deps.exists(primaryPointerPath) ? primaryPointerPath : deps.exists(legacyPointerPath) ? legacyPointerPath : undefined;
|
|
2057
|
+
if (!pointerPath) {
|
|
1862
2058
|
return {
|
|
1863
2059
|
pointerExisted: false,
|
|
1864
2060
|
corroborated: false,
|
|
1865
|
-
error: `No transcript pointer at ${
|
|
2061
|
+
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."
|
|
1866
2062
|
};
|
|
1867
2063
|
}
|
|
1868
2064
|
let contents;
|
|
@@ -1937,28 +2133,15 @@ function isTokenCollectionWiringBroken(capability) {
|
|
|
1937
2133
|
return false;
|
|
1938
2134
|
return capability.reason !== "no-transcript-resolved";
|
|
1939
2135
|
}
|
|
2136
|
+
// ../../packages/aeg-core/src/metering-io-guard.ts
|
|
2137
|
+
import { closeSync, constants as fsConstants, fstatSync, openSync, readFileSync } from "node:fs";
|
|
2138
|
+
var GUARD_OPEN_FLAGS = fsConstants.O_RDONLY | fsConstants.O_NONBLOCK | fsConstants.O_NOFOLLOW;
|
|
1940
2139
|
// ../../packages/aeg-core/src/file-classify.ts
|
|
1941
2140
|
function isCodeFile(p) {
|
|
1942
2141
|
if (p.endsWith(".md"))
|
|
1943
2142
|
return false;
|
|
1944
2143
|
return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
|
|
1945
2144
|
}
|
|
1946
|
-
// ../../packages/aeg-core/src/waiver-label.ts
|
|
1947
|
-
var WAIVER_LABEL = label("waiver-docs");
|
|
1948
|
-
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
1949
|
-
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
1950
|
-
function isPrincipal(login, principalAllowlist) {
|
|
1951
|
-
if (login === null)
|
|
1952
|
-
return false;
|
|
1953
|
-
const normalized = login.toLowerCase();
|
|
1954
|
-
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
1955
|
-
}
|
|
1956
|
-
function isWaiverLabelActorVerified(opts) {
|
|
1957
|
-
if (!opts.labels.includes(opts.label))
|
|
1958
|
-
return false;
|
|
1959
|
-
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
2145
|
// ../../packages/aeg-core/src/doc-owners.ts
|
|
1963
2146
|
var DOC_OWNERS_PATH = ".vinaya/doc-owners";
|
|
1964
2147
|
function parseDocOwners(content) {
|
|
@@ -2143,7 +2326,7 @@ function overrideActive(opts) {
|
|
|
2143
2326
|
return false;
|
|
2144
2327
|
}
|
|
2145
2328
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2146
|
-
import { createHash } from "node:crypto";
|
|
2329
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2147
2330
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2148
2331
|
function isPremiseHeader(line) {
|
|
2149
2332
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2175,6 +2358,32 @@ function parsePremiseBlock(prBody) {
|
|
|
2175
2358
|
}
|
|
2176
2359
|
return assertions;
|
|
2177
2360
|
}
|
|
2361
|
+
function premiseBlockText(prBody) {
|
|
2362
|
+
const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
|
|
2363
|
+
const lines = searchIn.split(/\r?\n/);
|
|
2364
|
+
let startIdx = -1;
|
|
2365
|
+
let endIdxExclusive = lines.length;
|
|
2366
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2367
|
+
const line = lines[i].trim();
|
|
2368
|
+
if (startIdx === -1) {
|
|
2369
|
+
if (isPremiseHeader(line))
|
|
2370
|
+
startIdx = i;
|
|
2371
|
+
continue;
|
|
2372
|
+
}
|
|
2373
|
+
if (line === "") {
|
|
2374
|
+
endIdxExclusive = i;
|
|
2375
|
+
break;
|
|
2376
|
+
}
|
|
2377
|
+
if (!PREMISE_LINE.test(line)) {
|
|
2378
|
+
endIdxExclusive = i;
|
|
2379
|
+
break;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
if (startIdx === -1)
|
|
2383
|
+
return null;
|
|
2384
|
+
return lines.slice(startIdx, endIdxExclusive).join(`
|
|
2385
|
+
`);
|
|
2386
|
+
}
|
|
2178
2387
|
function checkPremises(assertions, fileReader) {
|
|
2179
2388
|
const failures = [];
|
|
2180
2389
|
for (const a of assertions) {
|
|
@@ -2188,7 +2397,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2188
2397
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2189
2398
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2190
2399
|
} else if (a.kind === "sha256") {
|
|
2191
|
-
const actual =
|
|
2400
|
+
const actual = createHash2("sha256").update(content).digest("hex");
|
|
2192
2401
|
if (actual !== a.value.toLowerCase()) {
|
|
2193
2402
|
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.`);
|
|
2194
2403
|
}
|
|
@@ -2261,15 +2470,18 @@ function testPlanRegion(prBody) {
|
|
|
2261
2470
|
return located.found ? located.section : prBody;
|
|
2262
2471
|
}
|
|
2263
2472
|
function checkTestPlan(prBody) {
|
|
2264
|
-
const
|
|
2473
|
+
const located = locateTestPlanSection(prBody);
|
|
2474
|
+
const region = located.found ? located.section : prBody;
|
|
2265
2475
|
if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
|
|
2266
2476
|
return { status: "pass", errors: [] };
|
|
2267
2477
|
if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
|
|
2268
2478
|
return { status: "pass", errors: [] };
|
|
2479
|
+
if (located.found && extractFencedBlocks(region).length > 0)
|
|
2480
|
+
return { status: "pass", errors: [] };
|
|
2269
2481
|
return {
|
|
2270
2482
|
status: "fail",
|
|
2271
2483
|
errors: [
|
|
2272
|
-
"brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
|
|
2484
|
+
"brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, a fenced `[agent]` command list, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
|
|
2273
2485
|
]
|
|
2274
2486
|
};
|
|
2275
2487
|
}
|
|
@@ -2392,8 +2604,162 @@ function isBriefShaped(prBody) {
|
|
|
2392
2604
|
const stripped = stripCode(prBody);
|
|
2393
2605
|
return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
|
|
2394
2606
|
}
|
|
2607
|
+
var COMMAND_WORDS = ["export", "bun", "gh", "git", "grep", "sed", "cat", "diff", "vinaya"];
|
|
2608
|
+
var BRIEF_RULES_SINCE_PR = 394;
|
|
2609
|
+
var AGENT_BOXES_REFUSED_SINCE_PR = 396;
|
|
2610
|
+
var AGENT_BOX_LINE_RE = /^-\s*\[[ xX]\]\s*\*{2}\[agent\]\*{2}/im;
|
|
2611
|
+
function checkNoAgentBoxes(prBody) {
|
|
2612
|
+
const region = testPlanRegion(prBody);
|
|
2613
|
+
if (!AGENT_BOX_LINE_RE.test(region))
|
|
2614
|
+
return { status: "pass", errors: [] };
|
|
2615
|
+
return {
|
|
2616
|
+
status: "fail",
|
|
2617
|
+
errors: [
|
|
2618
|
+
"brief-validation no agent boxes: the Test Plan carries a checkbox `[agent]` item — an agent never ticks a box or edits a PR body (Principal ruling, 2026-09-03, after PR #395). Render the `[agent]` half as a fenced command list instead; `vinaya pr report` runs each command from the PR head and writes its actual output into the AEG:EVIDENCE block. `[principal]` checkboxes are unaffected."
|
|
2619
|
+
]
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
function rolloutThresholdFor(message) {
|
|
2623
|
+
if (message.startsWith("brief-validation unpinned code claim:") || message.startsWith("brief-validation commands carry output:") || message.startsWith("brief-validation consumer tests:") || message.startsWith("brief-validation defeat cases:")) {
|
|
2624
|
+
return BRIEF_RULES_SINCE_PR;
|
|
2625
|
+
}
|
|
2626
|
+
if (message.startsWith("brief-validation no agent boxes:"))
|
|
2627
|
+
return AGENT_BOXES_REFUSED_SINCE_PR;
|
|
2628
|
+
return null;
|
|
2629
|
+
}
|
|
2630
|
+
function partitionBriefErrorsByRollout(errors, prNumber) {
|
|
2631
|
+
const blocking = [];
|
|
2632
|
+
const info = [];
|
|
2633
|
+
for (const e of errors) {
|
|
2634
|
+
const threshold = rolloutThresholdFor(e);
|
|
2635
|
+
const grandfathered = threshold !== null && prNumber !== null && prNumber < threshold;
|
|
2636
|
+
(grandfathered ? info : blocking).push(e);
|
|
2637
|
+
}
|
|
2638
|
+
return { blocking, info };
|
|
2639
|
+
}
|
|
2640
|
+
function extractFencedBlocks(text) {
|
|
2641
|
+
const re = /^[ \t]*(`{3,}|~{3,})([^\n]*)\n([\s\S]*?)^[ \t]*\1[ \t]*$/gm;
|
|
2642
|
+
const blocks = [];
|
|
2643
|
+
let m = re.exec(text);
|
|
2644
|
+
while (m !== null) {
|
|
2645
|
+
const lang = m[2].trim().toLowerCase().split(/\s+/)[0] ?? "";
|
|
2646
|
+
blocks.push({ start: m.index, end: m.index + m[0].length, lang, content: m[3] });
|
|
2647
|
+
m = re.exec(text);
|
|
2648
|
+
}
|
|
2649
|
+
return blocks;
|
|
2650
|
+
}
|
|
2651
|
+
function firstNonBlankLine(text) {
|
|
2652
|
+
const line = text.split(`
|
|
2653
|
+
`).find((l) => l.trim().length > 0);
|
|
2654
|
+
return line?.trim() ?? "";
|
|
2655
|
+
}
|
|
2656
|
+
var FILE_LINE_RE = /\b[\w./-]+\.(?:tsx?|jsx?|mjs|cjs|md|mdx|json|ya?ml|sh|py|toml):\d+\b/g;
|
|
2657
|
+
function checkNoUnpinnedCodeClaims(prBody) {
|
|
2658
|
+
const premiseText = premiseBlockText(prBody);
|
|
2659
|
+
const withoutPremise = premiseText ? prBody.replace(premiseText, "") : prBody;
|
|
2660
|
+
const scanned = stripCode(withoutPremise, { inlineSpans: "keep" });
|
|
2661
|
+
const matches = scanned.match(FILE_LINE_RE) ?? [];
|
|
2662
|
+
if (matches.length === 0)
|
|
2663
|
+
return { status: "pass", errors: [] };
|
|
2664
|
+
return {
|
|
2665
|
+
status: "fail",
|
|
2666
|
+
errors: matches.map((m) => `brief-validation unpinned code claim: "${m}" states a code fact by file-and-line reference outside a \`Premise:\` pin and outside a fenced code block — pin it in \`Premise:\`, or show the executed command and its output instead.`)
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
function extractNumberedSection(prBody, num) {
|
|
2670
|
+
const startRe = new RegExp(`^#{1,4}\\s*(?:\\*\\*)?${num}[a-z]?\\.\\s`, "im");
|
|
2671
|
+
const start = startRe.exec(prBody);
|
|
2672
|
+
if (!start)
|
|
2673
|
+
return null;
|
|
2674
|
+
const afterStart = start.index + start[0].length;
|
|
2675
|
+
const rest = prBody.slice(afterStart);
|
|
2676
|
+
const next = /^#{1,4}\s/m.exec(rest);
|
|
2677
|
+
return next ? rest.slice(0, next.index) : rest;
|
|
2678
|
+
}
|
|
2679
|
+
function commandWordOf(line) {
|
|
2680
|
+
return line.trim().split(/\s+/)[0] ?? "";
|
|
2681
|
+
}
|
|
2682
|
+
function isCommandBlock(content) {
|
|
2683
|
+
return COMMAND_WORDS.includes(commandWordOf(firstNonBlankLine(content)));
|
|
2684
|
+
}
|
|
2685
|
+
function isStep0Block(content) {
|
|
2686
|
+
return firstNonBlankLine(content).startsWith("git worktree add");
|
|
2687
|
+
}
|
|
2688
|
+
function checkCommandsCarryOutput(prBody) {
|
|
2689
|
+
const errors = [];
|
|
2690
|
+
for (const [label2, num] of [
|
|
2691
|
+
["5", 5],
|
|
2692
|
+
["6", 6]
|
|
2693
|
+
]) {
|
|
2694
|
+
const section = extractNumberedSection(prBody, num);
|
|
2695
|
+
if (!section)
|
|
2696
|
+
continue;
|
|
2697
|
+
const blocks = extractFencedBlocks(section);
|
|
2698
|
+
for (let i = 0;i < blocks.length; i++) {
|
|
2699
|
+
const block = blocks[i];
|
|
2700
|
+
if (isStep0Block(block.content) || !isCommandBlock(block.content))
|
|
2701
|
+
continue;
|
|
2702
|
+
const next = blocks[i + 1];
|
|
2703
|
+
if (!next || isCommandBlock(next.content)) {
|
|
2704
|
+
errors.push(`brief-validation commands carry output: §${label2} has a command block ("${firstNonBlankLine(block.content)}") not immediately followed by a non-command output block — the very next fenced block must hold that command's actual output, not another command.`);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2709
|
+
}
|
|
2710
|
+
var CONSUMER_TESTS_SENTINEL_RE = /consumer-tests\s*:\s*none\s*[-—–]\s*\S/i;
|
|
2711
|
+
function packagesNamedIn(text) {
|
|
2712
|
+
const re = /\bpackages\/([A-Za-z0-9_-]+)\//g;
|
|
2713
|
+
const pkgs = new Set;
|
|
2714
|
+
let m = re.exec(text);
|
|
2715
|
+
while (m !== null) {
|
|
2716
|
+
pkgs.add(m[1]);
|
|
2717
|
+
m = re.exec(text);
|
|
2718
|
+
}
|
|
2719
|
+
return [...pkgs];
|
|
2720
|
+
}
|
|
2721
|
+
function hasTestPathForConsumer(text, consumerDir) {
|
|
2722
|
+
const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2723
|
+
const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
|
|
2724
|
+
return re.test(text);
|
|
2725
|
+
}
|
|
2726
|
+
function checkConsumerTests(prBody, consumersOf) {
|
|
2727
|
+
const section4 = extractNumberedSection(prBody, 4);
|
|
2728
|
+
if (!section4)
|
|
2729
|
+
return { status: "pass", errors: [] };
|
|
2730
|
+
if (CONSUMER_TESTS_SENTINEL_RE.test(section4))
|
|
2731
|
+
return { status: "pass", errors: [] };
|
|
2732
|
+
const errors = [];
|
|
2733
|
+
for (const pkg of packagesNamedIn(section4)) {
|
|
2734
|
+
for (const consumerDir of consumersOf(pkg)) {
|
|
2735
|
+
if (hasTestPathForConsumer(section4, consumerDir))
|
|
2736
|
+
continue;
|
|
2737
|
+
errors.push(`brief-validation consumer tests: §4 names a path under packages/${pkg}/, and ${consumerDir} depends on @attalabs/${pkg}, but no test path under ${consumerDir} is named in §4 — name one, or add \`consumer-tests: none — <reason>\`.`);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2741
|
+
}
|
|
2742
|
+
var CHECK_NAME_RE = /\bcheck-[a-z0-9-]+(?:\.ts)?\b/;
|
|
2743
|
+
var FORGE_WRITE_COMMAND_RE = /\bgh\s+(?:pr|issue)\s+(?:create|merge|close|comment|edit|review)\b|\bgit\s+push\b|\bvinaya\s+pr\s+(?:create|report\s+--write)\b/i;
|
|
2744
|
+
var DEFEAT_CASES_RE = /defeat cases\s*:/i;
|
|
2745
|
+
function checkDefeatCases(prBody) {
|
|
2746
|
+
const section4 = extractNumberedSection(prBody, 4);
|
|
2747
|
+
if (!section4)
|
|
2748
|
+
return { status: "pass", errors: [] };
|
|
2749
|
+
if (!CHECK_NAME_RE.test(section4) && !FORGE_WRITE_COMMAND_RE.test(section4))
|
|
2750
|
+
return { status: "pass", errors: [] };
|
|
2751
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2752
|
+
if (DEFEAT_CASES_RE.test(section6))
|
|
2753
|
+
return { status: "pass", errors: [] };
|
|
2754
|
+
return {
|
|
2755
|
+
status: "fail",
|
|
2756
|
+
errors: [
|
|
2757
|
+
"brief-validation defeat cases: §4 names a check or a forge-writing command, but §6 carries no `Defeat cases:` line — name the inputs that would defeat this check, or that this command must not accidentally trigger on."
|
|
2758
|
+
]
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2395
2761
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2396
|
-
const { requireClosesN = true } = options;
|
|
2762
|
+
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2397
2763
|
const results = [
|
|
2398
2764
|
checkTierField(prBody, readTier),
|
|
2399
2765
|
checkTestPlan(prBody),
|
|
@@ -2406,48 +2772,257 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2406
2772
|
checkAutonomyClause(prBody),
|
|
2407
2773
|
checkProjectField(prBody),
|
|
2408
2774
|
checkForField(prBody),
|
|
2775
|
+
checkNoUnpinnedCodeClaims(prBody),
|
|
2776
|
+
checkNoAgentBoxes(prBody),
|
|
2777
|
+
checkCommandsCarryOutput(prBody),
|
|
2778
|
+
checkConsumerTests(prBody, consumersOf),
|
|
2779
|
+
checkDefeatCases(prBody),
|
|
2409
2780
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2410
2781
|
];
|
|
2411
2782
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2412
2783
|
}
|
|
2413
|
-
// ../../packages/aeg-core/src/
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
{ name: "Suggested agent-class", pattern: "(?:Suggested\\s+)?agent-class" },
|
|
2425
|
-
{ name: "Stop-and-escalate", pattern: "Stop-and-escalate" },
|
|
2426
|
-
{ name: "Docs to keep coherent", pattern: "Docs to keep coherent|§7" }
|
|
2784
|
+
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2785
|
+
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
2786
|
+
var STATIC_PORTABLE_PREFIXES = [
|
|
2787
|
+
"roles/",
|
|
2788
|
+
"contracts/",
|
|
2789
|
+
"skills/",
|
|
2790
|
+
".github/",
|
|
2791
|
+
".vinaya/",
|
|
2792
|
+
".claude/",
|
|
2793
|
+
".git/",
|
|
2794
|
+
".husky/"
|
|
2427
2795
|
];
|
|
2428
|
-
var
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2796
|
+
var EXEMPT_LITERALS = new Set([
|
|
2797
|
+
"path/inside/the/shipped/diff.ts",
|
|
2798
|
+
"path/inside/the/surface.ts",
|
|
2799
|
+
"apps/x/specs/..."
|
|
2800
|
+
]);
|
|
2801
|
+
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
2802
|
+
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
2803
|
+
var VENDOR_NAME_SOURCE = "\\bclaude code\\b|\\bclaude\\b|\\banthropic\\b|\\bchatgpt\\b|\\bopenai\\b|\\bgpt\\b|\\bgemini\\b|\\bcodex\\b|\\bgrok\\b|\\bdeepseek\\b|\\bopus\\b|\\bsonnet\\b|\\bhaiku\\b";
|
|
2804
|
+
var VENDOR_EXAMPLE_START = /<!--\s*AEG:VENDOR-EXAMPLE:START\s*-->/;
|
|
2805
|
+
var VENDOR_EXAMPLE_END = /<!--\s*AEG:VENDOR-EXAMPLE:END\s*-->/;
|
|
2806
|
+
function maskVendorExampleRegion(masked) {
|
|
2807
|
+
const start = VENDOR_EXAMPLE_START.exec(masked);
|
|
2808
|
+
if (!start)
|
|
2809
|
+
return masked;
|
|
2810
|
+
const afterStart = start.index + start[0].length;
|
|
2811
|
+
const end = VENDOR_EXAMPLE_END.exec(masked.slice(afterStart));
|
|
2812
|
+
if (!end)
|
|
2813
|
+
return masked;
|
|
2814
|
+
const regionEnd = afterStart + end.index + end[0].length;
|
|
2815
|
+
const region = masked.slice(start.index, regionEnd);
|
|
2816
|
+
const blanked = region.replace(/[^\n]/g, " ");
|
|
2817
|
+
return masked.slice(0, start.index) + blanked + masked.slice(regionEnd);
|
|
2818
|
+
}
|
|
2819
|
+
function extractVendorMentions(content) {
|
|
2820
|
+
const scoped = maskVendorExampleRegion(maskCode(content));
|
|
2821
|
+
const found = [];
|
|
2822
|
+
const pattern = new RegExp(VENDOR_NAME_SOURCE, "gi");
|
|
2823
|
+
let match = pattern.exec(scoped);
|
|
2824
|
+
while (match !== null) {
|
|
2825
|
+
found.push({ name: match[0], index: match.index });
|
|
2826
|
+
match = pattern.exec(scoped);
|
|
2439
2827
|
}
|
|
2440
|
-
return
|
|
2828
|
+
return found;
|
|
2441
2829
|
}
|
|
2442
|
-
function
|
|
2443
|
-
|
|
2830
|
+
function extractCitedPaths(content) {
|
|
2831
|
+
const found = [];
|
|
2832
|
+
const spanPattern = /`([^`\n]+)`/g;
|
|
2833
|
+
let match = spanPattern.exec(content);
|
|
2834
|
+
while (match !== null) {
|
|
2835
|
+
const cited = match[1] ?? "";
|
|
2836
|
+
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
2837
|
+
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
2838
|
+
found.push({ cited, index: match.index });
|
|
2839
|
+
}
|
|
2840
|
+
match = spanPattern.exec(content);
|
|
2841
|
+
}
|
|
2842
|
+
return found;
|
|
2444
2843
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2844
|
+
function lineAtIndex(content, index) {
|
|
2845
|
+
let line = 1;
|
|
2846
|
+
for (let i = 0;i < index; i++) {
|
|
2847
|
+
if (content.charCodeAt(i) === 10)
|
|
2848
|
+
line++;
|
|
2849
|
+
}
|
|
2850
|
+
return line;
|
|
2448
2851
|
}
|
|
2449
|
-
function
|
|
2450
|
-
|
|
2852
|
+
function isPortable(cited, shipsPrefix) {
|
|
2853
|
+
if (cited.startsWith(shipsPrefix))
|
|
2854
|
+
return true;
|
|
2855
|
+
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
2856
|
+
}
|
|
2857
|
+
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
2858
|
+
const findings = [];
|
|
2859
|
+
for (const file of files) {
|
|
2860
|
+
if (!file.path.startsWith(shipsPrefix))
|
|
2861
|
+
continue;
|
|
2862
|
+
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
2863
|
+
if (EXEMPT_LITERALS.has(cited))
|
|
2864
|
+
continue;
|
|
2865
|
+
if (isPortable(cited, shipsPrefix))
|
|
2866
|
+
continue;
|
|
2867
|
+
findings.push({
|
|
2868
|
+
file: file.path,
|
|
2869
|
+
line: lineAtIndex(file.content, index),
|
|
2870
|
+
cited,
|
|
2871
|
+
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`,
|
|
2872
|
+
kind: "path"
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
for (const { name, index } of extractVendorMentions(file.content)) {
|
|
2876
|
+
findings.push({
|
|
2877
|
+
file: file.path,
|
|
2878
|
+
line: lineAtIndex(file.content, index),
|
|
2879
|
+
cited: name,
|
|
2880
|
+
message: `names "${name}" outside the one fenced vendor-example home (an <!-- AEG:VENDOR-EXAMPLE:START --> … <!-- AEG:VENDOR-EXAMPLE:END --> pair) — portable doctrine refers to a host generically everywhere else`,
|
|
2881
|
+
kind: "vendor-name"
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
return findings;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// ../../packages/aeg-core/src/doctrine-no-procedures.ts
|
|
2889
|
+
function vendorExampleSpan(content) {
|
|
2890
|
+
const start = VENDOR_EXAMPLE_START.exec(content);
|
|
2891
|
+
if (!start)
|
|
2892
|
+
return null;
|
|
2893
|
+
const afterStart = start.index + start[0].length;
|
|
2894
|
+
const end = VENDOR_EXAMPLE_END.exec(content.slice(afterStart));
|
|
2895
|
+
if (!end)
|
|
2896
|
+
return null;
|
|
2897
|
+
return { start: start.index, end: afterStart + end.index + end[0].length };
|
|
2898
|
+
}
|
|
2899
|
+
function isCommandLine(line) {
|
|
2900
|
+
const word = line.trim().split(/\s+/)[0] ?? "";
|
|
2901
|
+
return COMMAND_WORDS.includes(word);
|
|
2902
|
+
}
|
|
2903
|
+
var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
|
|
2904
|
+
function procedureLineNumberAt(content, index) {
|
|
2905
|
+
return content.slice(0, index).split(`
|
|
2906
|
+
`).length;
|
|
2907
|
+
}
|
|
2908
|
+
function isTemplatePath(path) {
|
|
2909
|
+
return /(^|\/)templates\//.test(path);
|
|
2910
|
+
}
|
|
2911
|
+
function checkDoctrineNoProcedures(files) {
|
|
2912
|
+
const findings = [];
|
|
2913
|
+
for (const file of files) {
|
|
2914
|
+
if (isTemplatePath(file.path))
|
|
2915
|
+
continue;
|
|
2916
|
+
const span = vendorExampleSpan(file.content);
|
|
2917
|
+
for (const block of extractFencedBlocks(file.content)) {
|
|
2918
|
+
if (span && block.start >= span.start && block.end <= span.end)
|
|
2919
|
+
continue;
|
|
2920
|
+
if (!SHELL_LANGS.has(block.lang))
|
|
2921
|
+
continue;
|
|
2922
|
+
const commandLineCount = block.content.split(`
|
|
2923
|
+
`).filter(isCommandLine).length;
|
|
2924
|
+
if (commandLineCount >= 2) {
|
|
2925
|
+
findings.push({
|
|
2926
|
+
file: file.path,
|
|
2927
|
+
line: procedureLineNumberAt(file.content, block.start),
|
|
2928
|
+
message: `this fenced block has ${commandLineCount} shell-command lines — this sequence is a \`vinaya\` command, name it (or move it inside the \`AEG:VENDOR-EXAMPLE\` anchor / a \`templates/\` file).`
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
return findings;
|
|
2934
|
+
}
|
|
2935
|
+
// ../../packages/aeg-core/src/pr-report-density.ts
|
|
2936
|
+
function headingSectionBody(prBody, heading) {
|
|
2937
|
+
const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "im");
|
|
2938
|
+
const m = headingRe.exec(prBody);
|
|
2939
|
+
if (!m)
|
|
2940
|
+
return null;
|
|
2941
|
+
const start = m.index + m[0].length;
|
|
2942
|
+
const rest = prBody.slice(start);
|
|
2943
|
+
const nextHeading = /^#{1,2}\s/m.exec(rest);
|
|
2944
|
+
return nextHeading ? rest.slice(0, nextHeading.index) : rest;
|
|
2945
|
+
}
|
|
2946
|
+
function stripAnchorBlocks(sectionText) {
|
|
2947
|
+
let result = sectionText;
|
|
2948
|
+
for (const field of ["CLOSES", "PROJECT", "TIER", "PREMISE", "TEST-PLAN", "EVIDENCE"]) {
|
|
2949
|
+
for (;; ) {
|
|
2950
|
+
const bounds = anchoredRegionBounds(result, field);
|
|
2951
|
+
if (!bounds)
|
|
2952
|
+
break;
|
|
2953
|
+
result = result.slice(0, bounds.outerStart) + result.slice(bounds.outerEnd);
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
return result;
|
|
2957
|
+
}
|
|
2958
|
+
function paragraphCount(sectionText) {
|
|
2959
|
+
const stripped = stripCode(stripAnchorBlocks(sectionText)).trim();
|
|
2960
|
+
if (stripped.length === 0)
|
|
2961
|
+
return 0;
|
|
2962
|
+
return stripped.split(/\n[ \t]*\n/).map((block) => block.trim()).filter((block) => block.length > 0).length;
|
|
2963
|
+
}
|
|
2964
|
+
function checkSectionDensity(prBody, heading) {
|
|
2965
|
+
const body = headingSectionBody(prBody, heading);
|
|
2966
|
+
if (body === null)
|
|
2967
|
+
return { status: "pass", errors: [] };
|
|
2968
|
+
const count = paragraphCount(body);
|
|
2969
|
+
if (count <= 1)
|
|
2970
|
+
return { status: "pass", errors: [] };
|
|
2971
|
+
return {
|
|
2972
|
+
status: "fail",
|
|
2973
|
+
errors: [
|
|
2974
|
+
`pr-report-density ${heading}: "## ${heading}" holds ${count} paragraphs — the canonical PR-report form (aeg-root/roles/developer.md § PR body) requires exactly one. Collapse it to one paragraph; move the rest into a section of your own below the canonical four ("Add anything you want beneath the four sections", developer.md), a commit message, or the changeset — never into the AEG:EVIDENCE block, which is emitted only, never hand-typed.`
|
|
2975
|
+
]
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
function checkSummaryDensity(prBody) {
|
|
2979
|
+
return checkSectionDensity(prBody, "Summary");
|
|
2980
|
+
}
|
|
2981
|
+
function checkScopeDensity(prBody) {
|
|
2982
|
+
return checkSectionDensity(prBody, "Scope");
|
|
2983
|
+
}
|
|
2984
|
+
function checkPrReportDensity(prBody) {
|
|
2985
|
+
const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
|
|
2986
|
+
return { errors: results.flatMap((r) => r.errors) };
|
|
2987
|
+
}
|
|
2988
|
+
// ../../packages/aeg-core/src/issue-validation.ts
|
|
2989
|
+
function hasRationaleField(body, labelPattern) {
|
|
2990
|
+
const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
|
|
2991
|
+
return re.test(body);
|
|
2992
|
+
}
|
|
2993
|
+
var RATIONALE_FIELDS = [
|
|
2994
|
+
{ name: "Boundary", pattern: "Boundary" },
|
|
2995
|
+
{ name: "Sizing", pattern: "Sizing" },
|
|
2996
|
+
{ name: "Project(s) + blast radius", pattern: "Project\\(s\\)|Project(?:s)?\\s*\\+|blast radius" },
|
|
2997
|
+
{ name: "Dependency rationale", pattern: "Dependency rationale|Depends[- ]on" },
|
|
2998
|
+
{ name: "Traps to avoid", pattern: "Traps" },
|
|
2999
|
+
{ name: "Suggested agent-class", pattern: "(?:Suggested\\s+)?agent-class" },
|
|
3000
|
+
{ name: "Stop-and-escalate", pattern: "Stop-and-escalate" },
|
|
3001
|
+
{ name: "Docs to keep coherent", pattern: "Docs to keep coherent|§7" }
|
|
3002
|
+
];
|
|
3003
|
+
var DEPENDENCY_RATIONALE_FIELD_NAME = "Dependency rationale";
|
|
3004
|
+
function checkIssueRationale(body) {
|
|
3005
|
+
const errors = [];
|
|
3006
|
+
for (const f of RATIONALE_FIELDS) {
|
|
3007
|
+
if (!hasRationaleField(body, f.pattern)) {
|
|
3008
|
+
errors.push(`issue-validation ${f.name}: rationale field not found in the Issue body — every task Issue carries the full Planner's rationale (aeg-root/contracts/planner-brief.md).`);
|
|
3009
|
+
continue;
|
|
3010
|
+
}
|
|
3011
|
+
if (f.name === DEPENDENCY_RATIONALE_FIELD_NAME && !SECTION_HEADER.test(body)) {
|
|
3012
|
+
errors.push(`issue-validation ${f.name}: rationale field found, but not in the form amend-deps requires — ` + "write `**Dependency rationale** — …`, not `**Dependency rationale:** …`. " + "`amendRationaleDeps` (the only sanctioned way to edit Depends-on/Conflicts-with) locates this " + "section by the exact anchor `**Dependency rationale**`; a colon inside the bold breaks that match " + "and the Issue becomes unamendable.");
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3016
|
+
}
|
|
3017
|
+
function isTaskIssueLabelSet(labels) {
|
|
3018
|
+
return hasLabel("tranche", labels);
|
|
3019
|
+
}
|
|
3020
|
+
var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
|
|
3021
|
+
function isControlCodePoint(codePoint) {
|
|
3022
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
3023
|
+
}
|
|
3024
|
+
function isDisplayControlCodePoint(codePoint) {
|
|
3025
|
+
return codePoint >= 8203 && codePoint <= 8207 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297 || codePoint === 65279;
|
|
2451
3026
|
}
|
|
2452
3027
|
var MESSAGE_VALUE_COUNT_MAX = 5;
|
|
2453
3028
|
function residueForMessage(values) {
|
|
@@ -2661,6 +3236,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
2661
3236
|
const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
|
|
2662
3237
|
if (!depEntry)
|
|
2663
3238
|
continue;
|
|
3239
|
+
const sameTask = depEntry.trancheSlug === e.trancheSlug && depEntry.task.id === e.task.id;
|
|
3240
|
+
const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
|
|
3241
|
+
if (sameTask || sameIssue) {
|
|
3242
|
+
failures.push({
|
|
3243
|
+
issue: e.task.issue,
|
|
3244
|
+
tranche: e.trancheSlug,
|
|
3245
|
+
task: e.task.id,
|
|
3246
|
+
reason: `INTERNAL: parsed a self-dependency (depends-on ${dep}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream.`
|
|
3247
|
+
});
|
|
3248
|
+
continue;
|
|
3249
|
+
}
|
|
2664
3250
|
const depFacts = depEntry.facts;
|
|
2665
3251
|
const depClosed = depFacts?.issueState === "closed";
|
|
2666
3252
|
if (!depClosed) {
|
|
@@ -2804,41 +3390,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2804
3390
|
}
|
|
2805
3391
|
return { ok: true, expectedIssue };
|
|
2806
3392
|
}
|
|
2807
|
-
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2808
|
-
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2809
|
-
function extractHeadSha(comment) {
|
|
2810
|
-
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2811
|
-
return m ? m[1].toLowerCase() : null;
|
|
2812
|
-
}
|
|
2813
|
-
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2814
|
-
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2815
|
-
if (clearHits.length > 0) {
|
|
2816
|
-
const latest = clearHits[clearHits.length - 1];
|
|
2817
|
-
const m = latest.match(valuePattern);
|
|
2818
|
-
return {
|
|
2819
|
-
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2820
|
-
headSha: extractHeadSha(latest),
|
|
2821
|
-
danglingNote: null
|
|
2822
|
-
};
|
|
2823
|
-
}
|
|
2824
|
-
return {
|
|
2825
|
-
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2826
|
-
headSha: null,
|
|
2827
|
-
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2828
|
-
};
|
|
2829
|
-
}
|
|
2830
|
-
function extractCodeReviewVerdict(comments) {
|
|
2831
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
2832
|
-
}
|
|
2833
|
-
function extractSecurityReviewVerdict(comments) {
|
|
2834
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
2835
|
-
}
|
|
2836
3393
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
2837
3394
|
function isBoundToHead(extraction, headSha) {
|
|
2838
3395
|
if (!extraction.headSha)
|
|
2839
3396
|
return false;
|
|
2840
3397
|
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2841
3398
|
}
|
|
3399
|
+
function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
3400
|
+
if (patchIdOf === undefined || !extraction.headSha)
|
|
3401
|
+
return false;
|
|
3402
|
+
const judged = patchIdOf(extraction.headSha);
|
|
3403
|
+
const current = patchIdOf(headSha);
|
|
3404
|
+
if (judged === null || current === null)
|
|
3405
|
+
return false;
|
|
3406
|
+
return judged === current;
|
|
3407
|
+
}
|
|
3408
|
+
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3409
|
+
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3410
|
+
}
|
|
2842
3411
|
function checkReviewGate(input) {
|
|
2843
3412
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2844
3413
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2854,6 +3423,8 @@ function checkReviewGate(input) {
|
|
|
2854
3423
|
waived: true
|
|
2855
3424
|
};
|
|
2856
3425
|
}
|
|
3426
|
+
const reportedMechanicalChecks = input.mechanicalChecks.filter((c) => c.bucket !== "skipping" && c.bucket !== "neutral");
|
|
3427
|
+
const mechanicalChecksClean = reportedMechanicalChecks.length > 0 && reportedMechanicalChecks.every((c) => c.bucket === "pass");
|
|
2857
3428
|
const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
|
|
2858
3429
|
const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
|
|
2859
3430
|
const verifiedBodies = verified.map((c) => c.body);
|
|
@@ -2861,12 +3432,12 @@ function checkReviewGate(input) {
|
|
|
2861
3432
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2862
3433
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2863
3434
|
const securityClean = security.value === "PASS";
|
|
2864
|
-
const codeReviewBound =
|
|
2865
|
-
const securityBound =
|
|
2866
|
-
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
3435
|
+
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3436
|
+
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3437
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
|
|
2867
3438
|
return {
|
|
2868
3439
|
verdict: "pass",
|
|
2869
|
-
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
3440
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
|
|
2870
3441
|
waived: false
|
|
2871
3442
|
};
|
|
2872
3443
|
}
|
|
@@ -2881,6 +3452,9 @@ function checkReviewGate(input) {
|
|
|
2881
3452
|
} else if (!securityBound) {
|
|
2882
3453
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2883
3454
|
}
|
|
3455
|
+
if (!mechanicalChecksClean) {
|
|
3456
|
+
problems.push(reportedMechanicalChecks.length === 0 ? "no mechanical checks have reported for this head yet" : `mechanical check(s) not green: ${reportedMechanicalChecks.filter((c) => c.bucket !== "pass").map((c) => `${c.name} (${c.bucket})`).join(", ")}`);
|
|
3457
|
+
}
|
|
2884
3458
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2885
3459
|
return {
|
|
2886
3460
|
verdict: "fail",
|
|
@@ -3160,72 +3734,6 @@ function compareToBaseline(current, baseline) {
|
|
|
3160
3734
|
perTool
|
|
3161
3735
|
};
|
|
3162
3736
|
}
|
|
3163
|
-
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
3164
|
-
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
3165
|
-
var STATIC_PORTABLE_PREFIXES = [
|
|
3166
|
-
"roles/",
|
|
3167
|
-
"contracts/",
|
|
3168
|
-
"skills/",
|
|
3169
|
-
".github/",
|
|
3170
|
-
".vinaya/",
|
|
3171
|
-
".claude/",
|
|
3172
|
-
".git/",
|
|
3173
|
-
".husky/"
|
|
3174
|
-
];
|
|
3175
|
-
var EXEMPT_LITERALS = new Set([
|
|
3176
|
-
"path/inside/the/shipped/diff.ts",
|
|
3177
|
-
"path/inside/the/surface.ts",
|
|
3178
|
-
"apps/x/specs/..."
|
|
3179
|
-
]);
|
|
3180
|
-
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
3181
|
-
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
3182
|
-
function extractCitedPaths(content) {
|
|
3183
|
-
const found = [];
|
|
3184
|
-
const spanPattern = /`([^`\n]+)`/g;
|
|
3185
|
-
let match = spanPattern.exec(content);
|
|
3186
|
-
while (match !== null) {
|
|
3187
|
-
const cited = match[1] ?? "";
|
|
3188
|
-
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
3189
|
-
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
3190
|
-
found.push({ cited, index: match.index });
|
|
3191
|
-
}
|
|
3192
|
-
match = spanPattern.exec(content);
|
|
3193
|
-
}
|
|
3194
|
-
return found;
|
|
3195
|
-
}
|
|
3196
|
-
function lineAtIndex(content, index) {
|
|
3197
|
-
let line = 1;
|
|
3198
|
-
for (let i = 0;i < index; i++) {
|
|
3199
|
-
if (content.charCodeAt(i) === 10)
|
|
3200
|
-
line++;
|
|
3201
|
-
}
|
|
3202
|
-
return line;
|
|
3203
|
-
}
|
|
3204
|
-
function isPortable(cited, shipsPrefix) {
|
|
3205
|
-
if (cited.startsWith(shipsPrefix))
|
|
3206
|
-
return true;
|
|
3207
|
-
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
3208
|
-
}
|
|
3209
|
-
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
3210
|
-
const findings = [];
|
|
3211
|
-
for (const file of files) {
|
|
3212
|
-
if (!file.path.startsWith(shipsPrefix))
|
|
3213
|
-
continue;
|
|
3214
|
-
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
3215
|
-
if (EXEMPT_LITERALS.has(cited))
|
|
3216
|
-
continue;
|
|
3217
|
-
if (isPortable(cited, shipsPrefix))
|
|
3218
|
-
continue;
|
|
3219
|
-
findings.push({
|
|
3220
|
-
file: file.path,
|
|
3221
|
-
line: lineAtIndex(file.content, index),
|
|
3222
|
-
cited,
|
|
3223
|
-
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
|
|
3224
|
-
});
|
|
3225
|
-
}
|
|
3226
|
-
}
|
|
3227
|
-
return findings;
|
|
3228
|
-
}
|
|
3229
3737
|
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3230
3738
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3231
3739
|
function shipsArchivePrefix(shipsPrefix) {
|
|
@@ -3561,6 +4069,12 @@ function scanRetiredVocabulary(files) {
|
|
|
3561
4069
|
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
3562
4070
|
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
3563
4071
|
}
|
|
4072
|
+
function isSelfDependency(dep, task, issue) {
|
|
4073
|
+
if (dep.issue !== null && issue !== null && dep.issue === issue.number)
|
|
4074
|
+
return true;
|
|
4075
|
+
const bare = dep.id.trim().replace(/^#/, "");
|
|
4076
|
+
return bare.toLowerCase() === task.id.trim().toLowerCase();
|
|
4077
|
+
}
|
|
3564
4078
|
function checkDispatchReadiness(input) {
|
|
3565
4079
|
const { trancheSlug, task } = input;
|
|
3566
4080
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
@@ -3575,6 +4089,11 @@ function checkDispatchReadiness(input) {
|
|
|
3575
4089
|
blockers.push(`dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
|
|
3576
4090
|
}
|
|
3577
4091
|
for (const dep of input.dependsOn) {
|
|
4092
|
+
if (isSelfDependency(dep, task, input.issue)) {
|
|
4093
|
+
const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
|
|
4094
|
+
blockers.push(`dispatch-gate INTERNAL: parsed a self-dependency for task ${task.id} (${issueStr}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream. Re-run once the rationale is corrected or the fix ships.`);
|
|
4095
|
+
continue;
|
|
4096
|
+
}
|
|
3578
4097
|
if (dep.resolved === false) {
|
|
3579
4098
|
blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on "${dep.id}", which is UNRESOLVABLE — the resolver could not find a matching tranche/task/Issue for this edge (not a claim about merge status). Not dispatchable until the edge is corrected.`);
|
|
3580
4099
|
continue;
|
|
@@ -3814,27 +4333,27 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
3814
4333
|
};
|
|
3815
4334
|
}
|
|
3816
4335
|
const checkboxLines = section.split(`
|
|
3817
|
-
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]
|
|
4336
|
+
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
|
|
3818
4337
|
if (checkboxLines.length === 0) {
|
|
3819
4338
|
return {
|
|
3820
4339
|
verdict: "pass",
|
|
3821
4340
|
messages: [
|
|
3822
|
-
"Test Plan section found but has no checkbox items.",
|
|
4341
|
+
"Test Plan section found but has no `[principal]` checkbox items.",
|
|
3823
4342
|
"PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
|
|
3824
4343
|
]
|
|
3825
4344
|
};
|
|
3826
4345
|
}
|
|
3827
4346
|
const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
|
|
3828
4347
|
const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
|
|
3829
|
-
const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
4348
|
+
const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
3830
4349
|
if (unchecked.length > 0) {
|
|
3831
|
-
messages.push("", "FAIL — the following Test Plan items are unticked:");
|
|
4350
|
+
messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
|
|
3832
4351
|
for (const line of unchecked)
|
|
3833
4352
|
messages.push(` ${line}`);
|
|
3834
|
-
messages.push("", "Per aeg-root/roles/developer.md (Verification), a PR is not mergeable while any Test Plan box is unticked.", "
|
|
4353
|
+
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.");
|
|
3835
4354
|
return { verdict: "fail", messages };
|
|
3836
4355
|
}
|
|
3837
|
-
messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
4356
|
+
messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
3838
4357
|
return { verdict: "pass", messages };
|
|
3839
4358
|
}
|
|
3840
4359
|
// ../../packages/aeg-core/src/workspace-escape.ts
|
|
@@ -3915,82 +4434,14 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
3915
4434
|
return findings;
|
|
3916
4435
|
}
|
|
3917
4436
|
// src/lib/diff-evidence.ts
|
|
3918
|
-
import { execFileSync as
|
|
3919
|
-
import { join } from "node:path";
|
|
3920
|
-
function fileDiffAgainst(ref, path) {
|
|
3921
|
-
let out;
|
|
3922
|
-
try {
|
|
3923
|
-
out = execFileSync2("git", ["diff", `${ref}...HEAD`, "--", path], {
|
|
3924
|
-
encoding: "utf8",
|
|
3925
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3926
|
-
}).trim();
|
|
3927
|
-
} catch {
|
|
3928
|
-
return null;
|
|
3929
|
-
}
|
|
3930
|
-
return out === "" ? null : out;
|
|
3931
|
-
}
|
|
3932
|
-
function revParse(ref) {
|
|
3933
|
-
try {
|
|
3934
|
-
return execFileSync2("git", ["rev-parse", "--verify", ref], {
|
|
3935
|
-
encoding: "utf8",
|
|
3936
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3937
|
-
}).trim();
|
|
3938
|
-
} catch {
|
|
3939
|
-
return null;
|
|
3940
|
-
}
|
|
3941
|
-
}
|
|
3942
|
-
function repoRoot() {
|
|
3943
|
-
try {
|
|
3944
|
-
return execFileSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
3945
|
-
encoding: "utf8",
|
|
3946
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3947
|
-
}).trim();
|
|
3948
|
-
} catch {
|
|
3949
|
-
return null;
|
|
3950
|
-
}
|
|
3951
|
-
}
|
|
3952
|
-
function changedFiles(base) {
|
|
3953
|
-
let out;
|
|
3954
|
-
try {
|
|
3955
|
-
out = execFileSync2("git", ["diff", "--name-only", `${base}...HEAD`], {
|
|
3956
|
-
encoding: "utf8",
|
|
3957
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3958
|
-
}).trim();
|
|
3959
|
-
} catch {
|
|
3960
|
-
return null;
|
|
3961
|
-
}
|
|
3962
|
-
return out === "" ? [] : out.split(`
|
|
3963
|
-
`).map((s) => s.trim()).filter(Boolean);
|
|
3964
|
-
}
|
|
3965
|
-
function resolveChangedFiles(base = process.env.BASE_SHA || "origin/main") {
|
|
3966
|
-
const root = repoRoot();
|
|
3967
|
-
if (root === null)
|
|
3968
|
-
return null;
|
|
3969
|
-
const head = revParse("HEAD");
|
|
3970
|
-
for (const ref of [base, "main"]) {
|
|
3971
|
-
const resolved = revParse(ref);
|
|
3972
|
-
if (resolved === null || resolved === head)
|
|
3973
|
-
continue;
|
|
3974
|
-
const relPaths = changedFiles(ref);
|
|
3975
|
-
if (relPaths === null)
|
|
3976
|
-
continue;
|
|
3977
|
-
return relPaths.map((p) => join(root, p));
|
|
3978
|
-
}
|
|
3979
|
-
return null;
|
|
3980
|
-
}
|
|
3981
|
-
|
|
3982
|
-
// src/checks/contract.ts
|
|
3983
|
-
var CHECK_SCHEMA_VERSION = 1;
|
|
3984
|
-
function emitCheckError(error) {
|
|
3985
|
-
process.stderr.write(`${JSON.stringify(error)}
|
|
3986
|
-
`);
|
|
3987
|
-
}
|
|
4437
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
4438
|
+
import { join as join2, relative, resolve as resolve2 } from "node:path";
|
|
3988
4439
|
|
|
3989
4440
|
// src/lib/config.ts
|
|
3990
|
-
import { execFileSync as
|
|
3991
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
4441
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4442
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
3992
4443
|
import { homedir } from "node:os";
|
|
3993
|
-
import { dirname, join
|
|
4444
|
+
import { dirname, join, resolve } from "node:path";
|
|
3994
4445
|
import { z } from "zod";
|
|
3995
4446
|
|
|
3996
4447
|
// src/lib/agent-vendors.ts
|
|
@@ -4138,16 +4589,16 @@ var VinayaConfigSchema = z.object({
|
|
|
4138
4589
|
}).optional(),
|
|
4139
4590
|
projects: z.array(ProjectEntrySchema).optional()
|
|
4140
4591
|
});
|
|
4141
|
-
var GLOBAL_VINAYA_HOME =
|
|
4142
|
-
var GLOBAL_CONFIG_PATH =
|
|
4592
|
+
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
4593
|
+
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|
|
4143
4594
|
var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
|
|
4144
4595
|
function findLocalConfig() {
|
|
4145
4596
|
let dir = process.cwd();
|
|
4146
4597
|
while (true) {
|
|
4147
|
-
const candidate =
|
|
4598
|
+
const candidate = join(dir, LOCAL_CONFIG_FILENAME);
|
|
4148
4599
|
if (existsSync(candidate))
|
|
4149
4600
|
return candidate;
|
|
4150
|
-
if (existsSync(
|
|
4601
|
+
if (existsSync(join(dir, ".git")))
|
|
4151
4602
|
return null;
|
|
4152
4603
|
const parent = dirname(dir);
|
|
4153
4604
|
if (parent === dir)
|
|
@@ -4219,7 +4670,7 @@ function trustAnchorRepo() {
|
|
|
4219
4670
|
return validated;
|
|
4220
4671
|
}
|
|
4221
4672
|
try {
|
|
4222
|
-
const url =
|
|
4673
|
+
const url = execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
4223
4674
|
encoding: "utf-8",
|
|
4224
4675
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4225
4676
|
}).trim();
|
|
@@ -4233,7 +4684,7 @@ function ghFetchTrustAnchorConfig() {
|
|
|
4233
4684
|
const repo = trustAnchorRepo();
|
|
4234
4685
|
if (!repo)
|
|
4235
4686
|
throw new Error("could not resolve repo identity for the trust-anchor read");
|
|
4236
|
-
return
|
|
4687
|
+
return execFileSync2("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
|
|
4237
4688
|
encoding: "utf-8",
|
|
4238
4689
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4239
4690
|
});
|
|
@@ -4281,13 +4732,139 @@ function loadConfig() {
|
|
|
4281
4732
|
if (!path)
|
|
4282
4733
|
return null;
|
|
4283
4734
|
try {
|
|
4284
|
-
const raw = JSON.parse(
|
|
4735
|
+
const raw = JSON.parse(readFileSync2(path, "utf-8"));
|
|
4285
4736
|
return stripGlobalOnlyKeys(VinayaConfigSchema.parse(raw), path);
|
|
4286
4737
|
} catch {
|
|
4287
4738
|
return null;
|
|
4288
4739
|
}
|
|
4289
4740
|
}
|
|
4290
|
-
var TOKENS_COLLECT_TRUST_PATH =
|
|
4741
|
+
var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
|
|
4742
|
+
|
|
4743
|
+
// src/checks/contract.ts
|
|
4744
|
+
var CHECK_SCHEMA_VERSION = 1;
|
|
4745
|
+
function emitCheckError(error) {
|
|
4746
|
+
process.stderr.write(`${JSON.stringify(error)}
|
|
4747
|
+
`);
|
|
4748
|
+
}
|
|
4749
|
+
|
|
4750
|
+
// src/commands/review-post.ts
|
|
4751
|
+
function parseChangedLineRanges(diffOutput) {
|
|
4752
|
+
const result = {};
|
|
4753
|
+
let currentFile = null;
|
|
4754
|
+
for (const line of diffOutput.split(`
|
|
4755
|
+
`)) {
|
|
4756
|
+
if (line.startsWith("+++ ")) {
|
|
4757
|
+
const path = line.slice(4).trim();
|
|
4758
|
+
currentFile = path === "/dev/null" ? null : path.replace(/^b\//, "");
|
|
4759
|
+
continue;
|
|
4760
|
+
}
|
|
4761
|
+
if (line.startsWith("@@") && currentFile) {
|
|
4762
|
+
const m = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
|
|
4763
|
+
if (m) {
|
|
4764
|
+
const start = Number(m[1]);
|
|
4765
|
+
const count = m[2] !== undefined ? Number(m[2]) : 1;
|
|
4766
|
+
if (count > 0) {
|
|
4767
|
+
if (!result[currentFile])
|
|
4768
|
+
result[currentFile] = [];
|
|
4769
|
+
result[currentFile].push([start, start + count - 1]);
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
return result;
|
|
4775
|
+
}
|
|
4776
|
+
|
|
4777
|
+
// src/lib/diff-evidence.ts
|
|
4778
|
+
function fileDiffAgainst(ref, path) {
|
|
4779
|
+
let out;
|
|
4780
|
+
try {
|
|
4781
|
+
out = execFileSync3("git", ["diff", `${ref}...HEAD`, "--", path], {
|
|
4782
|
+
encoding: "utf8",
|
|
4783
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4784
|
+
}).trim();
|
|
4785
|
+
} catch {
|
|
4786
|
+
return null;
|
|
4787
|
+
}
|
|
4788
|
+
return out === "" ? null : out;
|
|
4789
|
+
}
|
|
4790
|
+
function revParse(ref) {
|
|
4791
|
+
try {
|
|
4792
|
+
return execFileSync3("git", ["rev-parse", "--verify", ref], {
|
|
4793
|
+
encoding: "utf8",
|
|
4794
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4795
|
+
}).trim();
|
|
4796
|
+
} catch {
|
|
4797
|
+
return null;
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
function repoRoot() {
|
|
4801
|
+
try {
|
|
4802
|
+
return execFileSync3("git", ["rev-parse", "--show-toplevel"], {
|
|
4803
|
+
encoding: "utf8",
|
|
4804
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4805
|
+
}).trim();
|
|
4806
|
+
} catch {
|
|
4807
|
+
return null;
|
|
4808
|
+
}
|
|
4809
|
+
}
|
|
4810
|
+
function changedFiles(base) {
|
|
4811
|
+
let out;
|
|
4812
|
+
try {
|
|
4813
|
+
out = execFileSync3("git", ["diff", "--name-only", `${base}...HEAD`], {
|
|
4814
|
+
encoding: "utf8",
|
|
4815
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4816
|
+
}).trim();
|
|
4817
|
+
} catch {
|
|
4818
|
+
return null;
|
|
4819
|
+
}
|
|
4820
|
+
return out === "" ? [] : out.split(`
|
|
4821
|
+
`).map((s) => s.trim()).filter(Boolean);
|
|
4822
|
+
}
|
|
4823
|
+
function resolveChangedFiles(base = process.env.BASE_SHA || "origin/main") {
|
|
4824
|
+
return resolveDiff(base)?.files ?? null;
|
|
4825
|
+
}
|
|
4826
|
+
function resolveDiff(base = process.env.BASE_SHA || "origin/main") {
|
|
4827
|
+
const root = repoRoot();
|
|
4828
|
+
if (root === null)
|
|
4829
|
+
return null;
|
|
4830
|
+
const head = revParse("HEAD");
|
|
4831
|
+
for (const ref of [base, "main"]) {
|
|
4832
|
+
const resolved = revParse(ref);
|
|
4833
|
+
if (resolved === null || resolved === head)
|
|
4834
|
+
continue;
|
|
4835
|
+
const relPaths = changedFiles(ref);
|
|
4836
|
+
if (relPaths === null)
|
|
4837
|
+
continue;
|
|
4838
|
+
return { base: ref, root, files: relPaths.map((p) => join2(root, p)) };
|
|
4839
|
+
}
|
|
4840
|
+
return null;
|
|
4841
|
+
}
|
|
4842
|
+
function changedLineRanges(ref, path) {
|
|
4843
|
+
const diff = fileDiffAgainst(ref, path);
|
|
4844
|
+
if (diff === null)
|
|
4845
|
+
return null;
|
|
4846
|
+
return parseChangedLineRanges(diff)[path] ?? null;
|
|
4847
|
+
}
|
|
4848
|
+
function lineIsInRanges(line, ranges) {
|
|
4849
|
+
return ranges.some(([start, end]) => line >= start && line <= end);
|
|
4850
|
+
}
|
|
4851
|
+
function findingsInThisDiff(findings) {
|
|
4852
|
+
const diff = resolveDiff();
|
|
4853
|
+
if (diff === null)
|
|
4854
|
+
return [...findings];
|
|
4855
|
+
const changed = new Set(diff.files);
|
|
4856
|
+
const ranges = new Map;
|
|
4857
|
+
return findings.filter((f) => {
|
|
4858
|
+
const abs = resolve2(diff.root, f.file);
|
|
4859
|
+
if (!changed.has(abs))
|
|
4860
|
+
return false;
|
|
4861
|
+
const relPath = relative(diff.root, abs);
|
|
4862
|
+
if (!ranges.has(relPath))
|
|
4863
|
+
ranges.set(relPath, changedLineRanges(diff.base, relPath));
|
|
4864
|
+
const fileRanges = ranges.get(relPath) ?? null;
|
|
4865
|
+
return fileRanges === null ? true : lineIsInRanges(f.line, fileRanges);
|
|
4866
|
+
});
|
|
4867
|
+
}
|
|
4291
4868
|
|
|
4292
4869
|
// src/checks/bin/check-doc-coverage.ts
|
|
4293
4870
|
var CHECK_NAME = "doc-coverage";
|
|
@@ -4307,7 +4884,7 @@ function resolvePrBody() {
|
|
|
4307
4884
|
return process.env.PR_BODY;
|
|
4308
4885
|
if (process.env.PR_BODY_FILE) {
|
|
4309
4886
|
try {
|
|
4310
|
-
return
|
|
4887
|
+
return readFileSync3(process.env.PR_BODY_FILE, "utf8");
|
|
4311
4888
|
} catch {
|
|
4312
4889
|
return "";
|
|
4313
4890
|
}
|
|
@@ -4365,7 +4942,7 @@ function main() {
|
|
|
4365
4942
|
if (changed.length === 0) {
|
|
4366
4943
|
process.exit(0);
|
|
4367
4944
|
}
|
|
4368
|
-
const content = existsSync2(DOC_OWNERS_PATH) ?
|
|
4945
|
+
const content = existsSync2(DOC_OWNERS_PATH) ? readFileSync3(DOC_OWNERS_PATH, "utf8") : null;
|
|
4369
4946
|
const result = evaluateC5(changed, content, resolvePrBody(), existsSync2, waiverActive(), (p) => fileDiffAgainst(ref, p));
|
|
4370
4947
|
if (result.errors.length > 0) {
|
|
4371
4948
|
for (const message of result.errors) {
|