@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-coherence.ts
|
|
4
6
|
import { execFile as execFile4, execFileSync as execFileSync3 } from "node:child_process";
|
|
5
|
-
import { existsSync as existsSync2, readFileSync as
|
|
7
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
6
8
|
import { join as join2 } from "node:path";
|
|
7
9
|
import { promisify as promisify4 } from "node:util";
|
|
8
10
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
@@ -213,17 +215,18 @@ function splitIds(raw) {
|
|
|
213
215
|
return [];
|
|
214
216
|
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
|
|
215
217
|
}
|
|
216
|
-
function resolveIds(raw
|
|
218
|
+
function resolveIds(raw) {
|
|
217
219
|
const ids = splitIds(raw);
|
|
218
220
|
const resolved = [];
|
|
221
|
+
let lastSlug = null;
|
|
219
222
|
for (const id of ids) {
|
|
220
223
|
const qualified = id.match(SLUG_QUALIFIED_ID);
|
|
221
224
|
if (qualified) {
|
|
222
|
-
lastSlug
|
|
225
|
+
lastSlug = qualified[1] ?? null;
|
|
223
226
|
resolved.push(id);
|
|
224
|
-
} else if (lastSlug
|
|
225
|
-
resolved.push(`${lastSlug
|
|
226
|
-
lastSlug
|
|
227
|
+
} else if (lastSlug) {
|
|
228
|
+
resolved.push(`${lastSlug} ${id}`);
|
|
229
|
+
lastSlug = null;
|
|
227
230
|
} else {
|
|
228
231
|
resolved.push(id);
|
|
229
232
|
}
|
|
@@ -243,12 +246,7 @@ function parseRationaleDeps(body) {
|
|
|
243
246
|
const section = extractSection(body);
|
|
244
247
|
const dependsOn = [];
|
|
245
248
|
const conflictsWith = [];
|
|
246
|
-
let current = null;
|
|
247
249
|
const assignedFields = new Set;
|
|
248
|
-
const lastSlugByField = {
|
|
249
|
-
dependsOn: { current: null },
|
|
250
|
-
conflictsWith: { current: null }
|
|
251
|
-
};
|
|
252
250
|
const spanPattern = /`([^`]*)`/g;
|
|
253
251
|
let match = spanPattern.exec(section);
|
|
254
252
|
while (match !== null) {
|
|
@@ -258,13 +256,9 @@ function parseRationaleDeps(body) {
|
|
|
258
256
|
const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
|
|
259
257
|
if (!assignedFields.has(field)) {
|
|
260
258
|
assignedFields.add(field);
|
|
261
|
-
|
|
262
|
-
const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
|
|
259
|
+
const ids = resolveIds(labelMatch[2] ?? "");
|
|
263
260
|
pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
264
261
|
}
|
|
265
|
-
} else if (current) {
|
|
266
|
-
const ids = resolveIds(content, lastSlugByField[current]);
|
|
267
|
-
pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
268
262
|
}
|
|
269
263
|
match = spanPattern.exec(section);
|
|
270
264
|
}
|
|
@@ -1729,6 +1723,48 @@ function splitTableRow(row) {
|
|
|
1729
1723
|
parts.pop();
|
|
1730
1724
|
return parts.map((p) => p.split(PLACEHOLDER).join("|"));
|
|
1731
1725
|
}
|
|
1726
|
+
// ../../packages/aeg-core/src/blast-radius-domains.ts
|
|
1727
|
+
function resolveWorkspaceEntry(entry2, listDirs) {
|
|
1728
|
+
if (entry2.endsWith("/*")) {
|
|
1729
|
+
const baseDir = entry2.slice(0, -2);
|
|
1730
|
+
return listDirs(baseDir).map((name) => `${baseDir}/${name}`);
|
|
1731
|
+
}
|
|
1732
|
+
if (entry2.includes("*"))
|
|
1733
|
+
return [];
|
|
1734
|
+
return [entry2];
|
|
1735
|
+
}
|
|
1736
|
+
// ../../packages/aeg-core/src/consumer-enumeration.ts
|
|
1737
|
+
function deriveWorkspaceMemberDirs(workspaces, listDirs) {
|
|
1738
|
+
const positive = workspaces.filter((entry2) => !entry2.startsWith("!"));
|
|
1739
|
+
const negative = workspaces.filter((entry2) => entry2.startsWith("!")).map((entry2) => entry2.slice(1));
|
|
1740
|
+
const excluded = new Set;
|
|
1741
|
+
for (const entry2 of negative) {
|
|
1742
|
+
for (const member of resolveWorkspaceEntry(entry2, listDirs))
|
|
1743
|
+
excluded.add(member);
|
|
1744
|
+
}
|
|
1745
|
+
const members = new Set;
|
|
1746
|
+
for (const entry2 of positive) {
|
|
1747
|
+
for (const member of resolveWorkspaceEntry(entry2, listDirs)) {
|
|
1748
|
+
if (!excluded.has(member))
|
|
1749
|
+
members.add(member);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return [...members].sort();
|
|
1753
|
+
}
|
|
1754
|
+
function buildConsumersOf(workspaces, listDirs, readManifest) {
|
|
1755
|
+
const members = deriveWorkspaceMemberDirs(workspaces, listDirs);
|
|
1756
|
+
return (pkg) => {
|
|
1757
|
+
const target = `@attalabs/${pkg}`;
|
|
1758
|
+
return members.filter((dir) => {
|
|
1759
|
+
if (dir === `packages/${pkg}`)
|
|
1760
|
+
return false;
|
|
1761
|
+
const manifest = readManifest(dir);
|
|
1762
|
+
if (!manifest)
|
|
1763
|
+
return false;
|
|
1764
|
+
return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {});
|
|
1765
|
+
});
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1732
1768
|
// ../../packages/aeg-core/src/state-machine-model.ts
|
|
1733
1769
|
var FORGE_FACT_INPUTS = [
|
|
1734
1770
|
{
|
|
@@ -1794,6 +1830,156 @@ var DERIVED_STATUSES = [
|
|
|
1794
1830
|
"incoherent"
|
|
1795
1831
|
];
|
|
1796
1832
|
var DERIVABLE_STATUSES = DERIVED_STATUSES.filter((s) => s !== "backlog");
|
|
1833
|
+
// ../../packages/aeg-core/src/parse-ledger.ts
|
|
1834
|
+
function rowFromCells(cells) {
|
|
1835
|
+
if (cells.length < 7)
|
|
1836
|
+
return null;
|
|
1837
|
+
const phase = (cells[0] ?? "").trim();
|
|
1838
|
+
if (!phase)
|
|
1839
|
+
return null;
|
|
1840
|
+
return {
|
|
1841
|
+
phase,
|
|
1842
|
+
role: (cells[1] ?? "").trim(),
|
|
1843
|
+
agentModel: (cells[2] ?? "").trim(),
|
|
1844
|
+
tokensIn: parseIntCell(cells[3] ?? ""),
|
|
1845
|
+
tokensOut: parseIntCell(cells[4] ?? ""),
|
|
1846
|
+
cost: parseCostCell(cells[5] ?? ""),
|
|
1847
|
+
date: (cells[6] ?? "").trim()
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
function parseIntCell(cell) {
|
|
1851
|
+
const trimmed = cell.replace(/`/g, "").trim();
|
|
1852
|
+
if (!trimmed || isEmDashOrDash(trimmed))
|
|
1853
|
+
return null;
|
|
1854
|
+
const normalized = trimmed.replace(/[,_\s]/g, "");
|
|
1855
|
+
if (!/^-?\d+$/.test(normalized))
|
|
1856
|
+
return null;
|
|
1857
|
+
return Number(normalized);
|
|
1858
|
+
}
|
|
1859
|
+
function parseCostCell(cell) {
|
|
1860
|
+
const trimmed = cell.replace(/`/g, "").trim();
|
|
1861
|
+
if (!trimmed || isEmDashOrDash(trimmed))
|
|
1862
|
+
return null;
|
|
1863
|
+
const normalized = trimmed.replace(/^\$/, "").replace(/[,_\s]/g, "");
|
|
1864
|
+
if (!/^-?\d+(\.\d+)?$/.test(normalized))
|
|
1865
|
+
return null;
|
|
1866
|
+
return Number(normalized);
|
|
1867
|
+
}
|
|
1868
|
+
function isEmDashOrDash(s) {
|
|
1869
|
+
const t = s.trim();
|
|
1870
|
+
return t === "—" || t === "-" || t === "–";
|
|
1871
|
+
}
|
|
1872
|
+
// ../../packages/aeg-core/src/waiver-label.ts
|
|
1873
|
+
var WAIVER_LABEL = label("waiver-docs");
|
|
1874
|
+
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
1875
|
+
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
1876
|
+
function isPrincipal(login, principalAllowlist) {
|
|
1877
|
+
if (login === null)
|
|
1878
|
+
return false;
|
|
1879
|
+
const normalized = login.toLowerCase();
|
|
1880
|
+
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
1881
|
+
}
|
|
1882
|
+
function isWaiverLabelActorVerified(opts) {
|
|
1883
|
+
if (!opts.labels.includes(opts.label))
|
|
1884
|
+
return false;
|
|
1885
|
+
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// ../../packages/aeg-core/src/parse-token-report.ts
|
|
1889
|
+
var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
|
|
1890
|
+
var INLINE_FIELD_LIST = /Phase:\s*([^|]+?)\s*\|\s*Role:\s*([^|]+?)\s*\|\s*Agent\/Model:\s*([^|]+?)\s*\|\s*Tokens in:\s*([^|]+?)\s*\|\s*Tokens out:\s*([^|]+?)\s*\|\s*Cost:\s*([^|]+?)\s*\|\s*Date:\s*(.+?)\s*$/im;
|
|
1891
|
+
function parseTokenReportEntries(body) {
|
|
1892
|
+
const lines = body.split(/\r?\n/);
|
|
1893
|
+
const headingIdxs = [];
|
|
1894
|
+
lines.forEach((line, i) => {
|
|
1895
|
+
if (TOKEN_REPORT_HEADING.test(line))
|
|
1896
|
+
headingIdxs.push(i);
|
|
1897
|
+
});
|
|
1898
|
+
const out = [];
|
|
1899
|
+
for (let h = 0;h < headingIdxs.length; h++) {
|
|
1900
|
+
const start = headingIdxs[h] + 1;
|
|
1901
|
+
const end = h + 1 < headingIdxs.length ? headingIdxs[h + 1] : lines.length;
|
|
1902
|
+
const section = lines.slice(start, end).join(`
|
|
1903
|
+
`);
|
|
1904
|
+
const tableRows = parseTableSection(section);
|
|
1905
|
+
if (tableRows.length > 0) {
|
|
1906
|
+
out.push(...tableRows);
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
const inlineRow = parseInlineFieldList(section);
|
|
1910
|
+
if (inlineRow)
|
|
1911
|
+
out.push(inlineRow);
|
|
1912
|
+
}
|
|
1913
|
+
return out;
|
|
1914
|
+
}
|
|
1915
|
+
function parseTableSection(section) {
|
|
1916
|
+
const lines = section.split(/\r?\n/);
|
|
1917
|
+
let i = 0;
|
|
1918
|
+
for (;i < lines.length; i++) {
|
|
1919
|
+
if ((lines[i] ?? "").trim().startsWith("|"))
|
|
1920
|
+
break;
|
|
1921
|
+
}
|
|
1922
|
+
if (i + 1 >= lines.length)
|
|
1923
|
+
return [];
|
|
1924
|
+
i += 2;
|
|
1925
|
+
const out = [];
|
|
1926
|
+
for (;i < lines.length; i++) {
|
|
1927
|
+
const trimmed = (lines[i] ?? "").trim();
|
|
1928
|
+
if (!trimmed.startsWith("|"))
|
|
1929
|
+
break;
|
|
1930
|
+
const row = rowFromCells(splitTableRow(trimmed));
|
|
1931
|
+
if (row)
|
|
1932
|
+
out.push(row);
|
|
1933
|
+
}
|
|
1934
|
+
return out;
|
|
1935
|
+
}
|
|
1936
|
+
function parseInlineFieldList(section) {
|
|
1937
|
+
const m = section.match(INLINE_FIELD_LIST);
|
|
1938
|
+
if (!m)
|
|
1939
|
+
return null;
|
|
1940
|
+
return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
|
|
1941
|
+
}
|
|
1942
|
+
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1943
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1944
|
+
function firstThreeLines(comment) {
|
|
1945
|
+
return comment.split(`
|
|
1946
|
+
`).slice(0, 3).join(`
|
|
1947
|
+
`);
|
|
1948
|
+
}
|
|
1949
|
+
function extractHeadSha(comment) {
|
|
1950
|
+
const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
|
|
1951
|
+
return m ? m[1].toLowerCase() : null;
|
|
1952
|
+
}
|
|
1953
|
+
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1954
|
+
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1955
|
+
if (candidates.length === 0) {
|
|
1956
|
+
return {
|
|
1957
|
+
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1958
|
+
headSha: null,
|
|
1959
|
+
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
const latest = candidates[candidates.length - 1];
|
|
1963
|
+
const m = firstThreeLines(latest).match(valuePattern);
|
|
1964
|
+
if (!m) {
|
|
1965
|
+
return {
|
|
1966
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
|
|
1967
|
+
headSha: null,
|
|
1968
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
return {
|
|
1972
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1973
|
+
headSha: extractHeadSha(latest),
|
|
1974
|
+
danglingNote: null
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
function extractCodeReviewVerdict(comments) {
|
|
1978
|
+
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
1979
|
+
}
|
|
1980
|
+
function extractSecurityReviewVerdict(comments) {
|
|
1981
|
+
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
1982
|
+
}
|
|
1797
1983
|
// ../../packages/aeg-core/src/report-tokens.ts
|
|
1798
1984
|
var DASH_LOOKALIKES = {
|
|
1799
1985
|
"-": "‑",
|
|
@@ -1802,6 +1988,7 @@ var DASH_LOOKALIKES = {
|
|
|
1802
1988
|
};
|
|
1803
1989
|
var DASH_CHARS = new Set(Object.keys(DASH_LOOKALIKES));
|
|
1804
1990
|
// ../../packages/aeg-core/src/claude-code-transcript.ts
|
|
1991
|
+
import { createHash } from "node:crypto";
|
|
1805
1992
|
function summarizeTranscript(jsonl) {
|
|
1806
1993
|
const seen = new Set;
|
|
1807
1994
|
const components = {
|
|
@@ -1850,7 +2037,14 @@ function numberOr(value, fallback) {
|
|
|
1850
2037
|
function sanitizeKey(value) {
|
|
1851
2038
|
return value.replace(/[^A-Za-z0-9]+/g, "-");
|
|
1852
2039
|
}
|
|
2040
|
+
function collisionResistantKey(value) {
|
|
2041
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
2042
|
+
return `${sanitizeKey(value)}-${digest}`;
|
|
2043
|
+
}
|
|
1853
2044
|
function transcriptPointerPath(projectDir, tmpDir) {
|
|
2045
|
+
return `${tmpDir}/claude-transcript-${collisionResistantKey(projectDir)}.txt`;
|
|
2046
|
+
}
|
|
2047
|
+
function legacyTranscriptPointerPath(projectDir, tmpDir) {
|
|
1854
2048
|
return `${tmpDir}/claude-transcript-${sanitizeKey(projectDir)}.txt`;
|
|
1855
2049
|
}
|
|
1856
2050
|
function resolvePointer(explicitTranscriptPath, deps) {
|
|
@@ -1858,13 +2052,15 @@ function resolvePointer(explicitTranscriptPath, deps) {
|
|
|
1858
2052
|
return { path: explicitTranscriptPath, corroborated: true };
|
|
1859
2053
|
const projectDir = deps.env.CLAUDE_PROJECT_DIR ?? deps.cwd;
|
|
1860
2054
|
const tmpDir = deps.env.TMPDIR ?? "/tmp";
|
|
1861
|
-
const
|
|
2055
|
+
const primaryPointerPath = transcriptPointerPath(projectDir, tmpDir);
|
|
2056
|
+
const legacyPointerPath = legacyTranscriptPointerPath(projectDir, tmpDir);
|
|
1862
2057
|
const currentSessionId = deps.env.CLAUDE_CODE_SESSION_ID;
|
|
1863
|
-
|
|
2058
|
+
const pointerPath = deps.exists(primaryPointerPath) ? primaryPointerPath : deps.exists(legacyPointerPath) ? legacyPointerPath : undefined;
|
|
2059
|
+
if (!pointerPath) {
|
|
1864
2060
|
return {
|
|
1865
2061
|
pointerExisted: false,
|
|
1866
2062
|
corroborated: false,
|
|
1867
|
-
error: `No transcript pointer at ${
|
|
2063
|
+
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."
|
|
1868
2064
|
};
|
|
1869
2065
|
}
|
|
1870
2066
|
let contents;
|
|
@@ -1939,28 +2135,15 @@ function isTokenCollectionWiringBroken(capability) {
|
|
|
1939
2135
|
return false;
|
|
1940
2136
|
return capability.reason !== "no-transcript-resolved";
|
|
1941
2137
|
}
|
|
2138
|
+
// ../../packages/aeg-core/src/metering-io-guard.ts
|
|
2139
|
+
import { closeSync, constants as fsConstants, fstatSync, openSync, readFileSync } from "node:fs";
|
|
2140
|
+
var GUARD_OPEN_FLAGS = fsConstants.O_RDONLY | fsConstants.O_NONBLOCK | fsConstants.O_NOFOLLOW;
|
|
1942
2141
|
// ../../packages/aeg-core/src/file-classify.ts
|
|
1943
2142
|
function isCodeFile(p) {
|
|
1944
2143
|
if (p.endsWith(".md"))
|
|
1945
2144
|
return false;
|
|
1946
2145
|
return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
|
|
1947
2146
|
}
|
|
1948
|
-
// ../../packages/aeg-core/src/waiver-label.ts
|
|
1949
|
-
var WAIVER_LABEL = label("waiver-docs");
|
|
1950
|
-
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
1951
|
-
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
1952
|
-
function isPrincipal(login, principalAllowlist) {
|
|
1953
|
-
if (login === null)
|
|
1954
|
-
return false;
|
|
1955
|
-
const normalized = login.toLowerCase();
|
|
1956
|
-
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
1957
|
-
}
|
|
1958
|
-
function isWaiverLabelActorVerified(opts) {
|
|
1959
|
-
if (!opts.labels.includes(opts.label))
|
|
1960
|
-
return false;
|
|
1961
|
-
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
2147
|
// ../../packages/aeg-core/src/doc-owners.ts
|
|
1965
2148
|
var DOC_OWNERS_PATH = ".vinaya/doc-owners";
|
|
1966
2149
|
function parseDocOwners(content) {
|
|
@@ -2145,7 +2328,7 @@ function overrideActive(opts) {
|
|
|
2145
2328
|
return false;
|
|
2146
2329
|
}
|
|
2147
2330
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2148
|
-
import { createHash } from "node:crypto";
|
|
2331
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2149
2332
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2150
2333
|
function isPremiseHeader(line) {
|
|
2151
2334
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2177,6 +2360,32 @@ function parsePremiseBlock(prBody) {
|
|
|
2177
2360
|
}
|
|
2178
2361
|
return assertions;
|
|
2179
2362
|
}
|
|
2363
|
+
function premiseBlockText(prBody) {
|
|
2364
|
+
const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
|
|
2365
|
+
const lines = searchIn.split(/\r?\n/);
|
|
2366
|
+
let startIdx = -1;
|
|
2367
|
+
let endIdxExclusive = lines.length;
|
|
2368
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2369
|
+
const line = lines[i].trim();
|
|
2370
|
+
if (startIdx === -1) {
|
|
2371
|
+
if (isPremiseHeader(line))
|
|
2372
|
+
startIdx = i;
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
if (line === "") {
|
|
2376
|
+
endIdxExclusive = i;
|
|
2377
|
+
break;
|
|
2378
|
+
}
|
|
2379
|
+
if (!PREMISE_LINE.test(line)) {
|
|
2380
|
+
endIdxExclusive = i;
|
|
2381
|
+
break;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
if (startIdx === -1)
|
|
2385
|
+
return null;
|
|
2386
|
+
return lines.slice(startIdx, endIdxExclusive).join(`
|
|
2387
|
+
`);
|
|
2388
|
+
}
|
|
2180
2389
|
function checkPremises(assertions, fileReader) {
|
|
2181
2390
|
const failures = [];
|
|
2182
2391
|
for (const a of assertions) {
|
|
@@ -2190,7 +2399,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2190
2399
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2191
2400
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2192
2401
|
} else if (a.kind === "sha256") {
|
|
2193
|
-
const actual =
|
|
2402
|
+
const actual = createHash2("sha256").update(content).digest("hex");
|
|
2194
2403
|
if (actual !== a.value.toLowerCase()) {
|
|
2195
2404
|
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.`);
|
|
2196
2405
|
}
|
|
@@ -2263,15 +2472,18 @@ function testPlanRegion(prBody) {
|
|
|
2263
2472
|
return located.found ? located.section : prBody;
|
|
2264
2473
|
}
|
|
2265
2474
|
function checkTestPlan(prBody) {
|
|
2266
|
-
const
|
|
2475
|
+
const located = locateTestPlanSection(prBody);
|
|
2476
|
+
const region = located.found ? located.section : prBody;
|
|
2267
2477
|
if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
|
|
2268
2478
|
return { status: "pass", errors: [] };
|
|
2269
2479
|
if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
|
|
2270
2480
|
return { status: "pass", errors: [] };
|
|
2481
|
+
if (located.found && extractFencedBlocks(region).length > 0)
|
|
2482
|
+
return { status: "pass", errors: [] };
|
|
2271
2483
|
return {
|
|
2272
2484
|
status: "fail",
|
|
2273
2485
|
errors: [
|
|
2274
|
-
"brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
|
|
2486
|
+
"brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, a fenced `[agent]` command list, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
|
|
2275
2487
|
]
|
|
2276
2488
|
};
|
|
2277
2489
|
}
|
|
@@ -2394,8 +2606,162 @@ function isBriefShaped(prBody) {
|
|
|
2394
2606
|
const stripped = stripCode(prBody);
|
|
2395
2607
|
return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
|
|
2396
2608
|
}
|
|
2609
|
+
var COMMAND_WORDS = ["export", "bun", "gh", "git", "grep", "sed", "cat", "diff", "vinaya"];
|
|
2610
|
+
var BRIEF_RULES_SINCE_PR = 394;
|
|
2611
|
+
var AGENT_BOXES_REFUSED_SINCE_PR = 396;
|
|
2612
|
+
var AGENT_BOX_LINE_RE = /^-\s*\[[ xX]\]\s*\*{2}\[agent\]\*{2}/im;
|
|
2613
|
+
function checkNoAgentBoxes(prBody) {
|
|
2614
|
+
const region = testPlanRegion(prBody);
|
|
2615
|
+
if (!AGENT_BOX_LINE_RE.test(region))
|
|
2616
|
+
return { status: "pass", errors: [] };
|
|
2617
|
+
return {
|
|
2618
|
+
status: "fail",
|
|
2619
|
+
errors: [
|
|
2620
|
+
"brief-validation no agent boxes: the Test Plan carries a checkbox `[agent]` item — an agent never ticks a box or edits a PR body (Principal ruling, 2026-09-03, after PR #395). Render the `[agent]` half as a fenced command list instead; `vinaya pr report` runs each command from the PR head and writes its actual output into the AEG:EVIDENCE block. `[principal]` checkboxes are unaffected."
|
|
2621
|
+
]
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
function rolloutThresholdFor(message) {
|
|
2625
|
+
if (message.startsWith("brief-validation unpinned code claim:") || message.startsWith("brief-validation commands carry output:") || message.startsWith("brief-validation consumer tests:") || message.startsWith("brief-validation defeat cases:")) {
|
|
2626
|
+
return BRIEF_RULES_SINCE_PR;
|
|
2627
|
+
}
|
|
2628
|
+
if (message.startsWith("brief-validation no agent boxes:"))
|
|
2629
|
+
return AGENT_BOXES_REFUSED_SINCE_PR;
|
|
2630
|
+
return null;
|
|
2631
|
+
}
|
|
2632
|
+
function partitionBriefErrorsByRollout(errors, prNumber) {
|
|
2633
|
+
const blocking = [];
|
|
2634
|
+
const info = [];
|
|
2635
|
+
for (const e of errors) {
|
|
2636
|
+
const threshold = rolloutThresholdFor(e);
|
|
2637
|
+
const grandfathered = threshold !== null && prNumber !== null && prNumber < threshold;
|
|
2638
|
+
(grandfathered ? info : blocking).push(e);
|
|
2639
|
+
}
|
|
2640
|
+
return { blocking, info };
|
|
2641
|
+
}
|
|
2642
|
+
function extractFencedBlocks(text) {
|
|
2643
|
+
const re = /^[ \t]*(`{3,}|~{3,})([^\n]*)\n([\s\S]*?)^[ \t]*\1[ \t]*$/gm;
|
|
2644
|
+
const blocks = [];
|
|
2645
|
+
let m = re.exec(text);
|
|
2646
|
+
while (m !== null) {
|
|
2647
|
+
const lang = m[2].trim().toLowerCase().split(/\s+/)[0] ?? "";
|
|
2648
|
+
blocks.push({ start: m.index, end: m.index + m[0].length, lang, content: m[3] });
|
|
2649
|
+
m = re.exec(text);
|
|
2650
|
+
}
|
|
2651
|
+
return blocks;
|
|
2652
|
+
}
|
|
2653
|
+
function firstNonBlankLine(text) {
|
|
2654
|
+
const line = text.split(`
|
|
2655
|
+
`).find((l) => l.trim().length > 0);
|
|
2656
|
+
return line?.trim() ?? "";
|
|
2657
|
+
}
|
|
2658
|
+
var FILE_LINE_RE = /\b[\w./-]+\.(?:tsx?|jsx?|mjs|cjs|md|mdx|json|ya?ml|sh|py|toml):\d+\b/g;
|
|
2659
|
+
function checkNoUnpinnedCodeClaims(prBody) {
|
|
2660
|
+
const premiseText = premiseBlockText(prBody);
|
|
2661
|
+
const withoutPremise = premiseText ? prBody.replace(premiseText, "") : prBody;
|
|
2662
|
+
const scanned = stripCode(withoutPremise, { inlineSpans: "keep" });
|
|
2663
|
+
const matches = scanned.match(FILE_LINE_RE) ?? [];
|
|
2664
|
+
if (matches.length === 0)
|
|
2665
|
+
return { status: "pass", errors: [] };
|
|
2666
|
+
return {
|
|
2667
|
+
status: "fail",
|
|
2668
|
+
errors: matches.map((m) => `brief-validation unpinned code claim: "${m}" states a code fact by file-and-line reference outside a \`Premise:\` pin and outside a fenced code block — pin it in \`Premise:\`, or show the executed command and its output instead.`)
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
function extractNumberedSection(prBody, num) {
|
|
2672
|
+
const startRe = new RegExp(`^#{1,4}\\s*(?:\\*\\*)?${num}[a-z]?\\.\\s`, "im");
|
|
2673
|
+
const start = startRe.exec(prBody);
|
|
2674
|
+
if (!start)
|
|
2675
|
+
return null;
|
|
2676
|
+
const afterStart = start.index + start[0].length;
|
|
2677
|
+
const rest = prBody.slice(afterStart);
|
|
2678
|
+
const next = /^#{1,4}\s/m.exec(rest);
|
|
2679
|
+
return next ? rest.slice(0, next.index) : rest;
|
|
2680
|
+
}
|
|
2681
|
+
function commandWordOf(line) {
|
|
2682
|
+
return line.trim().split(/\s+/)[0] ?? "";
|
|
2683
|
+
}
|
|
2684
|
+
function isCommandBlock(content) {
|
|
2685
|
+
return COMMAND_WORDS.includes(commandWordOf(firstNonBlankLine(content)));
|
|
2686
|
+
}
|
|
2687
|
+
function isStep0Block(content) {
|
|
2688
|
+
return firstNonBlankLine(content).startsWith("git worktree add");
|
|
2689
|
+
}
|
|
2690
|
+
function checkCommandsCarryOutput(prBody) {
|
|
2691
|
+
const errors = [];
|
|
2692
|
+
for (const [label2, num] of [
|
|
2693
|
+
["5", 5],
|
|
2694
|
+
["6", 6]
|
|
2695
|
+
]) {
|
|
2696
|
+
const section = extractNumberedSection(prBody, num);
|
|
2697
|
+
if (!section)
|
|
2698
|
+
continue;
|
|
2699
|
+
const blocks = extractFencedBlocks(section);
|
|
2700
|
+
for (let i = 0;i < blocks.length; i++) {
|
|
2701
|
+
const block = blocks[i];
|
|
2702
|
+
if (isStep0Block(block.content) || !isCommandBlock(block.content))
|
|
2703
|
+
continue;
|
|
2704
|
+
const next = blocks[i + 1];
|
|
2705
|
+
if (!next || isCommandBlock(next.content)) {
|
|
2706
|
+
errors.push(`brief-validation commands carry output: §${label2} has a command block ("${firstNonBlankLine(block.content)}") not immediately followed by a non-command output block — the very next fenced block must hold that command's actual output, not another command.`);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2711
|
+
}
|
|
2712
|
+
var CONSUMER_TESTS_SENTINEL_RE = /consumer-tests\s*:\s*none\s*[-—–]\s*\S/i;
|
|
2713
|
+
function packagesNamedIn(text) {
|
|
2714
|
+
const re = /\bpackages\/([A-Za-z0-9_-]+)\//g;
|
|
2715
|
+
const pkgs = new Set;
|
|
2716
|
+
let m = re.exec(text);
|
|
2717
|
+
while (m !== null) {
|
|
2718
|
+
pkgs.add(m[1]);
|
|
2719
|
+
m = re.exec(text);
|
|
2720
|
+
}
|
|
2721
|
+
return [...pkgs];
|
|
2722
|
+
}
|
|
2723
|
+
function hasTestPathForConsumer(text, consumerDir) {
|
|
2724
|
+
const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2725
|
+
const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
|
|
2726
|
+
return re.test(text);
|
|
2727
|
+
}
|
|
2728
|
+
function checkConsumerTests(prBody, consumersOf) {
|
|
2729
|
+
const section4 = extractNumberedSection(prBody, 4);
|
|
2730
|
+
if (!section4)
|
|
2731
|
+
return { status: "pass", errors: [] };
|
|
2732
|
+
if (CONSUMER_TESTS_SENTINEL_RE.test(section4))
|
|
2733
|
+
return { status: "pass", errors: [] };
|
|
2734
|
+
const errors = [];
|
|
2735
|
+
for (const pkg of packagesNamedIn(section4)) {
|
|
2736
|
+
for (const consumerDir of consumersOf(pkg)) {
|
|
2737
|
+
if (hasTestPathForConsumer(section4, consumerDir))
|
|
2738
|
+
continue;
|
|
2739
|
+
errors.push(`brief-validation consumer tests: §4 names a path under packages/${pkg}/, and ${consumerDir} depends on @attalabs/${pkg}, but no test path under ${consumerDir} is named in §4 — name one, or add \`consumer-tests: none — <reason>\`.`);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2743
|
+
}
|
|
2744
|
+
var CHECK_NAME_RE = /\bcheck-[a-z0-9-]+(?:\.ts)?\b/;
|
|
2745
|
+
var FORGE_WRITE_COMMAND_RE = /\bgh\s+(?:pr|issue)\s+(?:create|merge|close|comment|edit|review)\b|\bgit\s+push\b|\bvinaya\s+pr\s+(?:create|report\s+--write)\b/i;
|
|
2746
|
+
var DEFEAT_CASES_RE = /defeat cases\s*:/i;
|
|
2747
|
+
function checkDefeatCases(prBody) {
|
|
2748
|
+
const section4 = extractNumberedSection(prBody, 4);
|
|
2749
|
+
if (!section4)
|
|
2750
|
+
return { status: "pass", errors: [] };
|
|
2751
|
+
if (!CHECK_NAME_RE.test(section4) && !FORGE_WRITE_COMMAND_RE.test(section4))
|
|
2752
|
+
return { status: "pass", errors: [] };
|
|
2753
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2754
|
+
if (DEFEAT_CASES_RE.test(section6))
|
|
2755
|
+
return { status: "pass", errors: [] };
|
|
2756
|
+
return {
|
|
2757
|
+
status: "fail",
|
|
2758
|
+
errors: [
|
|
2759
|
+
"brief-validation defeat cases: §4 names a check or a forge-writing command, but §6 carries no `Defeat cases:` line — name the inputs that would defeat this check, or that this command must not accidentally trigger on."
|
|
2760
|
+
]
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2397
2763
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2398
|
-
const { requireClosesN = true } = options;
|
|
2764
|
+
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2399
2765
|
const results = [
|
|
2400
2766
|
checkTierField(prBody, readTier),
|
|
2401
2767
|
checkTestPlan(prBody),
|
|
@@ -2408,10 +2774,219 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2408
2774
|
checkAutonomyClause(prBody),
|
|
2409
2775
|
checkProjectField(prBody),
|
|
2410
2776
|
checkForField(prBody),
|
|
2777
|
+
checkNoUnpinnedCodeClaims(prBody),
|
|
2778
|
+
checkNoAgentBoxes(prBody),
|
|
2779
|
+
checkCommandsCarryOutput(prBody),
|
|
2780
|
+
checkConsumerTests(prBody, consumersOf),
|
|
2781
|
+
checkDefeatCases(prBody),
|
|
2411
2782
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2412
2783
|
];
|
|
2413
2784
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2414
2785
|
}
|
|
2786
|
+
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2787
|
+
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
2788
|
+
var STATIC_PORTABLE_PREFIXES = [
|
|
2789
|
+
"roles/",
|
|
2790
|
+
"contracts/",
|
|
2791
|
+
"skills/",
|
|
2792
|
+
".github/",
|
|
2793
|
+
".vinaya/",
|
|
2794
|
+
".claude/",
|
|
2795
|
+
".git/",
|
|
2796
|
+
".husky/"
|
|
2797
|
+
];
|
|
2798
|
+
var EXEMPT_LITERALS = new Set([
|
|
2799
|
+
"path/inside/the/shipped/diff.ts",
|
|
2800
|
+
"path/inside/the/surface.ts",
|
|
2801
|
+
"apps/x/specs/..."
|
|
2802
|
+
]);
|
|
2803
|
+
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
2804
|
+
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
2805
|
+
var VENDOR_NAME_SOURCE = "\\bclaude code\\b|\\bclaude\\b|\\banthropic\\b|\\bchatgpt\\b|\\bopenai\\b|\\bgpt\\b|\\bgemini\\b|\\bcodex\\b|\\bgrok\\b|\\bdeepseek\\b|\\bopus\\b|\\bsonnet\\b|\\bhaiku\\b";
|
|
2806
|
+
var VENDOR_EXAMPLE_START = /<!--\s*AEG:VENDOR-EXAMPLE:START\s*-->/;
|
|
2807
|
+
var VENDOR_EXAMPLE_END = /<!--\s*AEG:VENDOR-EXAMPLE:END\s*-->/;
|
|
2808
|
+
function maskVendorExampleRegion(masked) {
|
|
2809
|
+
const start = VENDOR_EXAMPLE_START.exec(masked);
|
|
2810
|
+
if (!start)
|
|
2811
|
+
return masked;
|
|
2812
|
+
const afterStart = start.index + start[0].length;
|
|
2813
|
+
const end = VENDOR_EXAMPLE_END.exec(masked.slice(afterStart));
|
|
2814
|
+
if (!end)
|
|
2815
|
+
return masked;
|
|
2816
|
+
const regionEnd = afterStart + end.index + end[0].length;
|
|
2817
|
+
const region = masked.slice(start.index, regionEnd);
|
|
2818
|
+
const blanked = region.replace(/[^\n]/g, " ");
|
|
2819
|
+
return masked.slice(0, start.index) + blanked + masked.slice(regionEnd);
|
|
2820
|
+
}
|
|
2821
|
+
function extractVendorMentions(content) {
|
|
2822
|
+
const scoped = maskVendorExampleRegion(maskCode(content));
|
|
2823
|
+
const found = [];
|
|
2824
|
+
const pattern = new RegExp(VENDOR_NAME_SOURCE, "gi");
|
|
2825
|
+
let match = pattern.exec(scoped);
|
|
2826
|
+
while (match !== null) {
|
|
2827
|
+
found.push({ name: match[0], index: match.index });
|
|
2828
|
+
match = pattern.exec(scoped);
|
|
2829
|
+
}
|
|
2830
|
+
return found;
|
|
2831
|
+
}
|
|
2832
|
+
function extractCitedPaths(content) {
|
|
2833
|
+
const found = [];
|
|
2834
|
+
const spanPattern = /`([^`\n]+)`/g;
|
|
2835
|
+
let match = spanPattern.exec(content);
|
|
2836
|
+
while (match !== null) {
|
|
2837
|
+
const cited = match[1] ?? "";
|
|
2838
|
+
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
2839
|
+
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
2840
|
+
found.push({ cited, index: match.index });
|
|
2841
|
+
}
|
|
2842
|
+
match = spanPattern.exec(content);
|
|
2843
|
+
}
|
|
2844
|
+
return found;
|
|
2845
|
+
}
|
|
2846
|
+
function lineAtIndex(content, index) {
|
|
2847
|
+
let line = 1;
|
|
2848
|
+
for (let i = 0;i < index; i++) {
|
|
2849
|
+
if (content.charCodeAt(i) === 10)
|
|
2850
|
+
line++;
|
|
2851
|
+
}
|
|
2852
|
+
return line;
|
|
2853
|
+
}
|
|
2854
|
+
function isPortable(cited, shipsPrefix) {
|
|
2855
|
+
if (cited.startsWith(shipsPrefix))
|
|
2856
|
+
return true;
|
|
2857
|
+
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
2858
|
+
}
|
|
2859
|
+
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
2860
|
+
const findings = [];
|
|
2861
|
+
for (const file of files) {
|
|
2862
|
+
if (!file.path.startsWith(shipsPrefix))
|
|
2863
|
+
continue;
|
|
2864
|
+
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
2865
|
+
if (EXEMPT_LITERALS.has(cited))
|
|
2866
|
+
continue;
|
|
2867
|
+
if (isPortable(cited, shipsPrefix))
|
|
2868
|
+
continue;
|
|
2869
|
+
findings.push({
|
|
2870
|
+
file: file.path,
|
|
2871
|
+
line: lineAtIndex(file.content, index),
|
|
2872
|
+
cited,
|
|
2873
|
+
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`,
|
|
2874
|
+
kind: "path"
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
for (const { name, index } of extractVendorMentions(file.content)) {
|
|
2878
|
+
findings.push({
|
|
2879
|
+
file: file.path,
|
|
2880
|
+
line: lineAtIndex(file.content, index),
|
|
2881
|
+
cited: name,
|
|
2882
|
+
message: `names "${name}" outside the one fenced vendor-example home (an <!-- AEG:VENDOR-EXAMPLE:START --> … <!-- AEG:VENDOR-EXAMPLE:END --> pair) — portable doctrine refers to a host generically everywhere else`,
|
|
2883
|
+
kind: "vendor-name"
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
return findings;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// ../../packages/aeg-core/src/doctrine-no-procedures.ts
|
|
2891
|
+
function vendorExampleSpan(content) {
|
|
2892
|
+
const start = VENDOR_EXAMPLE_START.exec(content);
|
|
2893
|
+
if (!start)
|
|
2894
|
+
return null;
|
|
2895
|
+
const afterStart = start.index + start[0].length;
|
|
2896
|
+
const end = VENDOR_EXAMPLE_END.exec(content.slice(afterStart));
|
|
2897
|
+
if (!end)
|
|
2898
|
+
return null;
|
|
2899
|
+
return { start: start.index, end: afterStart + end.index + end[0].length };
|
|
2900
|
+
}
|
|
2901
|
+
function isCommandLine(line) {
|
|
2902
|
+
const word = line.trim().split(/\s+/)[0] ?? "";
|
|
2903
|
+
return COMMAND_WORDS.includes(word);
|
|
2904
|
+
}
|
|
2905
|
+
var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
|
|
2906
|
+
function procedureLineNumberAt(content, index) {
|
|
2907
|
+
return content.slice(0, index).split(`
|
|
2908
|
+
`).length;
|
|
2909
|
+
}
|
|
2910
|
+
function isTemplatePath(path) {
|
|
2911
|
+
return /(^|\/)templates\//.test(path);
|
|
2912
|
+
}
|
|
2913
|
+
function checkDoctrineNoProcedures(files) {
|
|
2914
|
+
const findings = [];
|
|
2915
|
+
for (const file of files) {
|
|
2916
|
+
if (isTemplatePath(file.path))
|
|
2917
|
+
continue;
|
|
2918
|
+
const span = vendorExampleSpan(file.content);
|
|
2919
|
+
for (const block of extractFencedBlocks(file.content)) {
|
|
2920
|
+
if (span && block.start >= span.start && block.end <= span.end)
|
|
2921
|
+
continue;
|
|
2922
|
+
if (!SHELL_LANGS.has(block.lang))
|
|
2923
|
+
continue;
|
|
2924
|
+
const commandLineCount = block.content.split(`
|
|
2925
|
+
`).filter(isCommandLine).length;
|
|
2926
|
+
if (commandLineCount >= 2) {
|
|
2927
|
+
findings.push({
|
|
2928
|
+
file: file.path,
|
|
2929
|
+
line: procedureLineNumberAt(file.content, block.start),
|
|
2930
|
+
message: `this fenced block has ${commandLineCount} shell-command lines — this sequence is a \`vinaya\` command, name it (or move it inside the \`AEG:VENDOR-EXAMPLE\` anchor / a \`templates/\` file).`
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
return findings;
|
|
2936
|
+
}
|
|
2937
|
+
// ../../packages/aeg-core/src/pr-report-density.ts
|
|
2938
|
+
function headingSectionBody(prBody, heading) {
|
|
2939
|
+
const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "im");
|
|
2940
|
+
const m = headingRe.exec(prBody);
|
|
2941
|
+
if (!m)
|
|
2942
|
+
return null;
|
|
2943
|
+
const start = m.index + m[0].length;
|
|
2944
|
+
const rest = prBody.slice(start);
|
|
2945
|
+
const nextHeading = /^#{1,2}\s/m.exec(rest);
|
|
2946
|
+
return nextHeading ? rest.slice(0, nextHeading.index) : rest;
|
|
2947
|
+
}
|
|
2948
|
+
function stripAnchorBlocks(sectionText) {
|
|
2949
|
+
let result = sectionText;
|
|
2950
|
+
for (const field of ["CLOSES", "PROJECT", "TIER", "PREMISE", "TEST-PLAN", "EVIDENCE"]) {
|
|
2951
|
+
for (;; ) {
|
|
2952
|
+
const bounds = anchoredRegionBounds(result, field);
|
|
2953
|
+
if (!bounds)
|
|
2954
|
+
break;
|
|
2955
|
+
result = result.slice(0, bounds.outerStart) + result.slice(bounds.outerEnd);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
return result;
|
|
2959
|
+
}
|
|
2960
|
+
function paragraphCount(sectionText) {
|
|
2961
|
+
const stripped = stripCode(stripAnchorBlocks(sectionText)).trim();
|
|
2962
|
+
if (stripped.length === 0)
|
|
2963
|
+
return 0;
|
|
2964
|
+
return stripped.split(/\n[ \t]*\n/).map((block) => block.trim()).filter((block) => block.length > 0).length;
|
|
2965
|
+
}
|
|
2966
|
+
function checkSectionDensity(prBody, heading) {
|
|
2967
|
+
const body = headingSectionBody(prBody, heading);
|
|
2968
|
+
if (body === null)
|
|
2969
|
+
return { status: "pass", errors: [] };
|
|
2970
|
+
const count = paragraphCount(body);
|
|
2971
|
+
if (count <= 1)
|
|
2972
|
+
return { status: "pass", errors: [] };
|
|
2973
|
+
return {
|
|
2974
|
+
status: "fail",
|
|
2975
|
+
errors: [
|
|
2976
|
+
`pr-report-density ${heading}: "## ${heading}" holds ${count} paragraphs — the canonical PR-report form (aeg-root/roles/developer.md § PR body) requires exactly one. Collapse it to one paragraph; move the rest into a section of your own below the canonical four ("Add anything you want beneath the four sections", developer.md), a commit message, or the changeset — never into the AEG:EVIDENCE block, which is emitted only, never hand-typed.`
|
|
2977
|
+
]
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
function checkSummaryDensity(prBody) {
|
|
2981
|
+
return checkSectionDensity(prBody, "Summary");
|
|
2982
|
+
}
|
|
2983
|
+
function checkScopeDensity(prBody) {
|
|
2984
|
+
return checkSectionDensity(prBody, "Scope");
|
|
2985
|
+
}
|
|
2986
|
+
function checkPrReportDensity(prBody) {
|
|
2987
|
+
const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
|
|
2988
|
+
return { errors: results.flatMap((r) => r.errors) };
|
|
2989
|
+
}
|
|
2415
2990
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
2416
2991
|
function hasRationaleField(body, labelPattern) {
|
|
2417
2992
|
const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
|
|
@@ -2663,6 +3238,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
2663
3238
|
const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
|
|
2664
3239
|
if (!depEntry)
|
|
2665
3240
|
continue;
|
|
3241
|
+
const sameTask = depEntry.trancheSlug === e.trancheSlug && depEntry.task.id === e.task.id;
|
|
3242
|
+
const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
|
|
3243
|
+
if (sameTask || sameIssue) {
|
|
3244
|
+
failures.push({
|
|
3245
|
+
issue: e.task.issue,
|
|
3246
|
+
tranche: e.trancheSlug,
|
|
3247
|
+
task: e.task.id,
|
|
3248
|
+
reason: `INTERNAL: parsed a self-dependency (depends-on ${dep}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream.`
|
|
3249
|
+
});
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
2666
3252
|
const depFacts = depEntry.facts;
|
|
2667
3253
|
const depClosed = depFacts?.issueState === "closed";
|
|
2668
3254
|
if (!depClosed) {
|
|
@@ -2806,41 +3392,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2806
3392
|
}
|
|
2807
3393
|
return { ok: true, expectedIssue };
|
|
2808
3394
|
}
|
|
2809
|
-
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2810
|
-
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2811
|
-
function extractHeadSha(comment) {
|
|
2812
|
-
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2813
|
-
return m ? m[1].toLowerCase() : null;
|
|
2814
|
-
}
|
|
2815
|
-
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2816
|
-
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2817
|
-
if (clearHits.length > 0) {
|
|
2818
|
-
const latest = clearHits[clearHits.length - 1];
|
|
2819
|
-
const m = latest.match(valuePattern);
|
|
2820
|
-
return {
|
|
2821
|
-
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2822
|
-
headSha: extractHeadSha(latest),
|
|
2823
|
-
danglingNote: null
|
|
2824
|
-
};
|
|
2825
|
-
}
|
|
2826
|
-
return {
|
|
2827
|
-
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2828
|
-
headSha: null,
|
|
2829
|
-
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2830
|
-
};
|
|
2831
|
-
}
|
|
2832
|
-
function extractCodeReviewVerdict(comments) {
|
|
2833
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
2834
|
-
}
|
|
2835
|
-
function extractSecurityReviewVerdict(comments) {
|
|
2836
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
2837
|
-
}
|
|
2838
3395
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
2839
3396
|
function isBoundToHead(extraction, headSha) {
|
|
2840
3397
|
if (!extraction.headSha)
|
|
2841
3398
|
return false;
|
|
2842
3399
|
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2843
3400
|
}
|
|
3401
|
+
function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
3402
|
+
if (patchIdOf === undefined || !extraction.headSha)
|
|
3403
|
+
return false;
|
|
3404
|
+
const judged = patchIdOf(extraction.headSha);
|
|
3405
|
+
const current = patchIdOf(headSha);
|
|
3406
|
+
if (judged === null || current === null)
|
|
3407
|
+
return false;
|
|
3408
|
+
return judged === current;
|
|
3409
|
+
}
|
|
3410
|
+
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3411
|
+
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3412
|
+
}
|
|
2844
3413
|
function checkReviewGate(input) {
|
|
2845
3414
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2846
3415
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2856,6 +3425,8 @@ function checkReviewGate(input) {
|
|
|
2856
3425
|
waived: true
|
|
2857
3426
|
};
|
|
2858
3427
|
}
|
|
3428
|
+
const reportedMechanicalChecks = input.mechanicalChecks.filter((c) => c.bucket !== "skipping" && c.bucket !== "neutral");
|
|
3429
|
+
const mechanicalChecksClean = reportedMechanicalChecks.length > 0 && reportedMechanicalChecks.every((c) => c.bucket === "pass");
|
|
2859
3430
|
const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
|
|
2860
3431
|
const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
|
|
2861
3432
|
const verifiedBodies = verified.map((c) => c.body);
|
|
@@ -2863,12 +3434,12 @@ function checkReviewGate(input) {
|
|
|
2863
3434
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2864
3435
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2865
3436
|
const securityClean = security.value === "PASS";
|
|
2866
|
-
const codeReviewBound =
|
|
2867
|
-
const securityBound =
|
|
2868
|
-
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
3437
|
+
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3438
|
+
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3439
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
|
|
2869
3440
|
return {
|
|
2870
3441
|
verdict: "pass",
|
|
2871
|
-
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
3442
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
|
|
2872
3443
|
waived: false
|
|
2873
3444
|
};
|
|
2874
3445
|
}
|
|
@@ -2883,6 +3454,9 @@ function checkReviewGate(input) {
|
|
|
2883
3454
|
} else if (!securityBound) {
|
|
2884
3455
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2885
3456
|
}
|
|
3457
|
+
if (!mechanicalChecksClean) {
|
|
3458
|
+
problems.push(reportedMechanicalChecks.length === 0 ? "no mechanical checks have reported for this head yet" : `mechanical check(s) not green: ${reportedMechanicalChecks.filter((c) => c.bucket !== "pass").map((c) => `${c.name} (${c.bucket})`).join(", ")}`);
|
|
3459
|
+
}
|
|
2886
3460
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2887
3461
|
return {
|
|
2888
3462
|
verdict: "fail",
|
|
@@ -3162,72 +3736,6 @@ function compareToBaseline(current, baseline) {
|
|
|
3162
3736
|
perTool
|
|
3163
3737
|
};
|
|
3164
3738
|
}
|
|
3165
|
-
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
3166
|
-
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
3167
|
-
var STATIC_PORTABLE_PREFIXES = [
|
|
3168
|
-
"roles/",
|
|
3169
|
-
"contracts/",
|
|
3170
|
-
"skills/",
|
|
3171
|
-
".github/",
|
|
3172
|
-
".vinaya/",
|
|
3173
|
-
".claude/",
|
|
3174
|
-
".git/",
|
|
3175
|
-
".husky/"
|
|
3176
|
-
];
|
|
3177
|
-
var EXEMPT_LITERALS = new Set([
|
|
3178
|
-
"path/inside/the/shipped/diff.ts",
|
|
3179
|
-
"path/inside/the/surface.ts",
|
|
3180
|
-
"apps/x/specs/..."
|
|
3181
|
-
]);
|
|
3182
|
-
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
3183
|
-
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
3184
|
-
function extractCitedPaths(content) {
|
|
3185
|
-
const found = [];
|
|
3186
|
-
const spanPattern = /`([^`\n]+)`/g;
|
|
3187
|
-
let match = spanPattern.exec(content);
|
|
3188
|
-
while (match !== null) {
|
|
3189
|
-
const cited = match[1] ?? "";
|
|
3190
|
-
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
3191
|
-
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
3192
|
-
found.push({ cited, index: match.index });
|
|
3193
|
-
}
|
|
3194
|
-
match = spanPattern.exec(content);
|
|
3195
|
-
}
|
|
3196
|
-
return found;
|
|
3197
|
-
}
|
|
3198
|
-
function lineAtIndex(content, index) {
|
|
3199
|
-
let line = 1;
|
|
3200
|
-
for (let i = 0;i < index; i++) {
|
|
3201
|
-
if (content.charCodeAt(i) === 10)
|
|
3202
|
-
line++;
|
|
3203
|
-
}
|
|
3204
|
-
return line;
|
|
3205
|
-
}
|
|
3206
|
-
function isPortable(cited, shipsPrefix) {
|
|
3207
|
-
if (cited.startsWith(shipsPrefix))
|
|
3208
|
-
return true;
|
|
3209
|
-
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
3210
|
-
}
|
|
3211
|
-
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
3212
|
-
const findings = [];
|
|
3213
|
-
for (const file of files) {
|
|
3214
|
-
if (!file.path.startsWith(shipsPrefix))
|
|
3215
|
-
continue;
|
|
3216
|
-
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
3217
|
-
if (EXEMPT_LITERALS.has(cited))
|
|
3218
|
-
continue;
|
|
3219
|
-
if (isPortable(cited, shipsPrefix))
|
|
3220
|
-
continue;
|
|
3221
|
-
findings.push({
|
|
3222
|
-
file: file.path,
|
|
3223
|
-
line: lineAtIndex(file.content, index),
|
|
3224
|
-
cited,
|
|
3225
|
-
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
|
|
3226
|
-
});
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
return findings;
|
|
3230
|
-
}
|
|
3231
3739
|
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3232
3740
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3233
3741
|
function shipsArchivePrefix(shipsPrefix) {
|
|
@@ -3563,6 +4071,12 @@ function scanRetiredVocabulary(files) {
|
|
|
3563
4071
|
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
3564
4072
|
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
3565
4073
|
}
|
|
4074
|
+
function isSelfDependency(dep, task, issue) {
|
|
4075
|
+
if (dep.issue !== null && issue !== null && dep.issue === issue.number)
|
|
4076
|
+
return true;
|
|
4077
|
+
const bare = dep.id.trim().replace(/^#/, "");
|
|
4078
|
+
return bare.toLowerCase() === task.id.trim().toLowerCase();
|
|
4079
|
+
}
|
|
3566
4080
|
function checkDispatchReadiness(input) {
|
|
3567
4081
|
const { trancheSlug, task } = input;
|
|
3568
4082
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
@@ -3577,6 +4091,11 @@ function checkDispatchReadiness(input) {
|
|
|
3577
4091
|
blockers.push(`dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
|
|
3578
4092
|
}
|
|
3579
4093
|
for (const dep of input.dependsOn) {
|
|
4094
|
+
if (isSelfDependency(dep, task, input.issue)) {
|
|
4095
|
+
const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
|
|
4096
|
+
blockers.push(`dispatch-gate INTERNAL: parsed a self-dependency for task ${task.id} (${issueStr}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream. Re-run once the rationale is corrected or the fix ships.`);
|
|
4097
|
+
continue;
|
|
4098
|
+
}
|
|
3580
4099
|
if (dep.resolved === false) {
|
|
3581
4100
|
blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on "${dep.id}", which is UNRESOLVABLE — the resolver could not find a matching tranche/task/Issue for this edge (not a claim about merge status). Not dispatchable until the edge is corrected.`);
|
|
3582
4101
|
continue;
|
|
@@ -3816,27 +4335,27 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
3816
4335
|
};
|
|
3817
4336
|
}
|
|
3818
4337
|
const checkboxLines = section.split(`
|
|
3819
|
-
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]
|
|
4338
|
+
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
|
|
3820
4339
|
if (checkboxLines.length === 0) {
|
|
3821
4340
|
return {
|
|
3822
4341
|
verdict: "pass",
|
|
3823
4342
|
messages: [
|
|
3824
|
-
"Test Plan section found but has no checkbox items.",
|
|
4343
|
+
"Test Plan section found but has no `[principal]` checkbox items.",
|
|
3825
4344
|
"PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
|
|
3826
4345
|
]
|
|
3827
4346
|
};
|
|
3828
4347
|
}
|
|
3829
4348
|
const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
|
|
3830
4349
|
const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
|
|
3831
|
-
const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
4350
|
+
const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
3832
4351
|
if (unchecked.length > 0) {
|
|
3833
|
-
messages.push("", "FAIL — the following Test Plan items are unticked:");
|
|
4352
|
+
messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
|
|
3834
4353
|
for (const line of unchecked)
|
|
3835
4354
|
messages.push(` ${line}`);
|
|
3836
|
-
messages.push("", "Per aeg-root/roles/developer.md (Verification), a PR is not mergeable while any Test Plan box is unticked.", "
|
|
4355
|
+
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.");
|
|
3837
4356
|
return { verdict: "fail", messages };
|
|
3838
4357
|
}
|
|
3839
|
-
messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
4358
|
+
messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
3840
4359
|
return { verdict: "pass", messages };
|
|
3841
4360
|
}
|
|
3842
4361
|
// ../../packages/aeg-core/src/workspace-escape.ts
|
|
@@ -4021,6 +4540,19 @@ var COMMANDS = [
|
|
|
4021
4540
|
],
|
|
4022
4541
|
status: "shipped"
|
|
4023
4542
|
},
|
|
4543
|
+
{
|
|
4544
|
+
name: "brief render",
|
|
4545
|
+
description: "Emit the twelve-section brief skeleton from the forge and the tree, every derivable section filled",
|
|
4546
|
+
flags: [
|
|
4547
|
+
{ flag: "--surfaces <glob1,glob2,...>", description: "Intended surface globs, expanded against tracked files" },
|
|
4548
|
+
{ flag: "--out <path>", description: "Write the rendered brief to a file instead of stdout" }
|
|
4549
|
+
],
|
|
4550
|
+
details: [
|
|
4551
|
+
"Reads the task Issue (`vinaya/tranche:<tranche>`-labeled, id `<n>`) and the tree, and fills every section a program can derive: the header `Project:`/`Tier:`/`Closes #N`, the Step 0 worktree line, the dispatch-gate status as the pre-flight line, §4's file list (with consumer packages and a `sha256` premise pin per file), §7 from the `.vinaya/doc-owners` derivation, and every remaining section from the Issue's eight-field Planner rationale. Refuses — naming the missing fact — when a derived section cannot be derived: no Issue, the dispatch gate not clear, or a `--surfaces` glob matching no tracked file.",
|
|
4552
|
+
"Never writes under `aeg-root/` or to the Issue — a brief is pasted to the Developer, never committed. Reads `aeg-root/templates/brief-template.md` at run time for its one fixed sentence and the standing autonomy clause; every other byte is generated from the forge/tree facts."
|
|
4553
|
+
],
|
|
4554
|
+
status: "shipped"
|
|
4555
|
+
},
|
|
4024
4556
|
{
|
|
4025
4557
|
name: "pr create",
|
|
4026
4558
|
description: "Open a pull request after full brief-schema validation",
|
|
@@ -4060,11 +4592,16 @@ var COMMANDS = [
|
|
|
4060
4592
|
{
|
|
4061
4593
|
flag: "--write",
|
|
4062
4594
|
description: "Path to the PR body file; replaces the content between the AEG:EVIDENCE anchors in place"
|
|
4595
|
+
},
|
|
4596
|
+
{
|
|
4597
|
+
flag: "--push",
|
|
4598
|
+
description: "PR number; splices the same content into that PR's LIVE body (fetched from the forge) and pushes it via `gh pr edit`, self-verifying nothing outside the AEG:EVIDENCE/AEG:TOKENS regions changed. Mutually exclusive with --write."
|
|
4063
4599
|
}
|
|
4064
4600
|
],
|
|
4065
4601
|
details: [
|
|
4066
4602
|
"Two groups: Group A (recomputable) is the head sha and a width-invariant `git diff --numstat` against the merge-base with `BASE_SHA` (else `origin/main`, then `main`) — `check-evidence-fresh` recomputes and byte-compares this exactly. Group B (attested) is the result of `vinaya check --all --diff-only`, this CLI's own portable gate suite — `check-evidence-fresh` can only check it for staleness (the block's recorded head still matches the PR's real head), never re-run it.",
|
|
4067
|
-
"With no `--write`, prints the block to stdout instead of writing a file. Exits non-zero whenever the gate run failed, whether or not `--write` was given — the block records a failing result rather than hiding one, and a non-zero exit stops a scripted `--write && open-pr` from carrying a failing suite onto the forge."
|
|
4603
|
+
"With no `--write`/`--push`, prints the block to stdout instead of writing a file. Exits non-zero whenever the gate run failed, whether or not `--write`/`--push` was given — the block records a failing result rather than hiding one, and a non-zero exit stops a scripted `--write && open-pr` (or `--push`) from carrying a failing suite onto the forge.",
|
|
4604
|
+
"`--push` refuses before writing anything when the live body carries no real `AEG:EVIDENCE` pair (it never appends one, unlike `--write`), when the anchor resolves differently before and after normalisation, or when `<pr>` isn't a bare number. When the live body has no real `AEG:TOKENS` pair, the token splice is skipped (same reasoning, softer outcome) rather than creating one. After pushing, it re-reads the live body and restores the pre-edit body — exiting non-zero — if anything outside the two anchored regions changed."
|
|
4068
4605
|
],
|
|
4069
4606
|
status: "shipped"
|
|
4070
4607
|
},
|
|
@@ -4169,6 +4706,16 @@ var COMMANDS = [
|
|
|
4169
4706
|
],
|
|
4170
4707
|
status: "shipped"
|
|
4171
4708
|
},
|
|
4709
|
+
{
|
|
4710
|
+
name: "review status",
|
|
4711
|
+
description: "Print the review loop's own state for a PR, and its branch's distance from the base",
|
|
4712
|
+
details: [
|
|
4713
|
+
"Two lines at most. The first is `CONTINUE`, `PAUSE: <reason>[ <id>]` for `reappearance`, `zero-deaths` or `max-rounds` — or, for `stale`, the actionable fact itself: `push after verdict — re-review required`. The second reads `behind main by <n> — merge first` when the branch is behind its base, or `behind main: unknown — fetch origin/<base> first` when git cannot measure the distance; it is absent only when the branch is measurably not behind.",
|
|
4714
|
+
"Rounds are derived from the PR's own verdict comments — one round per `Judged head:` value, read through the same extractors the merge gate blocks on, and only from comments an allowlisted principal authored. A Developer round comment is recognised by its `<!-- aeg:developer:round-<n> -->` marker, at that fixed position, never by scanning its prose.",
|
|
4715
|
+
"Exit `0` only when the state is `CONTINUE` and the branch is not behind; `1` otherwise, so a script can gate on the exit code without parsing the text."
|
|
4716
|
+
],
|
|
4717
|
+
status: "shipped"
|
|
4718
|
+
},
|
|
4172
4719
|
{
|
|
4173
4720
|
name: "review post",
|
|
4174
4721
|
description: "Render, post, and self-verify a code-reviewer or security-review verdict comment on a PR",
|
|
@@ -4180,15 +4727,24 @@ var COMMANDS = [
|
|
|
4180
4727
|
},
|
|
4181
4728
|
{
|
|
4182
4729
|
flag: "--verdict",
|
|
4183
|
-
description: "code-reviewer: `APPROVE
|
|
4730
|
+
description: "Optional — derived from the findings file (code-reviewer: `APPROVE`/`REQUEST_CHANGES`; security: `PASS`/`FAIL`). Refused before posting if it disagrees with the derivation."
|
|
4731
|
+
},
|
|
4732
|
+
{
|
|
4733
|
+
flag: "--escalate",
|
|
4734
|
+
description: "`authority` | `strategy` | `product` — posts an `ESCALATE:` comment instead of a verdict. Refused together with `--verdict` or a blocking finding."
|
|
4184
4735
|
},
|
|
4736
|
+
{ flag: "--summary", description: "Required with `--escalate` — the escalation body text" },
|
|
4185
4737
|
{
|
|
4186
4738
|
flag: "--findings-file",
|
|
4187
|
-
description: "One finding per line: `SEVERITY|file:line|description` (`|`-delimited). Omit for zero findings."
|
|
4739
|
+
description: "One finding per line: `SEVERITY|file:line|description` (`|`-delimited). A re-review names a prior id in the description as `F<n> <class> <state>:`. Omit for zero findings."
|
|
4188
4740
|
},
|
|
4189
4741
|
{ flag: "--brief-conformance", description: "code-reviewer only: the BRIEF CONFORMANCE line" },
|
|
4190
4742
|
{ flag: "--spec-conformance", description: "code-reviewer only: the SPEC CONFORMANCE line" },
|
|
4191
4743
|
{ flag: "--scope", description: "code-reviewer only: the SCOPE line" },
|
|
4744
|
+
{
|
|
4745
|
+
flag: "--scope-evidence-file",
|
|
4746
|
+
description: "code-reviewer only: pasted diff-stat output, rendered as a fence directly below the verdict block"
|
|
4747
|
+
},
|
|
4192
4748
|
{ flag: "--tests", description: "code-reviewer only: the TESTS line" },
|
|
4193
4749
|
{ flag: "--docs", description: "code-reviewer only: the DOCS line" },
|
|
4194
4750
|
{ flag: "--config-scan", description: "security only: the CONFIG SCAN line" },
|
|
@@ -4202,12 +4758,18 @@ var COMMANDS = [
|
|
|
4202
4758
|
{ flag: "--tokens-in", description: "a non-negative integer, or `-` if unknown" },
|
|
4203
4759
|
{ flag: "--tokens-out", description: "a non-negative integer, or `-` if unknown" },
|
|
4204
4760
|
{ flag: "--cost", description: "free text, or `-` if unknown" },
|
|
4761
|
+
{
|
|
4762
|
+
flag: "--print-only",
|
|
4763
|
+
description: "Render and self-check the comment, print it, and exit — never calls `gh pr comment`"
|
|
4764
|
+
},
|
|
4205
4765
|
{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }
|
|
4206
4766
|
],
|
|
4207
4767
|
details: [
|
|
4768
|
+
"`--print-only` runs the exact same render-then-self-check path as a real post, then returns before `gh pr comment` — closes atta-labs/vinaya#184, where the old command silently posted a verdict anyway after a reviewer guessed this flag existed.",
|
|
4208
4769
|
"Every structural line (`VERDICT:`, `Judged head:`) is rendered from this command's own validated enum/sha inputs — never from a caller-supplied string — so a Reviewer's free-typed prose can no longer produce a shape the merge gate's line-anchored regex fails to see.",
|
|
4209
|
-
"
|
|
4210
|
-
"
|
|
4770
|
+
"The verdict is derived, not typed: a BLOCKER (or CRITICAL/HIGH) finding forces REQUEST_CHANGES/FAIL and its absence forces APPROVE/PASS, before posting anything — an explicit `--verdict` that disagrees is refused naming the derived value. When a same-role verdict comment already exists on the PR, a new findings file must carry every prior id with a state and no non-blocking finding outside the diff since that comment's judged head, or the post is refused.",
|
|
4771
|
+
"Before the post ever reaches the forge, runs the exact `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` functions `checkReviewGate` calls over its own rendered text and refuses (exit 2) unless exactly the intended verdict extracts and the other role extracts none — an escalation requires both to extract none. Both extractors read only a comment's first three lines, which are always this command's own structural lines, so no caller-supplied field can smuggle a line the gate would misread.",
|
|
4772
|
+
"After posting, re-fetches the PR's comments and runs them through the same extractors `checkReviewGate` calls — the same functions, not a second implementation — and exits non-zero naming precisely what failed to re-parse if the post does not come back clean, bound to the resolved head, and free of the other role's verdict. There is no `--skip-verify` escape."
|
|
4211
4773
|
],
|
|
4212
4774
|
status: "shipped"
|
|
4213
4775
|
},
|
|
@@ -4260,6 +4822,9 @@ var COMMANDS = [
|
|
|
4260
4822
|
{ flag: "--dry-run", description: "Print the full diff without regenerating anything" },
|
|
4261
4823
|
{ flag: "--yes", description: "Skip the confirmation prompt" }
|
|
4262
4824
|
],
|
|
4825
|
+
details: [
|
|
4826
|
+
"When `claude` is a selected agent vendor, also retrofits the Claude Code `Stop` hook (`.claude/hooks/track-transcript.sh`, `.claude/settings.json`) the same way `init` installs it on a fresh repo — for a repo that ran `init` before this hook existed, closing the gap that left token rows reading `—/—/—` with no operator ever told to re-run `init`. Never overwrites a foreign `.claude/settings.json` (refuse-if-foreign, same as `init`); appends to a foreign Stop-hook script rather than replacing it (managed-block, same discipline as the git hooks)."
|
|
4827
|
+
],
|
|
4263
4828
|
status: "shipped"
|
|
4264
4829
|
},
|
|
4265
4830
|
{
|
|
@@ -4378,7 +4943,7 @@ function emitCheckError(error) {
|
|
|
4378
4943
|
|
|
4379
4944
|
// src/lib/config.ts
|
|
4380
4945
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4381
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
4946
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
4382
4947
|
import { homedir } from "node:os";
|
|
4383
4948
|
import { dirname, join, resolve } from "node:path";
|
|
4384
4949
|
import { z as z2 } from "zod";
|
|
@@ -4671,7 +5236,7 @@ function loadConfig() {
|
|
|
4671
5236
|
if (!path)
|
|
4672
5237
|
return null;
|
|
4673
5238
|
try {
|
|
4674
|
-
const raw = JSON.parse(
|
|
5239
|
+
const raw = JSON.parse(readFileSync2(path, "utf-8"));
|
|
4675
5240
|
return stripGlobalOnlyKeys(VinayaConfigSchema.parse(raw), path);
|
|
4676
5241
|
} catch {
|
|
4677
5242
|
return null;
|
|
@@ -4696,7 +5261,7 @@ function readRegisteredProjectNames() {
|
|
|
4696
5261
|
const path = join2(root, ".vinaya/projects.md");
|
|
4697
5262
|
if (!existsSync2(path))
|
|
4698
5263
|
return [];
|
|
4699
|
-
return parseRegistry(
|
|
5264
|
+
return parseRegistry(readFileSync3(path, "utf8")).map((p) => p.name);
|
|
4700
5265
|
}
|
|
4701
5266
|
function resolveRepo2() {
|
|
4702
5267
|
const fromEnv = process.env.AEG_REPO;
|
|
@@ -4745,7 +5310,10 @@ function taskToEntry(entries, slug) {
|
|
|
4745
5310
|
m.set(`${slug}/${e.task.id}`, e);
|
|
4746
5311
|
return m;
|
|
4747
5312
|
}
|
|
4748
|
-
function recoveryPromptFor(checkCode) {
|
|
5313
|
+
function recoveryPromptFor(checkCode, detail = "") {
|
|
5314
|
+
if (detail.includes("INTERNAL:")) {
|
|
5315
|
+
return "This is an INTERNAL parser-bug report, not a real coherence failure — a task cannot depend on itself, so there is nothing here to close or resolve. Do NOT work around it. Report it upstream, and re-run once the rationale text is corrected or the parser fix ships.";
|
|
5316
|
+
}
|
|
4749
5317
|
switch (checkCode) {
|
|
4750
5318
|
case "A1":
|
|
4751
5319
|
return "The task's Issue is closed but its closing PR is not merged. Verify the PR actually merged (or reopen the Issue if it was closed in error), then re-run `vinaya check coherence`.";
|
|
@@ -4772,7 +5340,7 @@ function emitFailure(result) {
|
|
|
4772
5340
|
check: CHECK_NAME,
|
|
4773
5341
|
severity: "error",
|
|
4774
5342
|
message: `${result.check}: ${detail}`,
|
|
4775
|
-
agent_recovery_prompt: recoveryPromptFor(result.check)
|
|
5343
|
+
agent_recovery_prompt: recoveryPromptFor(result.check, detail)
|
|
4776
5344
|
});
|
|
4777
5345
|
}
|
|
4778
5346
|
async function main() {
|
|
@@ -4849,4 +5417,9 @@ async function main() {
|
|
|
4849
5417
|
}
|
|
4850
5418
|
process.exit(0);
|
|
4851
5419
|
}
|
|
4852
|
-
main
|
|
5420
|
+
if (__require.main == __require.module) {
|
|
5421
|
+
main();
|
|
5422
|
+
}
|
|
5423
|
+
export {
|
|
5424
|
+
recoveryPromptFor
|
|
5425
|
+
};
|