@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,9 +1,11 @@
|
|
|
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-changeset-coverage.ts
|
|
4
|
-
import { execFileSync as
|
|
5
|
-
import { readdirSync, readFileSync } from "node:fs";
|
|
6
|
-
import { join as
|
|
6
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
7
|
+
import { readdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
8
|
+
import { join as join3, relative as relative2 } from "node:path";
|
|
7
9
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
8
10
|
var GATE_AUDIENCE = {
|
|
9
11
|
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
@@ -212,17 +214,18 @@ function splitIds(raw) {
|
|
|
212
214
|
return [];
|
|
213
215
|
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
|
|
214
216
|
}
|
|
215
|
-
function resolveIds(raw
|
|
217
|
+
function resolveIds(raw) {
|
|
216
218
|
const ids = splitIds(raw);
|
|
217
219
|
const resolved = [];
|
|
220
|
+
let lastSlug = null;
|
|
218
221
|
for (const id of ids) {
|
|
219
222
|
const qualified = id.match(SLUG_QUALIFIED_ID);
|
|
220
223
|
if (qualified) {
|
|
221
|
-
lastSlug
|
|
224
|
+
lastSlug = qualified[1] ?? null;
|
|
222
225
|
resolved.push(id);
|
|
223
|
-
} else if (lastSlug
|
|
224
|
-
resolved.push(`${lastSlug
|
|
225
|
-
lastSlug
|
|
226
|
+
} else if (lastSlug) {
|
|
227
|
+
resolved.push(`${lastSlug} ${id}`);
|
|
228
|
+
lastSlug = null;
|
|
226
229
|
} else {
|
|
227
230
|
resolved.push(id);
|
|
228
231
|
}
|
|
@@ -242,12 +245,7 @@ function parseRationaleDeps(body) {
|
|
|
242
245
|
const section = extractSection(body);
|
|
243
246
|
const dependsOn = [];
|
|
244
247
|
const conflictsWith = [];
|
|
245
|
-
let current = null;
|
|
246
248
|
const assignedFields = new Set;
|
|
247
|
-
const lastSlugByField = {
|
|
248
|
-
dependsOn: { current: null },
|
|
249
|
-
conflictsWith: { current: null }
|
|
250
|
-
};
|
|
251
249
|
const spanPattern = /`([^`]*)`/g;
|
|
252
250
|
let match = spanPattern.exec(section);
|
|
253
251
|
while (match !== null) {
|
|
@@ -257,13 +255,9 @@ function parseRationaleDeps(body) {
|
|
|
257
255
|
const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
|
|
258
256
|
if (!assignedFields.has(field)) {
|
|
259
257
|
assignedFields.add(field);
|
|
260
|
-
|
|
261
|
-
const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
|
|
258
|
+
const ids = resolveIds(labelMatch[2] ?? "");
|
|
262
259
|
pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
263
260
|
}
|
|
264
|
-
} else if (current) {
|
|
265
|
-
const ids = resolveIds(content, lastSlugByField[current]);
|
|
266
|
-
pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
267
261
|
}
|
|
268
262
|
match = spanPattern.exec(section);
|
|
269
263
|
}
|
|
@@ -1728,6 +1722,48 @@ function splitTableRow(row) {
|
|
|
1728
1722
|
parts.pop();
|
|
1729
1723
|
return parts.map((p) => p.split(PLACEHOLDER).join("|"));
|
|
1730
1724
|
}
|
|
1725
|
+
// ../../packages/aeg-core/src/blast-radius-domains.ts
|
|
1726
|
+
function resolveWorkspaceEntry(entry2, listDirs) {
|
|
1727
|
+
if (entry2.endsWith("/*")) {
|
|
1728
|
+
const baseDir = entry2.slice(0, -2);
|
|
1729
|
+
return listDirs(baseDir).map((name) => `${baseDir}/${name}`);
|
|
1730
|
+
}
|
|
1731
|
+
if (entry2.includes("*"))
|
|
1732
|
+
return [];
|
|
1733
|
+
return [entry2];
|
|
1734
|
+
}
|
|
1735
|
+
// ../../packages/aeg-core/src/consumer-enumeration.ts
|
|
1736
|
+
function deriveWorkspaceMemberDirs(workspaces, listDirs) {
|
|
1737
|
+
const positive = workspaces.filter((entry2) => !entry2.startsWith("!"));
|
|
1738
|
+
const negative = workspaces.filter((entry2) => entry2.startsWith("!")).map((entry2) => entry2.slice(1));
|
|
1739
|
+
const excluded = new Set;
|
|
1740
|
+
for (const entry2 of negative) {
|
|
1741
|
+
for (const member of resolveWorkspaceEntry(entry2, listDirs))
|
|
1742
|
+
excluded.add(member);
|
|
1743
|
+
}
|
|
1744
|
+
const members = new Set;
|
|
1745
|
+
for (const entry2 of positive) {
|
|
1746
|
+
for (const member of resolveWorkspaceEntry(entry2, listDirs)) {
|
|
1747
|
+
if (!excluded.has(member))
|
|
1748
|
+
members.add(member);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
return [...members].sort();
|
|
1752
|
+
}
|
|
1753
|
+
function buildConsumersOf(workspaces, listDirs, readManifest) {
|
|
1754
|
+
const members = deriveWorkspaceMemberDirs(workspaces, listDirs);
|
|
1755
|
+
return (pkg) => {
|
|
1756
|
+
const target = `@attalabs/${pkg}`;
|
|
1757
|
+
return members.filter((dir) => {
|
|
1758
|
+
if (dir === `packages/${pkg}`)
|
|
1759
|
+
return false;
|
|
1760
|
+
const manifest = readManifest(dir);
|
|
1761
|
+
if (!manifest)
|
|
1762
|
+
return false;
|
|
1763
|
+
return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {});
|
|
1764
|
+
});
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1731
1767
|
// ../../packages/aeg-core/src/state-machine-model.ts
|
|
1732
1768
|
var FORGE_FACT_INPUTS = [
|
|
1733
1769
|
{
|
|
@@ -1793,6 +1829,156 @@ var DERIVED_STATUSES = [
|
|
|
1793
1829
|
"incoherent"
|
|
1794
1830
|
];
|
|
1795
1831
|
var DERIVABLE_STATUSES = DERIVED_STATUSES.filter((s) => s !== "backlog");
|
|
1832
|
+
// ../../packages/aeg-core/src/parse-ledger.ts
|
|
1833
|
+
function rowFromCells(cells) {
|
|
1834
|
+
if (cells.length < 7)
|
|
1835
|
+
return null;
|
|
1836
|
+
const phase = (cells[0] ?? "").trim();
|
|
1837
|
+
if (!phase)
|
|
1838
|
+
return null;
|
|
1839
|
+
return {
|
|
1840
|
+
phase,
|
|
1841
|
+
role: (cells[1] ?? "").trim(),
|
|
1842
|
+
agentModel: (cells[2] ?? "").trim(),
|
|
1843
|
+
tokensIn: parseIntCell(cells[3] ?? ""),
|
|
1844
|
+
tokensOut: parseIntCell(cells[4] ?? ""),
|
|
1845
|
+
cost: parseCostCell(cells[5] ?? ""),
|
|
1846
|
+
date: (cells[6] ?? "").trim()
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
function parseIntCell(cell) {
|
|
1850
|
+
const trimmed = cell.replace(/`/g, "").trim();
|
|
1851
|
+
if (!trimmed || isEmDashOrDash(trimmed))
|
|
1852
|
+
return null;
|
|
1853
|
+
const normalized = trimmed.replace(/[,_\s]/g, "");
|
|
1854
|
+
if (!/^-?\d+$/.test(normalized))
|
|
1855
|
+
return null;
|
|
1856
|
+
return Number(normalized);
|
|
1857
|
+
}
|
|
1858
|
+
function parseCostCell(cell) {
|
|
1859
|
+
const trimmed = cell.replace(/`/g, "").trim();
|
|
1860
|
+
if (!trimmed || isEmDashOrDash(trimmed))
|
|
1861
|
+
return null;
|
|
1862
|
+
const normalized = trimmed.replace(/^\$/, "").replace(/[,_\s]/g, "");
|
|
1863
|
+
if (!/^-?\d+(\.\d+)?$/.test(normalized))
|
|
1864
|
+
return null;
|
|
1865
|
+
return Number(normalized);
|
|
1866
|
+
}
|
|
1867
|
+
function isEmDashOrDash(s) {
|
|
1868
|
+
const t = s.trim();
|
|
1869
|
+
return t === "—" || t === "-" || t === "–";
|
|
1870
|
+
}
|
|
1871
|
+
// ../../packages/aeg-core/src/waiver-label.ts
|
|
1872
|
+
var WAIVER_LABEL = label("waiver-docs");
|
|
1873
|
+
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
1874
|
+
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
1875
|
+
function isPrincipal(login, principalAllowlist) {
|
|
1876
|
+
if (login === null)
|
|
1877
|
+
return false;
|
|
1878
|
+
const normalized = login.toLowerCase();
|
|
1879
|
+
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
1880
|
+
}
|
|
1881
|
+
function isWaiverLabelActorVerified(opts) {
|
|
1882
|
+
if (!opts.labels.includes(opts.label))
|
|
1883
|
+
return false;
|
|
1884
|
+
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// ../../packages/aeg-core/src/parse-token-report.ts
|
|
1888
|
+
var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
|
|
1889
|
+
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;
|
|
1890
|
+
function parseTokenReportEntries(body) {
|
|
1891
|
+
const lines = body.split(/\r?\n/);
|
|
1892
|
+
const headingIdxs = [];
|
|
1893
|
+
lines.forEach((line, i) => {
|
|
1894
|
+
if (TOKEN_REPORT_HEADING.test(line))
|
|
1895
|
+
headingIdxs.push(i);
|
|
1896
|
+
});
|
|
1897
|
+
const out = [];
|
|
1898
|
+
for (let h = 0;h < headingIdxs.length; h++) {
|
|
1899
|
+
const start = headingIdxs[h] + 1;
|
|
1900
|
+
const end = h + 1 < headingIdxs.length ? headingIdxs[h + 1] : lines.length;
|
|
1901
|
+
const section = lines.slice(start, end).join(`
|
|
1902
|
+
`);
|
|
1903
|
+
const tableRows = parseTableSection(section);
|
|
1904
|
+
if (tableRows.length > 0) {
|
|
1905
|
+
out.push(...tableRows);
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
const inlineRow = parseInlineFieldList(section);
|
|
1909
|
+
if (inlineRow)
|
|
1910
|
+
out.push(inlineRow);
|
|
1911
|
+
}
|
|
1912
|
+
return out;
|
|
1913
|
+
}
|
|
1914
|
+
function parseTableSection(section) {
|
|
1915
|
+
const lines = section.split(/\r?\n/);
|
|
1916
|
+
let i = 0;
|
|
1917
|
+
for (;i < lines.length; i++) {
|
|
1918
|
+
if ((lines[i] ?? "").trim().startsWith("|"))
|
|
1919
|
+
break;
|
|
1920
|
+
}
|
|
1921
|
+
if (i + 1 >= lines.length)
|
|
1922
|
+
return [];
|
|
1923
|
+
i += 2;
|
|
1924
|
+
const out = [];
|
|
1925
|
+
for (;i < lines.length; i++) {
|
|
1926
|
+
const trimmed = (lines[i] ?? "").trim();
|
|
1927
|
+
if (!trimmed.startsWith("|"))
|
|
1928
|
+
break;
|
|
1929
|
+
const row = rowFromCells(splitTableRow(trimmed));
|
|
1930
|
+
if (row)
|
|
1931
|
+
out.push(row);
|
|
1932
|
+
}
|
|
1933
|
+
return out;
|
|
1934
|
+
}
|
|
1935
|
+
function parseInlineFieldList(section) {
|
|
1936
|
+
const m = section.match(INLINE_FIELD_LIST);
|
|
1937
|
+
if (!m)
|
|
1938
|
+
return null;
|
|
1939
|
+
return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
|
|
1940
|
+
}
|
|
1941
|
+
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1942
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1943
|
+
function firstThreeLines(comment) {
|
|
1944
|
+
return comment.split(`
|
|
1945
|
+
`).slice(0, 3).join(`
|
|
1946
|
+
`);
|
|
1947
|
+
}
|
|
1948
|
+
function extractHeadSha(comment) {
|
|
1949
|
+
const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
|
|
1950
|
+
return m ? m[1].toLowerCase() : null;
|
|
1951
|
+
}
|
|
1952
|
+
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1953
|
+
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1954
|
+
if (candidates.length === 0) {
|
|
1955
|
+
return {
|
|
1956
|
+
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1957
|
+
headSha: null,
|
|
1958
|
+
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
const latest = candidates[candidates.length - 1];
|
|
1962
|
+
const m = firstThreeLines(latest).match(valuePattern);
|
|
1963
|
+
if (!m) {
|
|
1964
|
+
return {
|
|
1965
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
|
|
1966
|
+
headSha: null,
|
|
1967
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
return {
|
|
1971
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1972
|
+
headSha: extractHeadSha(latest),
|
|
1973
|
+
danglingNote: null
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
function extractCodeReviewVerdict(comments) {
|
|
1977
|
+
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
1978
|
+
}
|
|
1979
|
+
function extractSecurityReviewVerdict(comments) {
|
|
1980
|
+
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
1981
|
+
}
|
|
1796
1982
|
// ../../packages/aeg-core/src/report-tokens.ts
|
|
1797
1983
|
var DASH_LOOKALIKES = {
|
|
1798
1984
|
"-": "‑",
|
|
@@ -1801,6 +1987,7 @@ var DASH_LOOKALIKES = {
|
|
|
1801
1987
|
};
|
|
1802
1988
|
var DASH_CHARS = new Set(Object.keys(DASH_LOOKALIKES));
|
|
1803
1989
|
// ../../packages/aeg-core/src/claude-code-transcript.ts
|
|
1990
|
+
import { createHash } from "node:crypto";
|
|
1804
1991
|
function summarizeTranscript(jsonl) {
|
|
1805
1992
|
const seen = new Set;
|
|
1806
1993
|
const components = {
|
|
@@ -1849,7 +2036,14 @@ function numberOr(value, fallback) {
|
|
|
1849
2036
|
function sanitizeKey(value) {
|
|
1850
2037
|
return value.replace(/[^A-Za-z0-9]+/g, "-");
|
|
1851
2038
|
}
|
|
2039
|
+
function collisionResistantKey(value) {
|
|
2040
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
2041
|
+
return `${sanitizeKey(value)}-${digest}`;
|
|
2042
|
+
}
|
|
1852
2043
|
function transcriptPointerPath(projectDir, tmpDir) {
|
|
2044
|
+
return `${tmpDir}/claude-transcript-${collisionResistantKey(projectDir)}.txt`;
|
|
2045
|
+
}
|
|
2046
|
+
function legacyTranscriptPointerPath(projectDir, tmpDir) {
|
|
1853
2047
|
return `${tmpDir}/claude-transcript-${sanitizeKey(projectDir)}.txt`;
|
|
1854
2048
|
}
|
|
1855
2049
|
function resolvePointer(explicitTranscriptPath, deps) {
|
|
@@ -1857,13 +2051,15 @@ function resolvePointer(explicitTranscriptPath, deps) {
|
|
|
1857
2051
|
return { path: explicitTranscriptPath, corroborated: true };
|
|
1858
2052
|
const projectDir = deps.env.CLAUDE_PROJECT_DIR ?? deps.cwd;
|
|
1859
2053
|
const tmpDir = deps.env.TMPDIR ?? "/tmp";
|
|
1860
|
-
const
|
|
2054
|
+
const primaryPointerPath = transcriptPointerPath(projectDir, tmpDir);
|
|
2055
|
+
const legacyPointerPath = legacyTranscriptPointerPath(projectDir, tmpDir);
|
|
1861
2056
|
const currentSessionId = deps.env.CLAUDE_CODE_SESSION_ID;
|
|
1862
|
-
|
|
2057
|
+
const pointerPath = deps.exists(primaryPointerPath) ? primaryPointerPath : deps.exists(legacyPointerPath) ? legacyPointerPath : undefined;
|
|
2058
|
+
if (!pointerPath) {
|
|
1863
2059
|
return {
|
|
1864
2060
|
pointerExisted: false,
|
|
1865
2061
|
corroborated: false,
|
|
1866
|
-
error: `No transcript pointer at ${
|
|
2062
|
+
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."
|
|
1867
2063
|
};
|
|
1868
2064
|
}
|
|
1869
2065
|
let contents;
|
|
@@ -1938,28 +2134,15 @@ function isTokenCollectionWiringBroken(capability) {
|
|
|
1938
2134
|
return false;
|
|
1939
2135
|
return capability.reason !== "no-transcript-resolved";
|
|
1940
2136
|
}
|
|
2137
|
+
// ../../packages/aeg-core/src/metering-io-guard.ts
|
|
2138
|
+
import { closeSync, constants as fsConstants, fstatSync, openSync, readFileSync } from "node:fs";
|
|
2139
|
+
var GUARD_OPEN_FLAGS = fsConstants.O_RDONLY | fsConstants.O_NONBLOCK | fsConstants.O_NOFOLLOW;
|
|
1941
2140
|
// ../../packages/aeg-core/src/file-classify.ts
|
|
1942
2141
|
function isCodeFile(p) {
|
|
1943
2142
|
if (p.endsWith(".md"))
|
|
1944
2143
|
return false;
|
|
1945
2144
|
return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
|
|
1946
2145
|
}
|
|
1947
|
-
// ../../packages/aeg-core/src/waiver-label.ts
|
|
1948
|
-
var WAIVER_LABEL = label("waiver-docs");
|
|
1949
|
-
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
1950
|
-
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
1951
|
-
function isPrincipal(login, principalAllowlist) {
|
|
1952
|
-
if (login === null)
|
|
1953
|
-
return false;
|
|
1954
|
-
const normalized = login.toLowerCase();
|
|
1955
|
-
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
1956
|
-
}
|
|
1957
|
-
function isWaiverLabelActorVerified(opts) {
|
|
1958
|
-
if (!opts.labels.includes(opts.label))
|
|
1959
|
-
return false;
|
|
1960
|
-
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
2146
|
// ../../packages/aeg-core/src/doc-owners.ts
|
|
1964
2147
|
var DOC_OWNERS_PATH = ".vinaya/doc-owners";
|
|
1965
2148
|
function parseDocOwners(content) {
|
|
@@ -2144,7 +2327,7 @@ function overrideActive(opts) {
|
|
|
2144
2327
|
return false;
|
|
2145
2328
|
}
|
|
2146
2329
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2147
|
-
import { createHash } from "node:crypto";
|
|
2330
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2148
2331
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2149
2332
|
function isPremiseHeader(line) {
|
|
2150
2333
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2176,6 +2359,32 @@ function parsePremiseBlock(prBody) {
|
|
|
2176
2359
|
}
|
|
2177
2360
|
return assertions;
|
|
2178
2361
|
}
|
|
2362
|
+
function premiseBlockText(prBody) {
|
|
2363
|
+
const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
|
|
2364
|
+
const lines = searchIn.split(/\r?\n/);
|
|
2365
|
+
let startIdx = -1;
|
|
2366
|
+
let endIdxExclusive = lines.length;
|
|
2367
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2368
|
+
const line = lines[i].trim();
|
|
2369
|
+
if (startIdx === -1) {
|
|
2370
|
+
if (isPremiseHeader(line))
|
|
2371
|
+
startIdx = i;
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
if (line === "") {
|
|
2375
|
+
endIdxExclusive = i;
|
|
2376
|
+
break;
|
|
2377
|
+
}
|
|
2378
|
+
if (!PREMISE_LINE.test(line)) {
|
|
2379
|
+
endIdxExclusive = i;
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
if (startIdx === -1)
|
|
2384
|
+
return null;
|
|
2385
|
+
return lines.slice(startIdx, endIdxExclusive).join(`
|
|
2386
|
+
`);
|
|
2387
|
+
}
|
|
2179
2388
|
function checkPremises(assertions, fileReader) {
|
|
2180
2389
|
const failures = [];
|
|
2181
2390
|
for (const a of assertions) {
|
|
@@ -2189,7 +2398,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2189
2398
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2190
2399
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2191
2400
|
} else if (a.kind === "sha256") {
|
|
2192
|
-
const actual =
|
|
2401
|
+
const actual = createHash2("sha256").update(content).digest("hex");
|
|
2193
2402
|
if (actual !== a.value.toLowerCase()) {
|
|
2194
2403
|
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.`);
|
|
2195
2404
|
}
|
|
@@ -2262,15 +2471,18 @@ function testPlanRegion(prBody) {
|
|
|
2262
2471
|
return located.found ? located.section : prBody;
|
|
2263
2472
|
}
|
|
2264
2473
|
function checkTestPlan(prBody) {
|
|
2265
|
-
const
|
|
2474
|
+
const located = locateTestPlanSection(prBody);
|
|
2475
|
+
const region = located.found ? located.section : prBody;
|
|
2266
2476
|
if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
|
|
2267
2477
|
return { status: "pass", errors: [] };
|
|
2268
2478
|
if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
|
|
2269
2479
|
return { status: "pass", errors: [] };
|
|
2480
|
+
if (located.found && extractFencedBlocks(region).length > 0)
|
|
2481
|
+
return { status: "pass", errors: [] };
|
|
2270
2482
|
return {
|
|
2271
2483
|
status: "fail",
|
|
2272
2484
|
errors: [
|
|
2273
|
-
"brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
|
|
2485
|
+
"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."
|
|
2274
2486
|
]
|
|
2275
2487
|
};
|
|
2276
2488
|
}
|
|
@@ -2393,8 +2605,162 @@ function isBriefShaped(prBody) {
|
|
|
2393
2605
|
const stripped = stripCode(prBody);
|
|
2394
2606
|
return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
|
|
2395
2607
|
}
|
|
2608
|
+
var COMMAND_WORDS = ["export", "bun", "gh", "git", "grep", "sed", "cat", "diff", "vinaya"];
|
|
2609
|
+
var BRIEF_RULES_SINCE_PR = 394;
|
|
2610
|
+
var AGENT_BOXES_REFUSED_SINCE_PR = 396;
|
|
2611
|
+
var AGENT_BOX_LINE_RE = /^-\s*\[[ xX]\]\s*\*{2}\[agent\]\*{2}/im;
|
|
2612
|
+
function checkNoAgentBoxes(prBody) {
|
|
2613
|
+
const region = testPlanRegion(prBody);
|
|
2614
|
+
if (!AGENT_BOX_LINE_RE.test(region))
|
|
2615
|
+
return { status: "pass", errors: [] };
|
|
2616
|
+
return {
|
|
2617
|
+
status: "fail",
|
|
2618
|
+
errors: [
|
|
2619
|
+
"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."
|
|
2620
|
+
]
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
function rolloutThresholdFor(message) {
|
|
2624
|
+
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:")) {
|
|
2625
|
+
return BRIEF_RULES_SINCE_PR;
|
|
2626
|
+
}
|
|
2627
|
+
if (message.startsWith("brief-validation no agent boxes:"))
|
|
2628
|
+
return AGENT_BOXES_REFUSED_SINCE_PR;
|
|
2629
|
+
return null;
|
|
2630
|
+
}
|
|
2631
|
+
function partitionBriefErrorsByRollout(errors, prNumber) {
|
|
2632
|
+
const blocking = [];
|
|
2633
|
+
const info = [];
|
|
2634
|
+
for (const e of errors) {
|
|
2635
|
+
const threshold = rolloutThresholdFor(e);
|
|
2636
|
+
const grandfathered = threshold !== null && prNumber !== null && prNumber < threshold;
|
|
2637
|
+
(grandfathered ? info : blocking).push(e);
|
|
2638
|
+
}
|
|
2639
|
+
return { blocking, info };
|
|
2640
|
+
}
|
|
2641
|
+
function extractFencedBlocks(text) {
|
|
2642
|
+
const re = /^[ \t]*(`{3,}|~{3,})([^\n]*)\n([\s\S]*?)^[ \t]*\1[ \t]*$/gm;
|
|
2643
|
+
const blocks = [];
|
|
2644
|
+
let m = re.exec(text);
|
|
2645
|
+
while (m !== null) {
|
|
2646
|
+
const lang = m[2].trim().toLowerCase().split(/\s+/)[0] ?? "";
|
|
2647
|
+
blocks.push({ start: m.index, end: m.index + m[0].length, lang, content: m[3] });
|
|
2648
|
+
m = re.exec(text);
|
|
2649
|
+
}
|
|
2650
|
+
return blocks;
|
|
2651
|
+
}
|
|
2652
|
+
function firstNonBlankLine(text) {
|
|
2653
|
+
const line = text.split(`
|
|
2654
|
+
`).find((l) => l.trim().length > 0);
|
|
2655
|
+
return line?.trim() ?? "";
|
|
2656
|
+
}
|
|
2657
|
+
var FILE_LINE_RE = /\b[\w./-]+\.(?:tsx?|jsx?|mjs|cjs|md|mdx|json|ya?ml|sh|py|toml):\d+\b/g;
|
|
2658
|
+
function checkNoUnpinnedCodeClaims(prBody) {
|
|
2659
|
+
const premiseText = premiseBlockText(prBody);
|
|
2660
|
+
const withoutPremise = premiseText ? prBody.replace(premiseText, "") : prBody;
|
|
2661
|
+
const scanned = stripCode(withoutPremise, { inlineSpans: "keep" });
|
|
2662
|
+
const matches = scanned.match(FILE_LINE_RE) ?? [];
|
|
2663
|
+
if (matches.length === 0)
|
|
2664
|
+
return { status: "pass", errors: [] };
|
|
2665
|
+
return {
|
|
2666
|
+
status: "fail",
|
|
2667
|
+
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.`)
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
function extractNumberedSection(prBody, num) {
|
|
2671
|
+
const startRe = new RegExp(`^#{1,4}\\s*(?:\\*\\*)?${num}[a-z]?\\.\\s`, "im");
|
|
2672
|
+
const start = startRe.exec(prBody);
|
|
2673
|
+
if (!start)
|
|
2674
|
+
return null;
|
|
2675
|
+
const afterStart = start.index + start[0].length;
|
|
2676
|
+
const rest = prBody.slice(afterStart);
|
|
2677
|
+
const next = /^#{1,4}\s/m.exec(rest);
|
|
2678
|
+
return next ? rest.slice(0, next.index) : rest;
|
|
2679
|
+
}
|
|
2680
|
+
function commandWordOf(line) {
|
|
2681
|
+
return line.trim().split(/\s+/)[0] ?? "";
|
|
2682
|
+
}
|
|
2683
|
+
function isCommandBlock(content) {
|
|
2684
|
+
return COMMAND_WORDS.includes(commandWordOf(firstNonBlankLine(content)));
|
|
2685
|
+
}
|
|
2686
|
+
function isStep0Block(content) {
|
|
2687
|
+
return firstNonBlankLine(content).startsWith("git worktree add");
|
|
2688
|
+
}
|
|
2689
|
+
function checkCommandsCarryOutput(prBody) {
|
|
2690
|
+
const errors = [];
|
|
2691
|
+
for (const [label2, num] of [
|
|
2692
|
+
["5", 5],
|
|
2693
|
+
["6", 6]
|
|
2694
|
+
]) {
|
|
2695
|
+
const section = extractNumberedSection(prBody, num);
|
|
2696
|
+
if (!section)
|
|
2697
|
+
continue;
|
|
2698
|
+
const blocks = extractFencedBlocks(section);
|
|
2699
|
+
for (let i = 0;i < blocks.length; i++) {
|
|
2700
|
+
const block = blocks[i];
|
|
2701
|
+
if (isStep0Block(block.content) || !isCommandBlock(block.content))
|
|
2702
|
+
continue;
|
|
2703
|
+
const next = blocks[i + 1];
|
|
2704
|
+
if (!next || isCommandBlock(next.content)) {
|
|
2705
|
+
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.`);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2710
|
+
}
|
|
2711
|
+
var CONSUMER_TESTS_SENTINEL_RE = /consumer-tests\s*:\s*none\s*[-—–]\s*\S/i;
|
|
2712
|
+
function packagesNamedIn(text) {
|
|
2713
|
+
const re = /\bpackages\/([A-Za-z0-9_-]+)\//g;
|
|
2714
|
+
const pkgs = new Set;
|
|
2715
|
+
let m = re.exec(text);
|
|
2716
|
+
while (m !== null) {
|
|
2717
|
+
pkgs.add(m[1]);
|
|
2718
|
+
m = re.exec(text);
|
|
2719
|
+
}
|
|
2720
|
+
return [...pkgs];
|
|
2721
|
+
}
|
|
2722
|
+
function hasTestPathForConsumer(text, consumerDir) {
|
|
2723
|
+
const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2724
|
+
const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
|
|
2725
|
+
return re.test(text);
|
|
2726
|
+
}
|
|
2727
|
+
function checkConsumerTests(prBody, consumersOf) {
|
|
2728
|
+
const section4 = extractNumberedSection(prBody, 4);
|
|
2729
|
+
if (!section4)
|
|
2730
|
+
return { status: "pass", errors: [] };
|
|
2731
|
+
if (CONSUMER_TESTS_SENTINEL_RE.test(section4))
|
|
2732
|
+
return { status: "pass", errors: [] };
|
|
2733
|
+
const errors = [];
|
|
2734
|
+
for (const pkg of packagesNamedIn(section4)) {
|
|
2735
|
+
for (const consumerDir of consumersOf(pkg)) {
|
|
2736
|
+
if (hasTestPathForConsumer(section4, consumerDir))
|
|
2737
|
+
continue;
|
|
2738
|
+
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>\`.`);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2742
|
+
}
|
|
2743
|
+
var CHECK_NAME_RE = /\bcheck-[a-z0-9-]+(?:\.ts)?\b/;
|
|
2744
|
+
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;
|
|
2745
|
+
var DEFEAT_CASES_RE = /defeat cases\s*:/i;
|
|
2746
|
+
function checkDefeatCases(prBody) {
|
|
2747
|
+
const section4 = extractNumberedSection(prBody, 4);
|
|
2748
|
+
if (!section4)
|
|
2749
|
+
return { status: "pass", errors: [] };
|
|
2750
|
+
if (!CHECK_NAME_RE.test(section4) && !FORGE_WRITE_COMMAND_RE.test(section4))
|
|
2751
|
+
return { status: "pass", errors: [] };
|
|
2752
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2753
|
+
if (DEFEAT_CASES_RE.test(section6))
|
|
2754
|
+
return { status: "pass", errors: [] };
|
|
2755
|
+
return {
|
|
2756
|
+
status: "fail",
|
|
2757
|
+
errors: [
|
|
2758
|
+
"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."
|
|
2759
|
+
]
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2396
2762
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2397
|
-
const { requireClosesN = true } = options;
|
|
2763
|
+
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2398
2764
|
const results = [
|
|
2399
2765
|
checkTierField(prBody, readTier),
|
|
2400
2766
|
checkTestPlan(prBody),
|
|
@@ -2407,10 +2773,219 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2407
2773
|
checkAutonomyClause(prBody),
|
|
2408
2774
|
checkProjectField(prBody),
|
|
2409
2775
|
checkForField(prBody),
|
|
2776
|
+
checkNoUnpinnedCodeClaims(prBody),
|
|
2777
|
+
checkNoAgentBoxes(prBody),
|
|
2778
|
+
checkCommandsCarryOutput(prBody),
|
|
2779
|
+
checkConsumerTests(prBody, consumersOf),
|
|
2780
|
+
checkDefeatCases(prBody),
|
|
2410
2781
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2411
2782
|
];
|
|
2412
2783
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2413
2784
|
}
|
|
2785
|
+
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2786
|
+
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
2787
|
+
var STATIC_PORTABLE_PREFIXES = [
|
|
2788
|
+
"roles/",
|
|
2789
|
+
"contracts/",
|
|
2790
|
+
"skills/",
|
|
2791
|
+
".github/",
|
|
2792
|
+
".vinaya/",
|
|
2793
|
+
".claude/",
|
|
2794
|
+
".git/",
|
|
2795
|
+
".husky/"
|
|
2796
|
+
];
|
|
2797
|
+
var EXEMPT_LITERALS = new Set([
|
|
2798
|
+
"path/inside/the/shipped/diff.ts",
|
|
2799
|
+
"path/inside/the/surface.ts",
|
|
2800
|
+
"apps/x/specs/..."
|
|
2801
|
+
]);
|
|
2802
|
+
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
2803
|
+
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
2804
|
+
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";
|
|
2805
|
+
var VENDOR_EXAMPLE_START = /<!--\s*AEG:VENDOR-EXAMPLE:START\s*-->/;
|
|
2806
|
+
var VENDOR_EXAMPLE_END = /<!--\s*AEG:VENDOR-EXAMPLE:END\s*-->/;
|
|
2807
|
+
function maskVendorExampleRegion(masked) {
|
|
2808
|
+
const start = VENDOR_EXAMPLE_START.exec(masked);
|
|
2809
|
+
if (!start)
|
|
2810
|
+
return masked;
|
|
2811
|
+
const afterStart = start.index + start[0].length;
|
|
2812
|
+
const end = VENDOR_EXAMPLE_END.exec(masked.slice(afterStart));
|
|
2813
|
+
if (!end)
|
|
2814
|
+
return masked;
|
|
2815
|
+
const regionEnd = afterStart + end.index + end[0].length;
|
|
2816
|
+
const region = masked.slice(start.index, regionEnd);
|
|
2817
|
+
const blanked = region.replace(/[^\n]/g, " ");
|
|
2818
|
+
return masked.slice(0, start.index) + blanked + masked.slice(regionEnd);
|
|
2819
|
+
}
|
|
2820
|
+
function extractVendorMentions(content) {
|
|
2821
|
+
const scoped = maskVendorExampleRegion(maskCode(content));
|
|
2822
|
+
const found = [];
|
|
2823
|
+
const pattern = new RegExp(VENDOR_NAME_SOURCE, "gi");
|
|
2824
|
+
let match = pattern.exec(scoped);
|
|
2825
|
+
while (match !== null) {
|
|
2826
|
+
found.push({ name: match[0], index: match.index });
|
|
2827
|
+
match = pattern.exec(scoped);
|
|
2828
|
+
}
|
|
2829
|
+
return found;
|
|
2830
|
+
}
|
|
2831
|
+
function extractCitedPaths(content) {
|
|
2832
|
+
const found = [];
|
|
2833
|
+
const spanPattern = /`([^`\n]+)`/g;
|
|
2834
|
+
let match = spanPattern.exec(content);
|
|
2835
|
+
while (match !== null) {
|
|
2836
|
+
const cited = match[1] ?? "";
|
|
2837
|
+
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
2838
|
+
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
2839
|
+
found.push({ cited, index: match.index });
|
|
2840
|
+
}
|
|
2841
|
+
match = spanPattern.exec(content);
|
|
2842
|
+
}
|
|
2843
|
+
return found;
|
|
2844
|
+
}
|
|
2845
|
+
function lineAtIndex(content, index) {
|
|
2846
|
+
let line = 1;
|
|
2847
|
+
for (let i = 0;i < index; i++) {
|
|
2848
|
+
if (content.charCodeAt(i) === 10)
|
|
2849
|
+
line++;
|
|
2850
|
+
}
|
|
2851
|
+
return line;
|
|
2852
|
+
}
|
|
2853
|
+
function isPortable(cited, shipsPrefix) {
|
|
2854
|
+
if (cited.startsWith(shipsPrefix))
|
|
2855
|
+
return true;
|
|
2856
|
+
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
2857
|
+
}
|
|
2858
|
+
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
2859
|
+
const findings = [];
|
|
2860
|
+
for (const file of files) {
|
|
2861
|
+
if (!file.path.startsWith(shipsPrefix))
|
|
2862
|
+
continue;
|
|
2863
|
+
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
2864
|
+
if (EXEMPT_LITERALS.has(cited))
|
|
2865
|
+
continue;
|
|
2866
|
+
if (isPortable(cited, shipsPrefix))
|
|
2867
|
+
continue;
|
|
2868
|
+
findings.push({
|
|
2869
|
+
file: file.path,
|
|
2870
|
+
line: lineAtIndex(file.content, index),
|
|
2871
|
+
cited,
|
|
2872
|
+
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`,
|
|
2873
|
+
kind: "path"
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
for (const { name, index } of extractVendorMentions(file.content)) {
|
|
2877
|
+
findings.push({
|
|
2878
|
+
file: file.path,
|
|
2879
|
+
line: lineAtIndex(file.content, index),
|
|
2880
|
+
cited: name,
|
|
2881
|
+
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`,
|
|
2882
|
+
kind: "vendor-name"
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
return findings;
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// ../../packages/aeg-core/src/doctrine-no-procedures.ts
|
|
2890
|
+
function vendorExampleSpan(content) {
|
|
2891
|
+
const start = VENDOR_EXAMPLE_START.exec(content);
|
|
2892
|
+
if (!start)
|
|
2893
|
+
return null;
|
|
2894
|
+
const afterStart = start.index + start[0].length;
|
|
2895
|
+
const end = VENDOR_EXAMPLE_END.exec(content.slice(afterStart));
|
|
2896
|
+
if (!end)
|
|
2897
|
+
return null;
|
|
2898
|
+
return { start: start.index, end: afterStart + end.index + end[0].length };
|
|
2899
|
+
}
|
|
2900
|
+
function isCommandLine(line) {
|
|
2901
|
+
const word = line.trim().split(/\s+/)[0] ?? "";
|
|
2902
|
+
return COMMAND_WORDS.includes(word);
|
|
2903
|
+
}
|
|
2904
|
+
var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
|
|
2905
|
+
function procedureLineNumberAt(content, index) {
|
|
2906
|
+
return content.slice(0, index).split(`
|
|
2907
|
+
`).length;
|
|
2908
|
+
}
|
|
2909
|
+
function isTemplatePath(path) {
|
|
2910
|
+
return /(^|\/)templates\//.test(path);
|
|
2911
|
+
}
|
|
2912
|
+
function checkDoctrineNoProcedures(files) {
|
|
2913
|
+
const findings = [];
|
|
2914
|
+
for (const file of files) {
|
|
2915
|
+
if (isTemplatePath(file.path))
|
|
2916
|
+
continue;
|
|
2917
|
+
const span = vendorExampleSpan(file.content);
|
|
2918
|
+
for (const block of extractFencedBlocks(file.content)) {
|
|
2919
|
+
if (span && block.start >= span.start && block.end <= span.end)
|
|
2920
|
+
continue;
|
|
2921
|
+
if (!SHELL_LANGS.has(block.lang))
|
|
2922
|
+
continue;
|
|
2923
|
+
const commandLineCount = block.content.split(`
|
|
2924
|
+
`).filter(isCommandLine).length;
|
|
2925
|
+
if (commandLineCount >= 2) {
|
|
2926
|
+
findings.push({
|
|
2927
|
+
file: file.path,
|
|
2928
|
+
line: procedureLineNumberAt(file.content, block.start),
|
|
2929
|
+
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).`
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
return findings;
|
|
2935
|
+
}
|
|
2936
|
+
// ../../packages/aeg-core/src/pr-report-density.ts
|
|
2937
|
+
function headingSectionBody(prBody, heading) {
|
|
2938
|
+
const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "im");
|
|
2939
|
+
const m = headingRe.exec(prBody);
|
|
2940
|
+
if (!m)
|
|
2941
|
+
return null;
|
|
2942
|
+
const start = m.index + m[0].length;
|
|
2943
|
+
const rest = prBody.slice(start);
|
|
2944
|
+
const nextHeading = /^#{1,2}\s/m.exec(rest);
|
|
2945
|
+
return nextHeading ? rest.slice(0, nextHeading.index) : rest;
|
|
2946
|
+
}
|
|
2947
|
+
function stripAnchorBlocks(sectionText) {
|
|
2948
|
+
let result = sectionText;
|
|
2949
|
+
for (const field of ["CLOSES", "PROJECT", "TIER", "PREMISE", "TEST-PLAN", "EVIDENCE"]) {
|
|
2950
|
+
for (;; ) {
|
|
2951
|
+
const bounds = anchoredRegionBounds(result, field);
|
|
2952
|
+
if (!bounds)
|
|
2953
|
+
break;
|
|
2954
|
+
result = result.slice(0, bounds.outerStart) + result.slice(bounds.outerEnd);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
return result;
|
|
2958
|
+
}
|
|
2959
|
+
function paragraphCount(sectionText) {
|
|
2960
|
+
const stripped = stripCode(stripAnchorBlocks(sectionText)).trim();
|
|
2961
|
+
if (stripped.length === 0)
|
|
2962
|
+
return 0;
|
|
2963
|
+
return stripped.split(/\n[ \t]*\n/).map((block) => block.trim()).filter((block) => block.length > 0).length;
|
|
2964
|
+
}
|
|
2965
|
+
function checkSectionDensity(prBody, heading) {
|
|
2966
|
+
const body = headingSectionBody(prBody, heading);
|
|
2967
|
+
if (body === null)
|
|
2968
|
+
return { status: "pass", errors: [] };
|
|
2969
|
+
const count = paragraphCount(body);
|
|
2970
|
+
if (count <= 1)
|
|
2971
|
+
return { status: "pass", errors: [] };
|
|
2972
|
+
return {
|
|
2973
|
+
status: "fail",
|
|
2974
|
+
errors: [
|
|
2975
|
+
`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.`
|
|
2976
|
+
]
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
function checkSummaryDensity(prBody) {
|
|
2980
|
+
return checkSectionDensity(prBody, "Summary");
|
|
2981
|
+
}
|
|
2982
|
+
function checkScopeDensity(prBody) {
|
|
2983
|
+
return checkSectionDensity(prBody, "Scope");
|
|
2984
|
+
}
|
|
2985
|
+
function checkPrReportDensity(prBody) {
|
|
2986
|
+
const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
|
|
2987
|
+
return { errors: results.flatMap((r) => r.errors) };
|
|
2988
|
+
}
|
|
2414
2989
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
2415
2990
|
function hasRationaleField(body, labelPattern) {
|
|
2416
2991
|
const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
|
|
@@ -2662,6 +3237,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
2662
3237
|
const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
|
|
2663
3238
|
if (!depEntry)
|
|
2664
3239
|
continue;
|
|
3240
|
+
const sameTask = depEntry.trancheSlug === e.trancheSlug && depEntry.task.id === e.task.id;
|
|
3241
|
+
const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
|
|
3242
|
+
if (sameTask || sameIssue) {
|
|
3243
|
+
failures.push({
|
|
3244
|
+
issue: e.task.issue,
|
|
3245
|
+
tranche: e.trancheSlug,
|
|
3246
|
+
task: e.task.id,
|
|
3247
|
+
reason: `INTERNAL: parsed a self-dependency (depends-on ${dep}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream.`
|
|
3248
|
+
});
|
|
3249
|
+
continue;
|
|
3250
|
+
}
|
|
2665
3251
|
const depFacts = depEntry.facts;
|
|
2666
3252
|
const depClosed = depFacts?.issueState === "closed";
|
|
2667
3253
|
if (!depClosed) {
|
|
@@ -2805,41 +3391,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2805
3391
|
}
|
|
2806
3392
|
return { ok: true, expectedIssue };
|
|
2807
3393
|
}
|
|
2808
|
-
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2809
|
-
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2810
|
-
function extractHeadSha(comment) {
|
|
2811
|
-
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2812
|
-
return m ? m[1].toLowerCase() : null;
|
|
2813
|
-
}
|
|
2814
|
-
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2815
|
-
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2816
|
-
if (clearHits.length > 0) {
|
|
2817
|
-
const latest = clearHits[clearHits.length - 1];
|
|
2818
|
-
const m = latest.match(valuePattern);
|
|
2819
|
-
return {
|
|
2820
|
-
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2821
|
-
headSha: extractHeadSha(latest),
|
|
2822
|
-
danglingNote: null
|
|
2823
|
-
};
|
|
2824
|
-
}
|
|
2825
|
-
return {
|
|
2826
|
-
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2827
|
-
headSha: null,
|
|
2828
|
-
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2829
|
-
};
|
|
2830
|
-
}
|
|
2831
|
-
function extractCodeReviewVerdict(comments) {
|
|
2832
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
2833
|
-
}
|
|
2834
|
-
function extractSecurityReviewVerdict(comments) {
|
|
2835
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
2836
|
-
}
|
|
2837
3394
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
2838
3395
|
function isBoundToHead(extraction, headSha) {
|
|
2839
3396
|
if (!extraction.headSha)
|
|
2840
3397
|
return false;
|
|
2841
3398
|
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2842
3399
|
}
|
|
3400
|
+
function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
3401
|
+
if (patchIdOf === undefined || !extraction.headSha)
|
|
3402
|
+
return false;
|
|
3403
|
+
const judged = patchIdOf(extraction.headSha);
|
|
3404
|
+
const current = patchIdOf(headSha);
|
|
3405
|
+
if (judged === null || current === null)
|
|
3406
|
+
return false;
|
|
3407
|
+
return judged === current;
|
|
3408
|
+
}
|
|
3409
|
+
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3410
|
+
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3411
|
+
}
|
|
2843
3412
|
function checkReviewGate(input) {
|
|
2844
3413
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2845
3414
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2855,6 +3424,8 @@ function checkReviewGate(input) {
|
|
|
2855
3424
|
waived: true
|
|
2856
3425
|
};
|
|
2857
3426
|
}
|
|
3427
|
+
const reportedMechanicalChecks = input.mechanicalChecks.filter((c) => c.bucket !== "skipping" && c.bucket !== "neutral");
|
|
3428
|
+
const mechanicalChecksClean = reportedMechanicalChecks.length > 0 && reportedMechanicalChecks.every((c) => c.bucket === "pass");
|
|
2858
3429
|
const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
|
|
2859
3430
|
const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
|
|
2860
3431
|
const verifiedBodies = verified.map((c) => c.body);
|
|
@@ -2862,12 +3433,12 @@ function checkReviewGate(input) {
|
|
|
2862
3433
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2863
3434
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2864
3435
|
const securityClean = security.value === "PASS";
|
|
2865
|
-
const codeReviewBound =
|
|
2866
|
-
const securityBound =
|
|
2867
|
-
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
3436
|
+
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3437
|
+
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3438
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
|
|
2868
3439
|
return {
|
|
2869
3440
|
verdict: "pass",
|
|
2870
|
-
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
3441
|
+
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.`,
|
|
2871
3442
|
waived: false
|
|
2872
3443
|
};
|
|
2873
3444
|
}
|
|
@@ -2882,6 +3453,9 @@ function checkReviewGate(input) {
|
|
|
2882
3453
|
} else if (!securityBound) {
|
|
2883
3454
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2884
3455
|
}
|
|
3456
|
+
if (!mechanicalChecksClean) {
|
|
3457
|
+
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(", ")}`);
|
|
3458
|
+
}
|
|
2885
3459
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2886
3460
|
return {
|
|
2887
3461
|
verdict: "fail",
|
|
@@ -3161,72 +3735,6 @@ function compareToBaseline(current, baseline) {
|
|
|
3161
3735
|
perTool
|
|
3162
3736
|
};
|
|
3163
3737
|
}
|
|
3164
|
-
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
3165
|
-
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
3166
|
-
var STATIC_PORTABLE_PREFIXES = [
|
|
3167
|
-
"roles/",
|
|
3168
|
-
"contracts/",
|
|
3169
|
-
"skills/",
|
|
3170
|
-
".github/",
|
|
3171
|
-
".vinaya/",
|
|
3172
|
-
".claude/",
|
|
3173
|
-
".git/",
|
|
3174
|
-
".husky/"
|
|
3175
|
-
];
|
|
3176
|
-
var EXEMPT_LITERALS = new Set([
|
|
3177
|
-
"path/inside/the/shipped/diff.ts",
|
|
3178
|
-
"path/inside/the/surface.ts",
|
|
3179
|
-
"apps/x/specs/..."
|
|
3180
|
-
]);
|
|
3181
|
-
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
3182
|
-
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
3183
|
-
function extractCitedPaths(content) {
|
|
3184
|
-
const found = [];
|
|
3185
|
-
const spanPattern = /`([^`\n]+)`/g;
|
|
3186
|
-
let match = spanPattern.exec(content);
|
|
3187
|
-
while (match !== null) {
|
|
3188
|
-
const cited = match[1] ?? "";
|
|
3189
|
-
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
3190
|
-
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
3191
|
-
found.push({ cited, index: match.index });
|
|
3192
|
-
}
|
|
3193
|
-
match = spanPattern.exec(content);
|
|
3194
|
-
}
|
|
3195
|
-
return found;
|
|
3196
|
-
}
|
|
3197
|
-
function lineAtIndex(content, index) {
|
|
3198
|
-
let line = 1;
|
|
3199
|
-
for (let i = 0;i < index; i++) {
|
|
3200
|
-
if (content.charCodeAt(i) === 10)
|
|
3201
|
-
line++;
|
|
3202
|
-
}
|
|
3203
|
-
return line;
|
|
3204
|
-
}
|
|
3205
|
-
function isPortable(cited, shipsPrefix) {
|
|
3206
|
-
if (cited.startsWith(shipsPrefix))
|
|
3207
|
-
return true;
|
|
3208
|
-
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
3209
|
-
}
|
|
3210
|
-
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
3211
|
-
const findings = [];
|
|
3212
|
-
for (const file of files) {
|
|
3213
|
-
if (!file.path.startsWith(shipsPrefix))
|
|
3214
|
-
continue;
|
|
3215
|
-
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
3216
|
-
if (EXEMPT_LITERALS.has(cited))
|
|
3217
|
-
continue;
|
|
3218
|
-
if (isPortable(cited, shipsPrefix))
|
|
3219
|
-
continue;
|
|
3220
|
-
findings.push({
|
|
3221
|
-
file: file.path,
|
|
3222
|
-
line: lineAtIndex(file.content, index),
|
|
3223
|
-
cited,
|
|
3224
|
-
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
|
|
3225
|
-
});
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
|
-
return findings;
|
|
3229
|
-
}
|
|
3230
3738
|
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3231
3739
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3232
3740
|
function shipsArchivePrefix(shipsPrefix) {
|
|
@@ -3562,6 +4070,12 @@ function scanRetiredVocabulary(files) {
|
|
|
3562
4070
|
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
3563
4071
|
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
3564
4072
|
}
|
|
4073
|
+
function isSelfDependency(dep, task, issue) {
|
|
4074
|
+
if (dep.issue !== null && issue !== null && dep.issue === issue.number)
|
|
4075
|
+
return true;
|
|
4076
|
+
const bare = dep.id.trim().replace(/^#/, "");
|
|
4077
|
+
return bare.toLowerCase() === task.id.trim().toLowerCase();
|
|
4078
|
+
}
|
|
3565
4079
|
function checkDispatchReadiness(input) {
|
|
3566
4080
|
const { trancheSlug, task } = input;
|
|
3567
4081
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
@@ -3576,6 +4090,11 @@ function checkDispatchReadiness(input) {
|
|
|
3576
4090
|
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.`);
|
|
3577
4091
|
}
|
|
3578
4092
|
for (const dep of input.dependsOn) {
|
|
4093
|
+
if (isSelfDependency(dep, task, input.issue)) {
|
|
4094
|
+
const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
|
|
4095
|
+
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.`);
|
|
4096
|
+
continue;
|
|
4097
|
+
}
|
|
3579
4098
|
if (dep.resolved === false) {
|
|
3580
4099
|
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.`);
|
|
3581
4100
|
continue;
|
|
@@ -3815,27 +4334,27 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
3815
4334
|
};
|
|
3816
4335
|
}
|
|
3817
4336
|
const checkboxLines = section.split(`
|
|
3818
|
-
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]
|
|
4337
|
+
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
|
|
3819
4338
|
if (checkboxLines.length === 0) {
|
|
3820
4339
|
return {
|
|
3821
4340
|
verdict: "pass",
|
|
3822
4341
|
messages: [
|
|
3823
|
-
"Test Plan section found but has no checkbox items.",
|
|
4342
|
+
"Test Plan section found but has no `[principal]` checkbox items.",
|
|
3824
4343
|
"PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
|
|
3825
4344
|
]
|
|
3826
4345
|
};
|
|
3827
4346
|
}
|
|
3828
4347
|
const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
|
|
3829
4348
|
const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
|
|
3830
|
-
const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
4349
|
+
const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
3831
4350
|
if (unchecked.length > 0) {
|
|
3832
|
-
messages.push("", "FAIL — the following Test Plan items are unticked:");
|
|
4351
|
+
messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
|
|
3833
4352
|
for (const line of unchecked)
|
|
3834
4353
|
messages.push(` ${line}`);
|
|
3835
|
-
messages.push("", "Per aeg-root/roles/developer.md (Verification), a PR is not mergeable while any Test Plan box is unticked.", "
|
|
4354
|
+
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.");
|
|
3836
4355
|
return { verdict: "fail", messages };
|
|
3837
4356
|
}
|
|
3838
|
-
messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
4357
|
+
messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
3839
4358
|
return { verdict: "pass", messages };
|
|
3840
4359
|
}
|
|
3841
4360
|
// ../../packages/aeg-core/src/workspace-escape.ts
|
|
@@ -3916,12 +4435,351 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
3916
4435
|
return findings;
|
|
3917
4436
|
}
|
|
3918
4437
|
// src/lib/diff-evidence.ts
|
|
4438
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
4439
|
+
import { join as join2, relative, resolve as resolve2 } from "node:path";
|
|
4440
|
+
|
|
4441
|
+
// src/lib/config.ts
|
|
3919
4442
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3920
|
-
import {
|
|
4443
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
4444
|
+
import { homedir } from "node:os";
|
|
4445
|
+
import { dirname, join, resolve } from "node:path";
|
|
4446
|
+
import { z } from "zod";
|
|
4447
|
+
|
|
4448
|
+
// src/lib/agent-vendors.ts
|
|
4449
|
+
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
4450
|
+
|
|
4451
|
+
// src/lib/config.ts
|
|
4452
|
+
var EnvEntrySchema = z.union([
|
|
4453
|
+
z.literal(true),
|
|
4454
|
+
z.object({ optional: z.literal(true) }),
|
|
4455
|
+
z.object({ anyOf: z.array(z.string()).min(2) }),
|
|
4456
|
+
z.string()
|
|
4457
|
+
]);
|
|
4458
|
+
var CheckEntrySchema = z.object({
|
|
4459
|
+
run: z.string(),
|
|
4460
|
+
scope: z.enum(["diff", "full"]),
|
|
4461
|
+
include: z.array(z.string()).optional(),
|
|
4462
|
+
args: z.array(z.string()).optional(),
|
|
4463
|
+
timeoutMs: z.number().optional(),
|
|
4464
|
+
env: z.record(z.string(), EnvEntrySchema).optional(),
|
|
4465
|
+
requiresOpenPr: z.boolean().optional(),
|
|
4466
|
+
ownWorkflow: z.boolean().optional()
|
|
4467
|
+
}).superRefine((entry2, ctx) => {
|
|
4468
|
+
if (!entry2.env)
|
|
4469
|
+
return;
|
|
4470
|
+
for (const [key, value] of Object.entries(entry2.env)) {
|
|
4471
|
+
if (typeof value !== "object" || value === null || !("anyOf" in value))
|
|
4472
|
+
continue;
|
|
4473
|
+
const members = value.anyOf;
|
|
4474
|
+
if (new Set(members).size !== members.length) {
|
|
4475
|
+
ctx.addIssue({
|
|
4476
|
+
code: z.ZodIssueCode.custom,
|
|
4477
|
+
message: `env.${key}.anyOf has duplicate members`,
|
|
4478
|
+
path: ["env", key, "anyOf"]
|
|
4479
|
+
});
|
|
4480
|
+
}
|
|
4481
|
+
if (!members.includes(key)) {
|
|
4482
|
+
ctx.addIssue({
|
|
4483
|
+
code: z.ZodIssueCode.custom,
|
|
4484
|
+
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
4485
|
+
path: ["env", key, "anyOf"]
|
|
4486
|
+
});
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
});
|
|
4490
|
+
var RoleEntrySchema = z.object({
|
|
4491
|
+
contract: z.string().refine((p) => p.includes("/"), {
|
|
4492
|
+
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
4493
|
+
})
|
|
4494
|
+
});
|
|
4495
|
+
var BRIEF_BUILTINS = [
|
|
4496
|
+
"tier",
|
|
4497
|
+
"testPlan",
|
|
4498
|
+
"testPlanExclusivity",
|
|
4499
|
+
"principalPlaceholder",
|
|
4500
|
+
"surfaceMap",
|
|
4501
|
+
"docUpdateList",
|
|
4502
|
+
"worktreeStep0",
|
|
4503
|
+
"stopConditions",
|
|
4504
|
+
"autonomyClause",
|
|
4505
|
+
"project",
|
|
4506
|
+
"for",
|
|
4507
|
+
"closesN",
|
|
4508
|
+
"premiseCoverage",
|
|
4509
|
+
"issueRationale",
|
|
4510
|
+
"milestoneShape"
|
|
4511
|
+
];
|
|
4512
|
+
var BriefSectionSchema = z.union([
|
|
4513
|
+
z.object({ builtin: z.enum(BRIEF_BUILTINS) }),
|
|
4514
|
+
z.object({ heading: z.string().min(1), name: z.string().optional() }),
|
|
4515
|
+
z.object({ field: z.string().min(1), name: z.string().optional() }),
|
|
4516
|
+
z.object({ phrase: z.string().min(1), name: z.string().optional() })
|
|
4517
|
+
]);
|
|
4518
|
+
var BriefSchemaSchema = z.object({
|
|
4519
|
+
pr: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
|
|
4520
|
+
issue: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
|
|
4521
|
+
milestone: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
|
|
4522
|
+
ack: z.array(z.enum(BRIEF_BUILTINS)).optional()
|
|
4523
|
+
});
|
|
4524
|
+
function isSafeRepoRelPath(p) {
|
|
4525
|
+
if (p.length === 0)
|
|
4526
|
+
return false;
|
|
4527
|
+
if (p.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(p))
|
|
4528
|
+
return false;
|
|
4529
|
+
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
4530
|
+
}
|
|
4531
|
+
var SafeRepoRelPath = z.string().refine(isSafeRepoRelPath, {
|
|
4532
|
+
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
4533
|
+
});
|
|
4534
|
+
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
4535
|
+
function isCanonicalHookBlockPath(p) {
|
|
4536
|
+
return CANONICAL_HOOK_BLOCK_PREFIXES.some((prefix) => p.startsWith(prefix) && p.length > prefix.length);
|
|
4537
|
+
}
|
|
4538
|
+
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
4539
|
+
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
4540
|
+
});
|
|
4541
|
+
var ManagedBlockRecordSchema = z.object({
|
|
4542
|
+
path: ManagedHookBlockPath,
|
|
4543
|
+
marker: z.string(),
|
|
4544
|
+
comment: z.enum(["hash", "html"])
|
|
4545
|
+
});
|
|
4546
|
+
var ManagedManifestSchema = z.object({
|
|
4547
|
+
version: z.number().int().positive(),
|
|
4548
|
+
files: z.array(SafeRepoRelPath),
|
|
4549
|
+
blocks: z.array(ManagedBlockRecordSchema),
|
|
4550
|
+
labels: z.array(z.string()),
|
|
4551
|
+
agents: z.array(z.enum(AGENT_VENDORS)).optional()
|
|
4552
|
+
});
|
|
4553
|
+
var ProjectEntrySchema = z.object({
|
|
4554
|
+
name: z.string().min(1),
|
|
4555
|
+
description: z.string().min(1).optional(),
|
|
4556
|
+
path: z.string().min(1).optional()
|
|
4557
|
+
});
|
|
4558
|
+
function parseTokensCollectDeclaration(value) {
|
|
4559
|
+
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
4560
|
+
if (!m)
|
|
4561
|
+
return null;
|
|
4562
|
+
const [, interpreter, script] = m;
|
|
4563
|
+
if (!isSafeRepoRelPath(script))
|
|
4564
|
+
return null;
|
|
4565
|
+
return { interpreter, script };
|
|
4566
|
+
}
|
|
4567
|
+
var VinayaConfigSchema = z.object({
|
|
4568
|
+
rings: z.object({
|
|
4569
|
+
ring1_forgeWriteInterception: z.boolean(),
|
|
4570
|
+
ring2_asyncAudits: z.boolean()
|
|
4571
|
+
}).optional(),
|
|
4572
|
+
checks: z.record(z.string(), CheckEntrySchema).optional(),
|
|
4573
|
+
roles: z.record(z.string(), RoleEntrySchema).optional(),
|
|
4574
|
+
briefSchema: BriefSchemaSchema.optional(),
|
|
4575
|
+
managed: ManagedManifestSchema.optional(),
|
|
4576
|
+
principals: z.array(z.string()).min(1).optional(),
|
|
4577
|
+
releaseActor: z.string().min(1).optional(),
|
|
4578
|
+
ci: z.object({ setup: z.string().min(1) }).optional(),
|
|
4579
|
+
tokens: z.object({
|
|
4580
|
+
collect: z.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
4581
|
+
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
4582
|
+
})
|
|
4583
|
+
}).optional(),
|
|
4584
|
+
blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional(),
|
|
4585
|
+
proseGates: z.object({
|
|
4586
|
+
doctrineRoot: z.string().min(1).optional(),
|
|
4587
|
+
readerFacingPrefix: z.string().min(1).optional(),
|
|
4588
|
+
readerFacingSuffix: z.string().min(1).optional(),
|
|
4589
|
+
legacySlugDir: z.string().min(1).optional()
|
|
4590
|
+
}).optional(),
|
|
4591
|
+
projects: z.array(ProjectEntrySchema).optional()
|
|
4592
|
+
});
|
|
4593
|
+
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
4594
|
+
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|
|
4595
|
+
var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
|
|
4596
|
+
function findLocalConfig() {
|
|
4597
|
+
let dir = process.cwd();
|
|
4598
|
+
while (true) {
|
|
4599
|
+
const candidate = join(dir, LOCAL_CONFIG_FILENAME);
|
|
4600
|
+
if (existsSync(candidate))
|
|
4601
|
+
return candidate;
|
|
4602
|
+
if (existsSync(join(dir, ".git")))
|
|
4603
|
+
return null;
|
|
4604
|
+
const parent = dirname(dir);
|
|
4605
|
+
if (parent === dir)
|
|
4606
|
+
return null;
|
|
4607
|
+
dir = parent;
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
function configPath() {
|
|
4611
|
+
const local = findLocalConfig();
|
|
4612
|
+
if (local)
|
|
4613
|
+
return local;
|
|
4614
|
+
if (existsSync(GLOBAL_CONFIG_PATH))
|
|
4615
|
+
return GLOBAL_CONFIG_PATH;
|
|
4616
|
+
return null;
|
|
4617
|
+
}
|
|
4618
|
+
function globalChecksIgnoredWarning(path) {
|
|
4619
|
+
return `${path}: "checks" registration in the global config is ignored — checks may only be registered from a repo-local vinaya.config.json.`;
|
|
4620
|
+
}
|
|
4621
|
+
function globalPrincipalsIgnoredWarning(path) {
|
|
4622
|
+
return `${path}: "principals" in the global config is ignored — principals may only be declared from a repo-local vinaya.config.json.`;
|
|
4623
|
+
}
|
|
4624
|
+
function globalRolesIgnoredWarning(path) {
|
|
4625
|
+
return `${path}: "roles" registration in the global config is ignored — roles may only be registered from a repo-local vinaya.config.json.`;
|
|
4626
|
+
}
|
|
4627
|
+
function globalReleaseActorIgnoredWarning(path) {
|
|
4628
|
+
return `${path}: "releaseActor" in the global config is ignored — releaseActor may only be declared from a repo-local vinaya.config.json.`;
|
|
4629
|
+
}
|
|
4630
|
+
function globalTokensCollectIgnoredWarning(path) {
|
|
4631
|
+
return `${path}: "tokens" in the global config is ignored — tokens.collect may only be declared from a repo-local vinaya.config.json.`;
|
|
4632
|
+
}
|
|
4633
|
+
function stripGlobalOnlyKeys(config, path) {
|
|
4634
|
+
if (path !== GLOBAL_CONFIG_PATH)
|
|
4635
|
+
return config;
|
|
4636
|
+
let result = config;
|
|
4637
|
+
if (result.checks && Object.keys(result.checks).length > 0) {
|
|
4638
|
+
console.error(`⚠ ${globalChecksIgnoredWarning(path)}`);
|
|
4639
|
+
result = { ...result, checks: undefined };
|
|
4640
|
+
}
|
|
4641
|
+
if (result.roles && Object.keys(result.roles).length > 0) {
|
|
4642
|
+
console.error(`⚠ ${globalRolesIgnoredWarning(path)}`);
|
|
4643
|
+
result = { ...result, roles: undefined };
|
|
4644
|
+
}
|
|
4645
|
+
if (result.principals && result.principals.length > 0) {
|
|
4646
|
+
console.error(`⚠ ${globalPrincipalsIgnoredWarning(path)}`);
|
|
4647
|
+
result = { ...result, principals: undefined };
|
|
4648
|
+
}
|
|
4649
|
+
if (result.releaseActor) {
|
|
4650
|
+
console.error(`⚠ ${globalReleaseActorIgnoredWarning(path)}`);
|
|
4651
|
+
result = { ...result, releaseActor: undefined };
|
|
4652
|
+
}
|
|
4653
|
+
if (result.tokens) {
|
|
4654
|
+
console.error(`⚠ ${globalTokensCollectIgnoredWarning(path)}`);
|
|
4655
|
+
result = { ...result, tokens: undefined };
|
|
4656
|
+
}
|
|
4657
|
+
return result;
|
|
4658
|
+
}
|
|
4659
|
+
function resolvePrincipalAllowlist(config) {
|
|
4660
|
+
return config?.principals ?? PRINCIPAL_ALLOWLIST;
|
|
4661
|
+
}
|
|
4662
|
+
function resolveReleaseActor(config) {
|
|
4663
|
+
return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
|
|
4664
|
+
}
|
|
4665
|
+
function trustAnchorRepo() {
|
|
4666
|
+
const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
4667
|
+
const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
|
|
4668
|
+
if (fromRunner) {
|
|
4669
|
+
const validated = wellFormed(fromRunner);
|
|
4670
|
+
if (validated)
|
|
4671
|
+
return validated;
|
|
4672
|
+
}
|
|
4673
|
+
try {
|
|
4674
|
+
const url = execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
4675
|
+
encoding: "utf-8",
|
|
4676
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4677
|
+
}).trim();
|
|
4678
|
+
const m = url.match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
4679
|
+
return m?.[1] && m[2] ? wellFormed(`${m[1]}/${m[2]}`) : null;
|
|
4680
|
+
} catch {
|
|
4681
|
+
return null;
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
function ghFetchTrustAnchorConfig() {
|
|
4685
|
+
const repo = trustAnchorRepo();
|
|
4686
|
+
if (!repo)
|
|
4687
|
+
throw new Error("could not resolve repo identity for the trust-anchor read");
|
|
4688
|
+
return execFileSync2("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
|
|
4689
|
+
encoding: "utf-8",
|
|
4690
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4691
|
+
});
|
|
4692
|
+
}
|
|
4693
|
+
function firstLine(err) {
|
|
4694
|
+
return err?.message?.split(`
|
|
4695
|
+
`)[0] ?? "unknown error";
|
|
4696
|
+
}
|
|
4697
|
+
function isMissingFileError(err) {
|
|
4698
|
+
const stderr = err?.stderr;
|
|
4699
|
+
const haystack = [
|
|
4700
|
+
err?.message ?? "",
|
|
4701
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
4702
|
+
].join(`
|
|
4703
|
+
`);
|
|
4704
|
+
return /\b404\b|not found/i.test(haystack);
|
|
4705
|
+
}
|
|
4706
|
+
function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
|
|
4707
|
+
const warn = (why) => process.stdout.write(`⚠ could not read the trust-anchor config (\`principals\`/\`releaseActor\`) from the default branch (${why}) — falling back to vinaya's built-in defaults.
|
|
4708
|
+
`);
|
|
4709
|
+
let base64;
|
|
4710
|
+
try {
|
|
4711
|
+
base64 = fetcher().trim();
|
|
4712
|
+
} catch (err) {
|
|
4713
|
+
if (!isMissingFileError(err))
|
|
4714
|
+
warn(firstLine(err));
|
|
4715
|
+
return null;
|
|
4716
|
+
}
|
|
4717
|
+
if (!base64)
|
|
4718
|
+
return null;
|
|
4719
|
+
try {
|
|
4720
|
+
const raw = Buffer.from(base64, "base64").toString("utf-8");
|
|
4721
|
+
const parsed = VinayaConfigSchema.safeParse(JSON.parse(raw));
|
|
4722
|
+
if (parsed.success)
|
|
4723
|
+
return parsed.data;
|
|
4724
|
+
warn("it did not satisfy the config schema");
|
|
4725
|
+
return null;
|
|
4726
|
+
} catch {
|
|
4727
|
+
warn("it was not readable as JSON");
|
|
4728
|
+
return null;
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
function loadConfig() {
|
|
4732
|
+
const path = configPath();
|
|
4733
|
+
if (!path)
|
|
4734
|
+
return null;
|
|
4735
|
+
try {
|
|
4736
|
+
const raw = JSON.parse(readFileSync2(path, "utf-8"));
|
|
4737
|
+
return stripGlobalOnlyKeys(VinayaConfigSchema.parse(raw), path);
|
|
4738
|
+
} catch {
|
|
4739
|
+
return null;
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
|
|
4743
|
+
|
|
4744
|
+
// src/checks/contract.ts
|
|
4745
|
+
var CHECK_SCHEMA_VERSION = 1;
|
|
4746
|
+
function emitCheckError(error) {
|
|
4747
|
+
process.stderr.write(`${JSON.stringify(error)}
|
|
4748
|
+
`);
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
// src/commands/review-post.ts
|
|
4752
|
+
function parseChangedLineRanges(diffOutput) {
|
|
4753
|
+
const result = {};
|
|
4754
|
+
let currentFile = null;
|
|
4755
|
+
for (const line of diffOutput.split(`
|
|
4756
|
+
`)) {
|
|
4757
|
+
if (line.startsWith("+++ ")) {
|
|
4758
|
+
const path = line.slice(4).trim();
|
|
4759
|
+
currentFile = path === "/dev/null" ? null : path.replace(/^b\//, "");
|
|
4760
|
+
continue;
|
|
4761
|
+
}
|
|
4762
|
+
if (line.startsWith("@@") && currentFile) {
|
|
4763
|
+
const m = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
|
|
4764
|
+
if (m) {
|
|
4765
|
+
const start = Number(m[1]);
|
|
4766
|
+
const count = m[2] !== undefined ? Number(m[2]) : 1;
|
|
4767
|
+
if (count > 0) {
|
|
4768
|
+
if (!result[currentFile])
|
|
4769
|
+
result[currentFile] = [];
|
|
4770
|
+
result[currentFile].push([start, start + count - 1]);
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
return result;
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4778
|
+
// src/lib/diff-evidence.ts
|
|
3921
4779
|
function fileDiffAgainst(ref, path) {
|
|
3922
4780
|
let out;
|
|
3923
4781
|
try {
|
|
3924
|
-
out =
|
|
4782
|
+
out = execFileSync3("git", ["diff", `${ref}...HEAD`, "--", path], {
|
|
3925
4783
|
encoding: "utf8",
|
|
3926
4784
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3927
4785
|
}).trim();
|
|
@@ -3932,7 +4790,7 @@ function fileDiffAgainst(ref, path) {
|
|
|
3932
4790
|
}
|
|
3933
4791
|
function revParse(ref) {
|
|
3934
4792
|
try {
|
|
3935
|
-
return
|
|
4793
|
+
return execFileSync3("git", ["rev-parse", "--verify", ref], {
|
|
3936
4794
|
encoding: "utf8",
|
|
3937
4795
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3938
4796
|
}).trim();
|
|
@@ -3942,7 +4800,7 @@ function revParse(ref) {
|
|
|
3942
4800
|
}
|
|
3943
4801
|
function repoRoot() {
|
|
3944
4802
|
try {
|
|
3945
|
-
return
|
|
4803
|
+
return execFileSync3("git", ["rev-parse", "--show-toplevel"], {
|
|
3946
4804
|
encoding: "utf8",
|
|
3947
4805
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3948
4806
|
}).trim();
|
|
@@ -3953,7 +4811,7 @@ function repoRoot() {
|
|
|
3953
4811
|
function changedFiles(base) {
|
|
3954
4812
|
let out;
|
|
3955
4813
|
try {
|
|
3956
|
-
out =
|
|
4814
|
+
out = execFileSync3("git", ["diff", "--name-only", `${base}...HEAD`], {
|
|
3957
4815
|
encoding: "utf8",
|
|
3958
4816
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3959
4817
|
}).trim();
|
|
@@ -3964,6 +4822,9 @@ function changedFiles(base) {
|
|
|
3964
4822
|
`).map((s) => s.trim()).filter(Boolean);
|
|
3965
4823
|
}
|
|
3966
4824
|
function resolveChangedFiles(base = process.env.BASE_SHA || "origin/main") {
|
|
4825
|
+
return resolveDiff(base)?.files ?? null;
|
|
4826
|
+
}
|
|
4827
|
+
function resolveDiff(base = process.env.BASE_SHA || "origin/main") {
|
|
3967
4828
|
const root = repoRoot();
|
|
3968
4829
|
if (root === null)
|
|
3969
4830
|
return null;
|
|
@@ -3975,16 +4836,35 @@ function resolveChangedFiles(base = process.env.BASE_SHA || "origin/main") {
|
|
|
3975
4836
|
const relPaths = changedFiles(ref);
|
|
3976
4837
|
if (relPaths === null)
|
|
3977
4838
|
continue;
|
|
3978
|
-
return relPaths.map((p) =>
|
|
4839
|
+
return { base: ref, root, files: relPaths.map((p) => join2(root, p)) };
|
|
3979
4840
|
}
|
|
3980
4841
|
return null;
|
|
3981
4842
|
}
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
4843
|
+
function changedLineRanges(ref, path) {
|
|
4844
|
+
const diff = fileDiffAgainst(ref, path);
|
|
4845
|
+
if (diff === null)
|
|
4846
|
+
return null;
|
|
4847
|
+
return parseChangedLineRanges(diff)[path] ?? null;
|
|
4848
|
+
}
|
|
4849
|
+
function lineIsInRanges(line, ranges) {
|
|
4850
|
+
return ranges.some(([start, end]) => line >= start && line <= end);
|
|
4851
|
+
}
|
|
4852
|
+
function findingsInThisDiff(findings) {
|
|
4853
|
+
const diff = resolveDiff();
|
|
4854
|
+
if (diff === null)
|
|
4855
|
+
return [...findings];
|
|
4856
|
+
const changed = new Set(diff.files);
|
|
4857
|
+
const ranges = new Map;
|
|
4858
|
+
return findings.filter((f) => {
|
|
4859
|
+
const abs = resolve2(diff.root, f.file);
|
|
4860
|
+
if (!changed.has(abs))
|
|
4861
|
+
return false;
|
|
4862
|
+
const relPath = relative(diff.root, abs);
|
|
4863
|
+
if (!ranges.has(relPath))
|
|
4864
|
+
ranges.set(relPath, changedLineRanges(diff.base, relPath));
|
|
4865
|
+
const fileRanges = ranges.get(relPath) ?? null;
|
|
4866
|
+
return fileRanges === null ? true : lineIsInRanges(f.line, fileRanges);
|
|
4867
|
+
});
|
|
3988
4868
|
}
|
|
3989
4869
|
|
|
3990
4870
|
// src/checks/changeset-coverage-logic.ts
|
|
@@ -4057,7 +4937,7 @@ function evaluateChangesetCoverage(fixedGroup, changedFiles2, isReleaseBranch) {
|
|
|
4057
4937
|
var CHECK_NAME = "changeset-coverage";
|
|
4058
4938
|
function git(args) {
|
|
4059
4939
|
try {
|
|
4060
|
-
return
|
|
4940
|
+
return execFileSync4("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
4061
4941
|
} catch {
|
|
4062
4942
|
return "";
|
|
4063
4943
|
}
|
|
@@ -4067,7 +4947,7 @@ function toPosix(p) {
|
|
|
4067
4947
|
}
|
|
4068
4948
|
function readPackageJson(path) {
|
|
4069
4949
|
try {
|
|
4070
|
-
const parsed = JSON.parse(
|
|
4950
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
4071
4951
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
4072
4952
|
} catch {
|
|
4073
4953
|
return null;
|
|
@@ -4088,18 +4968,18 @@ function childDirs(absoluteDir) {
|
|
|
4088
4968
|
function candidateMemberDirs(root, pattern) {
|
|
4089
4969
|
if (pattern.endsWith("/*")) {
|
|
4090
4970
|
const prefix = pattern.slice(0, -2);
|
|
4091
|
-
return childDirs(
|
|
4971
|
+
return childDirs(join3(root, prefix)).map((name) => `${prefix}/${name}`);
|
|
4092
4972
|
}
|
|
4093
4973
|
if (!pattern.includes("*"))
|
|
4094
4974
|
return [pattern];
|
|
4095
4975
|
return [];
|
|
4096
4976
|
}
|
|
4097
4977
|
function resolveWorkspaceMembers(root) {
|
|
4098
|
-
const rootPkg = readPackageJson(
|
|
4978
|
+
const rootPkg = readPackageJson(join3(root, "package.json"));
|
|
4099
4979
|
const members = new Map;
|
|
4100
4980
|
for (const pattern of workspacePatterns(rootPkg)) {
|
|
4101
4981
|
for (const dir of candidateMemberDirs(root, pattern)) {
|
|
4102
|
-
const pkg = readPackageJson(
|
|
4982
|
+
const pkg = readPackageJson(join3(root, dir, "package.json"));
|
|
4103
4983
|
const name = pkg?.name;
|
|
4104
4984
|
const files = pkg?.files;
|
|
4105
4985
|
if (typeof name !== "string" || !Array.isArray(files))
|
|
@@ -4110,7 +4990,7 @@ function resolveWorkspaceMembers(root) {
|
|
|
4110
4990
|
return members;
|
|
4111
4991
|
}
|
|
4112
4992
|
function fixedGroupNames(root) {
|
|
4113
|
-
const pkg = readPackageJson(
|
|
4993
|
+
const pkg = readPackageJson(join3(root, ".changeset", "config.json"));
|
|
4114
4994
|
const fixed = pkg?.fixed;
|
|
4115
4995
|
if (!Array.isArray(fixed))
|
|
4116
4996
|
return [];
|
|
@@ -4132,7 +5012,7 @@ function main() {
|
|
|
4132
5012
|
if (changedAbs.length === 0) {
|
|
4133
5013
|
process.exit(0);
|
|
4134
5014
|
}
|
|
4135
|
-
const changedFiles2 = changedAbs.map((p) => toPosix(
|
|
5015
|
+
const changedFiles2 = changedAbs.map((p) => toPosix(relative2(root, p)));
|
|
4136
5016
|
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
4137
5017
|
const isReleaseBranch = branch === CHANGESET_RELEASE_BRANCH;
|
|
4138
5018
|
const workspaceMembers = resolveWorkspaceMembers(root);
|