@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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed model for AEG artifacts. See `aeg-root/tranche-model.md` for the
|
|
3
|
+
* canonical specification (§3 status table, §4 thin-file template).
|
|
4
|
+
*
|
|
5
|
+
* This module is pure: no I/O. The parser produces these shapes from file
|
|
6
|
+
* contents; `deriveTranche` consumes them plus a `ForgeFacts` snapshot.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ForgeFacts, Tranche, Lifecycle, Task } from '@attalabs/aeg-types'
|
|
10
|
+
|
|
11
|
+
// ---------- Registry (projects.md) ----------
|
|
12
|
+
|
|
13
|
+
export type Project = {
|
|
14
|
+
/** Registry-unique name. Authority for `Project:` validation. */
|
|
15
|
+
name: string
|
|
16
|
+
/** Declared home folder (e.g. `apps/<product>`). Never derived. */
|
|
17
|
+
path: string
|
|
18
|
+
/** Specs folder for the project. */
|
|
19
|
+
specsPath: string
|
|
20
|
+
/**
|
|
21
|
+
* Per-project state folder, or `null` when the cell holds prose like
|
|
22
|
+
* "state tracked globally for now" — i.e. state isn't tracked under this
|
|
23
|
+
* project's path.
|
|
24
|
+
*/
|
|
25
|
+
statePath: string | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type Registry = Project[]
|
|
29
|
+
|
|
30
|
+
// ---------- Tranche file (tranches/<name>.md) ----------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `Lifecycle`/`Task`/`Tranche` live in `@attalabs/aeg-types` (task
|
|
34
|
+
* aeg-forge-state-v1 3a) — re-exported here since every existing call site
|
|
35
|
+
* across the repo imports them from `@attalabs/aeg-core`. Moved out so
|
|
36
|
+
* `@attalabs/aeg-forge-state` can depend on these shapes without creating a
|
|
37
|
+
* package cycle with `aeg-core` (which in turn depends on
|
|
38
|
+
* `aeg-forge-state`'s derivation function).
|
|
39
|
+
*/
|
|
40
|
+
export type { Lifecycle, Task, Tranche }
|
|
41
|
+
|
|
42
|
+
// ---------- Forge facts ----------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `ForgeFacts` lives in `@attalabs/aeg-types` (aeg-core-purity fix, #521) —
|
|
46
|
+
* re-exported here for the same reason as `Lifecycle`/`Task`/`Tranche`:
|
|
47
|
+
* every existing call site imports it from `@attalabs/aeg-core`, and it moved
|
|
48
|
+
* out so the I/O-performing fetchers that produce it (now in
|
|
49
|
+
* `@attalabs/aeg-forge-state`) don't need to depend on `aeg-core`.
|
|
50
|
+
*/
|
|
51
|
+
export type { ForgeFacts }
|
|
52
|
+
|
|
53
|
+
// ---------- Derivation output ----------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The statuses derivation can conclude. The *rules* that produce them are not
|
|
57
|
+
* written here or in `derive-tranche.ts` — they live as an ordered,
|
|
58
|
+
* pure-data list in `state-machine-model.ts` (the `actions.ts`
|
|
59
|
+
* discipline), which `deriveStatus` executes and the docs render from the
|
|
60
|
+
* same list.
|
|
61
|
+
*
|
|
62
|
+
* `backlog` remains a member because it is a project-level concept consumers
|
|
63
|
+
* still render; derivation never emits it inside a tranche.
|
|
64
|
+
*
|
|
65
|
+
* `dropped` and `incoherent` are the two **honest terminal** statuses.
|
|
66
|
+
* A closed Issue with no merged PR must never resolve to `todo` (which implies
|
|
67
|
+
* not-started). `dropped` = closed `NOT_PLANNED` (legitimately abandoned);
|
|
68
|
+
* `incoherent` = closed `COMPLETED` but with no merged-PR link (genuinely done
|
|
69
|
+
* yet unprovable, or a broken close). Both are surfaced, never hidden.
|
|
70
|
+
*/
|
|
71
|
+
export type DerivedStatus =
|
|
72
|
+
| 'backlog'
|
|
73
|
+
| 'todo'
|
|
74
|
+
| 'in-flight'
|
|
75
|
+
| 'in-review'
|
|
76
|
+
| 'changes-requested'
|
|
77
|
+
| 'merged'
|
|
78
|
+
| 'blocked'
|
|
79
|
+
| 'dropped'
|
|
80
|
+
| 'incoherent'
|
|
81
|
+
|
|
82
|
+
export type DispatchBlockers = {
|
|
83
|
+
/** Depends-on edges whose target is not yet `merged`. */
|
|
84
|
+
dependsOnNotMerged: string[]
|
|
85
|
+
/**
|
|
86
|
+
* Conflicts-with edges whose target is currently occupying its collision
|
|
87
|
+
* domain — derived status `in-flight`, `in-review`, or `changes-requested`
|
|
88
|
+
* (§8 conflict gate).
|
|
89
|
+
*/
|
|
90
|
+
conflictsWithOpenOrInFlight: string[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type DerivedTask = {
|
|
94
|
+
task: Task
|
|
95
|
+
status: DerivedStatus
|
|
96
|
+
/**
|
|
97
|
+
* `true` when the §8 gates allow dispatching this task: all `dependsOn`
|
|
98
|
+
* targets are `merged`, and no `conflictsWith` target is open/in-flight.
|
|
99
|
+
* Dispatchability is independent of this task's own current status — a
|
|
100
|
+
* task already `in-review` is still "dispatchable" by this definition (the
|
|
101
|
+
* gates would have allowed it to start).
|
|
102
|
+
*/
|
|
103
|
+
dispatchable: boolean
|
|
104
|
+
blockers: DispatchBlockers
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type UnknownEdge = {
|
|
108
|
+
from: string
|
|
109
|
+
to: string
|
|
110
|
+
kind: 'depends-on' | 'conflicts-with'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type DerivedTranche = {
|
|
114
|
+
tranche: Tranche
|
|
115
|
+
tasks: DerivedTask[]
|
|
116
|
+
/**
|
|
117
|
+
* Edges that reference task ids not present in this tranche's topology
|
|
118
|
+
* table. Surface for diagnostics; does not throw, since real files
|
|
119
|
+
* occasionally name dropped/prose-only ids (e.g. a `3a` that survives only
|
|
120
|
+
* in a tranche file's narrative).
|
|
121
|
+
*/
|
|
122
|
+
unknownEdges: UnknownEdge[]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------- Token ledger (tranches/<name>.tokens.md) ----------
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* One row of the append-only per-tranche token/cost ledger. See
|
|
129
|
+
* `aeg-root/tranche-model.md` §12 for the canonical format. Each role
|
|
130
|
+
* appends one row at the end of its turn; re-entry appends another row.
|
|
131
|
+
* The tranche total is `sum(rows)`, derived at read time — never stored.
|
|
132
|
+
*
|
|
133
|
+
* The two capture sources surface here as `null` cells: a row written by a
|
|
134
|
+
* claude.ai role (Planner / Brief Author / Reviewer / Security) before the
|
|
135
|
+
* Principal has filled in its usage figure has `tokensIn`/`tokensOut`/`cost`
|
|
136
|
+
* as `null`; a terminal-role row (Developer / Archivist with `/cost`) has
|
|
137
|
+
* exact numbers.
|
|
138
|
+
*/
|
|
139
|
+
export type LedgerRow = {
|
|
140
|
+
/** Free-text phase identifier. Convention: `<task-id>: <phase>` per task
|
|
141
|
+
* (e.g. `9: develop`), or a bare phase for tranche-wide work (e.g.
|
|
142
|
+
* `planning`). The parser does not enforce the convention — the Phase
|
|
143
|
+
* cell is opaque so the table can be re-pivoted later. */
|
|
144
|
+
phase: string
|
|
145
|
+
role: string
|
|
146
|
+
/** Free-text — the role's agent + model (e.g. `claude-opus-4-7 (CC)`,
|
|
147
|
+
* `claude-opus-4-7 (chat)`). The PRICING table dependency is on the
|
|
148
|
+
* model name, not on the parser. */
|
|
149
|
+
agentModel: string
|
|
150
|
+
/** `null` when the cell was `—` / `-` / empty (unknown — claude.ai
|
|
151
|
+
* row not yet filled). */
|
|
152
|
+
tokensIn: number | null
|
|
153
|
+
tokensOut: number | null
|
|
154
|
+
/** Cost in USD as a number. `null` for unknown. Cost depends on the
|
|
155
|
+
* adapter's PRICING table — when the model is missing from it, this is
|
|
156
|
+
* `0` from the source row, not `null` (a known limitation, not a parser
|
|
157
|
+
* bug). */
|
|
158
|
+
cost: number | null
|
|
159
|
+
/** Raw date cell — typically `YYYY-MM-DD`. Captured as a string so the
|
|
160
|
+
* model docs can evolve the format without breaking the parser. */
|
|
161
|
+
date: string
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* `sumLedger(rows)` output — pure aggregate over the rows in source order.
|
|
166
|
+
* Null cells contribute zero; the row count is preserved so a UI can show
|
|
167
|
+
* "N entries totalling …".
|
|
168
|
+
*/
|
|
169
|
+
export type LedgerTotals = {
|
|
170
|
+
tokensIn: number
|
|
171
|
+
tokensOut: number
|
|
172
|
+
cost: number
|
|
173
|
+
rows: number
|
|
174
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared code-reviewer / security-review verdict extraction (aeg-review-gate-v1
|
|
3
|
+
* task 1, #474). Originally private to `archive-task.ts`'s post-merge
|
|
4
|
+
* provenance assembly (advisory-only, DANGLING on a missing/unclear verdict).
|
|
5
|
+
* Extracted so the pre-merge `review-gate.ts` blocking check calls the
|
|
6
|
+
* IDENTICAL regex/parsing logic — a future drift between two copies of the
|
|
7
|
+
* same pattern would silently reopen the gap this task exists to close (one
|
|
8
|
+
* implementation per fact, §11 constraint). Pure — no `fs`, no `fetch`.
|
|
9
|
+
*
|
|
10
|
+
* Picks the MOST RECENT comment carrying a clear value, not the first comment
|
|
11
|
+
* merely matching the pattern (aeg-review-gate-v1 task 1 fix — the original
|
|
12
|
+
* single-comment `.find()` broke on real multi-comment PRs: a REQUEST_CHANGES
|
|
13
|
+
* verdict followed by fixes and a later clean APPROVE).
|
|
14
|
+
*
|
|
15
|
+
* Line-anchored `VERDICT:` marker only — NOT a bare word search anywhere in
|
|
16
|
+
* the comment (aeg-review-gate-v1 task 1 follow-up, security-review FAIL
|
|
17
|
+
* finding, confirmed by direct execution: the old bare `\b(PASS|FAIL)\b`
|
|
18
|
+
* pattern matched the post-merge Archivist's own auto-generated DANGLING
|
|
19
|
+
* placeholder text — "no security-review pass was run before merge —
|
|
20
|
+
* DANGLING, see below" — as a clean PASS, since it contains the standalone
|
|
21
|
+
* word "pass". Same risk in reverse for "APPROVE" inside ordinary prose,
|
|
22
|
+
* e.g. "I do NOT approve of that design"). `^\s*VERDICT:\s*<value>\b`,
|
|
23
|
+
* multiline, is the real subagent contract (`roles/reviewer.md`/
|
|
24
|
+
* `roles/security.md`'s own VERDICT block, which `.claude/agents/
|
|
25
|
+
* code-reviewer.md`/`security-reviewer.md` require verbatim) — tightening
|
|
26
|
+
* to it closes the gap without inventing a new convention.
|
|
27
|
+
*
|
|
28
|
+
* The anchor tolerates a leading markdown EMPHASIS run — one to three `*` or
|
|
29
|
+
* `_`, immediately abutting the token — so `**VERDICT: APPROVE**` and
|
|
30
|
+
* `_VERDICT: APPROVE_` match (PR #636: the reviewer subagent emitted the
|
|
31
|
+
* bolded form and the gate read the PR as carrying no code-review verdict at
|
|
32
|
+
* all). The spec still mandates the bare line; this only stops an agent's
|
|
33
|
+
* markdown drift from silently DANGLING a real verdict.
|
|
34
|
+
*
|
|
35
|
+
* Emphasis ONLY — the following are deliberately NOT tolerated, because each
|
|
36
|
+
* is a way for prose to *mention* a verdict rather than *cast* one, and this
|
|
37
|
+
* gate blocks merges (#639 review, findings 1/3/5):
|
|
38
|
+
* `> VERDICT: APPROVE` blockquote — GitHub's quote-reply syntax. A
|
|
39
|
+
* Developer quoting the reviewer's earlier text
|
|
40
|
+
* would otherwise become the PR's own verdict, and
|
|
41
|
+
* most-recent-hit-wins means the quote beats a live
|
|
42
|
+
* REQUEST CHANGES. Fail-open and silent.
|
|
43
|
+
* `* VERDICT: APPROVE` list item — the space is what distinguishes it
|
|
44
|
+
* from emphasis; a bullet is prose context. `-` and
|
|
45
|
+
* `1.` never matched, so `*` must not either.
|
|
46
|
+
* `# VERDICT: APPROVE` heading — same mention-not-cast reasoning.
|
|
47
|
+
* `` `VERDICT: APPROVE` `` code span — decided, not overlooked. A
|
|
48
|
+
* backticked marker is exactly how the role docs
|
|
49
|
+
* and this comment WRITE about the contract, so
|
|
50
|
+
* tolerating it would match prose describing the
|
|
51
|
+
* rule. If a real agent ever emits the backticked
|
|
52
|
+
* form, fix the agent: it fails loud (DANGLING),
|
|
53
|
+
* which is the safe direction.
|
|
54
|
+
*
|
|
55
|
+
* The value-side boundary is `(?![A-Za-z0-9])`, not `\b`: `_` is a word
|
|
56
|
+
* character, so `\b` after the captured value would reject the closing `_` of
|
|
57
|
+
* `_VERDICT: APPROVE_` while accepting the `*` of the bolded form — the class
|
|
58
|
+
* and the claim would disagree. It still rejects `APPROVED`/`PASSED`.
|
|
59
|
+
*
|
|
60
|
+
* What is NOT loosened is the requirement that a literal `VERDICT:` token be
|
|
61
|
+
* present — ordinary prose and the Archivist's own DANGLING placeholder still
|
|
62
|
+
* miss.
|
|
63
|
+
*
|
|
64
|
+
* No separate "marker present but value unclear" branch: a generic
|
|
65
|
+
* `VERDICT:`-prefix-only marker would itself cross-contaminate the two
|
|
66
|
+
* extractors (a security reviewer's own `VERDICT: PASS` line would loosely
|
|
67
|
+
* match the code-review marker too, misreporting "code-reviewer comment
|
|
68
|
+
* found but unclear" for a comment that was never a code review at all).
|
|
69
|
+
* Anything that doesn't match the exact value pattern reads as "missing",
|
|
70
|
+
* identical to no comment existing — which is also what the DANGLING
|
|
71
|
+
* placeholder case above requires.
|
|
72
|
+
*
|
|
73
|
+
* Reviewed-commit binding (#73, a duplicate of #71 closes this one). The
|
|
74
|
+
* winning comment (the one whose `VERDICT:` line matched) is also searched
|
|
75
|
+
* for a `Judged head: <sha>` line — the exact phrase reviewers on
|
|
76
|
+
* atta-labs/attalabs#664 were already hand-typing before this existed as a
|
|
77
|
+
* mechanism, formalized rather than invented. Same anchor discipline as
|
|
78
|
+
* `VERDICT:` itself: line-start, optional leading emphasis run, no
|
|
79
|
+
* blockquote/list-item/heading/code-span tolerance — a sha mentioned in
|
|
80
|
+
* ordinary prose ("see 8365ca57 for context") is a mention, not a binding,
|
|
81
|
+
* and must not parse as one. Accepts both the abbreviated (7-char) and full
|
|
82
|
+
* (40-char) hex forms; `review-gate.ts` does the prefix comparison against
|
|
83
|
+
* the PR's actual head. Searched only within the SAME comment body that
|
|
84
|
+
* produced the winning verdict — a sha mentioned in a different comment is
|
|
85
|
+
* not this verdict's binding.
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
const HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im
|
|
89
|
+
|
|
90
|
+
function extractHeadSha(comment: string): string | null {
|
|
91
|
+
const m = comment.match(HEAD_SHA_PATTERN)
|
|
92
|
+
return m ? (m[1] as string).toLowerCase() : null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* `headSha` is `null` in two distinct situations that both mean "cannot
|
|
97
|
+
* confirm this verdict covers the current head": no verdict comment matched
|
|
98
|
+
* at all (`danglingNote` is also set), or a verdict comment matched but
|
|
99
|
+
* carried no `Judged head:` line (`danglingNote` is `null` — the verdict
|
|
100
|
+
* itself is real, only the binding is missing). `review-gate.ts` treats both
|
|
101
|
+
* as unbound; only their `danglingNote`/`value` differ.
|
|
102
|
+
*/
|
|
103
|
+
export type VerdictExtraction = { value: string; headSha: string | null; danglingNote: string | null }
|
|
104
|
+
|
|
105
|
+
function extractVerdict(comments: string[], valuePattern: RegExp, missingLabel: string): VerdictExtraction {
|
|
106
|
+
const clearHits = comments.filter((c) => valuePattern.test(c))
|
|
107
|
+
if (clearHits.length > 0) {
|
|
108
|
+
const latest = clearHits[clearHits.length - 1] as string
|
|
109
|
+
const m = latest.match(valuePattern) as RegExpMatchArray
|
|
110
|
+
return {
|
|
111
|
+
value: (m[1] as string).toUpperCase().replace(/[_-]/g, ' '),
|
|
112
|
+
headSha: extractHeadSha(latest),
|
|
113
|
+
danglingNote: null
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
119
|
+
headSha: null,
|
|
120
|
+
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** `value` is `APPROVE`, `REQUEST CHANGES`, `LGTM`, or a DANGLING placeholder string. */
|
|
125
|
+
export function extractCodeReviewVerdict(comments: string[]): VerdictExtraction {
|
|
126
|
+
return extractVerdict(
|
|
127
|
+
comments,
|
|
128
|
+
/^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im,
|
|
129
|
+
'code-reviewer'
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** `value` is `PASS`, `FAIL`, or a DANGLING placeholder string. */
|
|
134
|
+
export function extractSecurityReviewVerdict(comments: string[]): VerdictExtraction {
|
|
135
|
+
return extractVerdict(
|
|
136
|
+
comments,
|
|
137
|
+
/^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im,
|
|
138
|
+
'security-review'
|
|
139
|
+
)
|
|
140
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vocabulary-citation.ts — a generic, regex-parameterized citation checker.
|
|
3
|
+
*
|
|
4
|
+
* `retired-vocabulary.test.ts` hand-rolls this exact sweep shape (pattern +
|
|
5
|
+
* scope + exemptions + a non-vacuity self-test) for this repo's own internal
|
|
6
|
+
* use, but as a `vitest` file no adopter can install it — and it hardcodes
|
|
7
|
+
* every pattern, scope and exemption as a repo-specific literal. This module
|
|
8
|
+
* generalizes the sweep into a reusable primitive: every pattern, its scope,
|
|
9
|
+
* its exemptions, and the sample that proves it is not vacuous arrive as
|
|
10
|
+
* caller-supplied input. It contains no product name, host-tool path, or
|
|
11
|
+
* any other repo-specific literal — that knowledge belongs to whoever
|
|
12
|
+
* configures an instance (a Vinaya check bin is one such consumer).
|
|
13
|
+
*
|
|
14
|
+
* Pure — no `fs`, no child-process I/O. The caller injects `grepFn`/
|
|
15
|
+
* `matchFn`, mirroring `evaluateC5`'s `fileExists` injection in
|
|
16
|
+
* `doc-owners.ts`: this file decides what counts as a finding, the I/O shim
|
|
17
|
+
* decides how to look.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type VocabularyPattern = {
|
|
21
|
+
/** Short, stable id for reporting — never the raw regex, which can be long. */
|
|
22
|
+
id: string
|
|
23
|
+
/**
|
|
24
|
+
* POSIX ERE, `grep -E` compatible. Never `\d`/`\w`/`\s` — GNU grep reads
|
|
25
|
+
* those as literal characters, not shorthand classes, so a pattern relying
|
|
26
|
+
* on them can pass locally (a permissive grep) and go silently vacuous on
|
|
27
|
+
* CI (GNU grep). Use `[0-9]` etc. instead. `\b` is safe: both GNU and BSD
|
|
28
|
+
* `grep -E` support it as an extension.
|
|
29
|
+
*/
|
|
30
|
+
pattern: string
|
|
31
|
+
/** Repo-root-relative paths/dirs this pattern is checked against. */
|
|
32
|
+
scope: string[]
|
|
33
|
+
/** Path substrings exempted for this pattern only, beyond `globalExempt`. */
|
|
34
|
+
exempt?: string[]
|
|
35
|
+
/**
|
|
36
|
+
* Substrings that, when present on the matched LINE, make the hit legitimate.
|
|
37
|
+
* Distinct from `exempt`, which filters by path: a name can be leakage in one
|
|
38
|
+
* sentence and the product's own identifier in the next, within the same file.
|
|
39
|
+
* An npm scope or a domain that merely CONTAINS a product name is the product
|
|
40
|
+
* naming itself, not an unexplained reference an adopter cannot resolve —
|
|
41
|
+
* exempting the whole file would blind the pattern to real leakage beside it.
|
|
42
|
+
*
|
|
43
|
+
* Granularity is the LINE, which is the accepted limit: a genuine leak that
|
|
44
|
+
* shares a line with a legitimate citation escapes. Prose puts them on
|
|
45
|
+
* separate lines in practice, and the alternative — exempting only the
|
|
46
|
+
* matched span — would need column offsets `grep -n` does not report.
|
|
47
|
+
*/
|
|
48
|
+
exemptContent?: string[]
|
|
49
|
+
/** A string this pattern MUST match — proves the pattern is not vacuous. */
|
|
50
|
+
sample: string
|
|
51
|
+
/**
|
|
52
|
+
* When true, a hit on a line that is a markdown table row (starts with
|
|
53
|
+
* `|`, ignoring leading whitespace) is not a finding. A registry table's
|
|
54
|
+
* "implementation" column citing a harness path is verified by a gate
|
|
55
|
+
* (e.g. `verify-registry.ts`'s G1/G2), not a claim stated as normative
|
|
56
|
+
* prose — banning the shape would flag the very registry a widened
|
|
57
|
+
* pattern exists to protect.
|
|
58
|
+
*/
|
|
59
|
+
exemptTableRow?: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type VocabularyHit = { file: string; line: number; content: string }
|
|
63
|
+
|
|
64
|
+
export type VocabularyFinding = VocabularyHit & { patternId: string }
|
|
65
|
+
|
|
66
|
+
export type VocabularyCheckResult = {
|
|
67
|
+
findings: VocabularyFinding[]
|
|
68
|
+
/**
|
|
69
|
+
* Patterns whose sample failed to self-match — a config bug, reported
|
|
70
|
+
* loud instead of silently passing. A pattern that matches nothing passes
|
|
71
|
+
* every scope silently, which is not hypothetical: this is the exact
|
|
72
|
+
* failure mode `retired-vocabulary.test.ts`'s own non-vacuity suite exists
|
|
73
|
+
* to catch (`\bD-(###|NNN)\b` shipped green over eleven live sites because
|
|
74
|
+
* `\b` cannot anchor after `#`).
|
|
75
|
+
*/
|
|
76
|
+
vacuousPatterns: string[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const TABLE_ROW = /^\s*\|/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Evaluates every pattern against its own scope, filtering exempted paths
|
|
83
|
+
* and (optionally) table-row citations, and reports which patterns proved
|
|
84
|
+
* vacuous against their own sample instead of silently returning clean.
|
|
85
|
+
*/
|
|
86
|
+
export function evaluateVocabularyCitation(
|
|
87
|
+
patterns: VocabularyPattern[],
|
|
88
|
+
grepFn: (pattern: string, scope: string[]) => VocabularyHit[],
|
|
89
|
+
matchFn: (pattern: string, sample: string) => boolean,
|
|
90
|
+
globalExempt: string[] = []
|
|
91
|
+
): VocabularyCheckResult {
|
|
92
|
+
const findings: VocabularyFinding[] = []
|
|
93
|
+
const vacuousPatterns: string[] = []
|
|
94
|
+
|
|
95
|
+
for (const p of patterns) {
|
|
96
|
+
if (!matchFn(p.pattern, p.sample)) {
|
|
97
|
+
vacuousPatterns.push(p.id)
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const exempt = [...globalExempt, ...(p.exempt ?? [])]
|
|
102
|
+
const exemptContent = p.exemptContent ?? []
|
|
103
|
+
const hits = grepFn(p.pattern, p.scope).filter((h) => {
|
|
104
|
+
if (exempt.some((e) => h.file.includes(e))) return false
|
|
105
|
+
if (exemptContent.some((e) => h.content.includes(e))) return false
|
|
106
|
+
if (p.exemptTableRow && TABLE_ROW.test(h.content)) return false
|
|
107
|
+
return true
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
for (const h of hits) findings.push({ ...h, patternId: p.id })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { findings, vacuousPatterns }
|
|
114
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waiver-label actor verification. A doc-coverage waiver is honored
|
|
3
|
+
* ONLY when the `vinaya/waiver:docs` PR label is present AND the actor of that
|
|
4
|
+
* label's most recent labeling timeline event is a configured principal.
|
|
5
|
+
* Label presence alone is never sufficient — that is exactly the
|
|
6
|
+
* agent-emittable-string hole closes (supersedes's
|
|
7
|
+
* `Doc-waiver:` grammar). Pure — no env reads here; I/O stays in the CLI
|
|
8
|
+
* shim, same discipline as `pr-tier.ts`.
|
|
9
|
+
*
|
|
10
|
+
* `isWaiverLabelActorVerified` is parameterized by `label` (aeg-review-gate-v1
|
|
11
|
+
* task 1, #474) so a second waiver label (`vinaya/waiver:review`) can reuse the exact
|
|
12
|
+
* same actor-verification predicate rather than a copy-pasted duplicate that
|
|
13
|
+
* could drift out of sync.
|
|
14
|
+
*
|
|
15
|
+
* The label strings themselves are read from the code-owned vocabulary in
|
|
16
|
+
* `@attalabs/aeg-forge-state`'s `labels.ts`, never written as literals here — a
|
|
17
|
+
* namespace change lands in one file (discipline).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { label as labelFor } from '@attalabs/aeg-forge-state'
|
|
21
|
+
|
|
22
|
+
export const WAIVER_LABEL = labelFor('waiver-docs')
|
|
23
|
+
export const WAIVER_LABEL_REVIEW = labelFor('waiver-review')
|
|
24
|
+
export const PRINCIPAL_ALLOWLIST = ['daniboomerang']
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Membership test for a principal allowlist. **Case-insensitive, because
|
|
28
|
+
* GitHub logins are** — `Alice` and `alice` address the same account, and the
|
|
29
|
+
* forge may return either casing depending on the API surface. An exact-match
|
|
30
|
+
* test fails closed, but it fails closed *invisibly*: an adopter who wrote
|
|
31
|
+
* `"principals": ["Alice"]` would watch Alice's genuine verdicts land as
|
|
32
|
+
* DANGLING with no stated reason — precisely the baffling symptom the
|
|
33
|
+
* `principals` field exists to remove (review finding, PR #862).
|
|
34
|
+
*
|
|
35
|
+
* Shared by every allowlist consumer so the waiver check and the review gate
|
|
36
|
+
* can never disagree about whether a given login is a principal.
|
|
37
|
+
*/
|
|
38
|
+
export function isPrincipal(login: string | null, principalAllowlist: string[]): boolean {
|
|
39
|
+
if (login === null) return false
|
|
40
|
+
const normalized = login.toLowerCase()
|
|
41
|
+
return principalAllowlist.some((p) => p.toLowerCase() === normalized)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isWaiverLabelActorVerified(opts: {
|
|
45
|
+
label: string
|
|
46
|
+
labels: string[]
|
|
47
|
+
labelActor: string | null
|
|
48
|
+
principalAllowlist: string[]
|
|
49
|
+
}): boolean {
|
|
50
|
+
if (!opts.labels.includes(opts.label)) return false
|
|
51
|
+
return isPrincipal(opts.labelActor, opts.principalAllowlist)
|
|
52
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-escape detection (task 17, Issue #78). Neither the module
|
|
3
|
+
* dependency graph nor a literal-path grep sees a filesystem reference built
|
|
4
|
+
* at runtime from a relative string — measured 2026-08-16 during attalabs'
|
|
5
|
+
* adoption of the published CLI: deleting the vendored CLI workspace looked
|
|
6
|
+
* safe by both instruments (nothing depended on it; no source file
|
|
7
|
+
* referenced it by name) and broke a sibling workspace's test suite anyway,
|
|
8
|
+
* via `readFileSync(fileURLToPath(new URL('../../cli/src/index.ts',
|
|
9
|
+
* import.meta.url)))` — not a dependency edge, not an import, and a
|
|
10
|
+
* relative specifier with no literal `apps/vinaya/cli` substring for the
|
|
11
|
+
* grep to hit.
|
|
12
|
+
*
|
|
13
|
+
* This covers the three idioms Issue #78 names: `readFileSync`/`readFile`
|
|
14
|
+
* with a literal relative-path first argument, and `new URL('…',
|
|
15
|
+
* import.meta.url)`. It does NOT follow `import`/`require` module
|
|
16
|
+
* specifiers — the dependency graph already owns those, and duplicating it
|
|
17
|
+
* here would be noise, not signal.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately static-string-only: a literal containing `${…}` (a template
|
|
20
|
+
* with interpolation) is a dynamically computed path and stays out of
|
|
21
|
+
* scope, same as any non-literal argument — resolving those needs data-flow
|
|
22
|
+
* analysis, a different and much larger job than this file (Issue #78's own
|
|
23
|
+
* stated boundary).
|
|
24
|
+
*
|
|
25
|
+
* Zero I/O: every input (file paths + contents, `knownPaths`) is read by the
|
|
26
|
+
* adapter and passed in, same discipline as `doctrine-portability.ts` and
|
|
27
|
+
* `reader-resolvable-prose.ts`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export type WorkspaceEscapeSourceFile = { path: string; content: string }
|
|
31
|
+
|
|
32
|
+
export type WorkspaceEscapeReason = 'escapes-workspace-package' | 'path-not-found'
|
|
33
|
+
|
|
34
|
+
export type WorkspaceEscapeFinding = {
|
|
35
|
+
file: string
|
|
36
|
+
line: number
|
|
37
|
+
reference: string
|
|
38
|
+
resolved: string
|
|
39
|
+
reason: WorkspaceEscapeReason
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** `apps/*`/`packages/*` — this repo's own `package.json` `workspaces` globs. A caller with a different layout passes its own. */
|
|
43
|
+
const DEFAULT_WORKSPACE_DIRS: readonly string[] = ['apps', 'packages']
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* `readFileSync('./x')`/`readFile('../x')` — literal first argument,
|
|
47
|
+
* required to start `./` or `../` (the only shape found anywhere in this
|
|
48
|
+
* repo's own corpus; a bare non-relative literal like `'utf8'` is always
|
|
49
|
+
* the SECOND argument to these calls, never the first, so requiring the
|
|
50
|
+
* relative prefix costs no real coverage while ruling out an accidental
|
|
51
|
+
* match on an unrelated literal).
|
|
52
|
+
*/
|
|
53
|
+
const READ_CALL = /\b(?:readFileSync|readFile)\s*\(\s*(['"`])((?:\.\.?\/)[^'"`]*)\1/g
|
|
54
|
+
|
|
55
|
+
/** `new URL('…', import.meta.url)` — the second argument must be the literal expression, not a variable holding it. */
|
|
56
|
+
const NEW_URL_IMPORT_META_URL = /\bnew\s+URL\s*\(\s*(['"`])([^'"`]*)\1\s*,\s*import\.meta\.url\s*\)/g
|
|
57
|
+
|
|
58
|
+
function lineAtOffset(content: string, index: number): number {
|
|
59
|
+
let line = 1
|
|
60
|
+
for (let i = 0; i < index; i++) {
|
|
61
|
+
if (content.charCodeAt(i) === 10) line++
|
|
62
|
+
}
|
|
63
|
+
return line
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Repo-relative POSIX join + `.`/`..` normalization. No `node:path` — every
|
|
68
|
+
* input here is already POSIX (repo-relative, forward-slash), so there is
|
|
69
|
+
* no platform-separator handling to delegate, and this package's other pure
|
|
70
|
+
* scanners (`doctrine-portability.ts`, `reader-resolvable-prose.ts`) carry
|
|
71
|
+
* no filesystem-module dependency either.
|
|
72
|
+
*/
|
|
73
|
+
function resolveRelative(fromDir: string, ref: string): string {
|
|
74
|
+
const stack = fromDir === '' ? [] : fromDir.split('/')
|
|
75
|
+
for (const segment of ref.split('/')) {
|
|
76
|
+
if (segment === '' || segment === '.') continue
|
|
77
|
+
if (segment === '..') stack.pop()
|
|
78
|
+
else stack.push(segment)
|
|
79
|
+
}
|
|
80
|
+
return stack.join('/')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function dirOf(path: string): string {
|
|
84
|
+
const idx = path.lastIndexOf('/')
|
|
85
|
+
return idx === -1 ? '' : path.slice(0, idx)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The workspace package a repo-relative path belongs to — its first two segments when it sits under one of `workspaceDirs` (`apps/<name>`, `packages/<name>`); `null` for a repo-root file, `aeg-root/**`, or anything that normalizes above the repo root entirely. */
|
|
89
|
+
function workspacePackageOf(path: string, workspaceDirs: readonly string[]): string | null {
|
|
90
|
+
const [first, second] = path.split('/')
|
|
91
|
+
if (first === undefined || second === undefined || !workspaceDirs.includes(first)) return null
|
|
92
|
+
return `${first}/${second}`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Blanks out `/* *\/` and `//` comments, preserving every newline so
|
|
97
|
+
* reported line numbers stay accurate against the original file — same
|
|
98
|
+
* technique as `reader-resolvable-prose.ts`'s `stripNonProse` for `.ts`/
|
|
99
|
+
* `.tsx`. Without this, a module comment merely describing a call shape in
|
|
100
|
+
* prose (this very file's own header does) reads as a real call site.
|
|
101
|
+
*/
|
|
102
|
+
function stripComments(content: string): string {
|
|
103
|
+
return content
|
|
104
|
+
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ''))
|
|
105
|
+
.replace(/(^|[^:])\/\/[^\n]*/g, (m) => m.replace(/[^\n]/g, ''))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type ExtractedReference = { line: number; reference: string; resolved: string }
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Both idioms scanned by raw pattern, not a parser — same syntactic
|
|
112
|
+
* discipline as `symbol-collisions.ts`'s declaration scan. KNOWN BLIND SPOT,
|
|
113
|
+
* stated rather than papered over: a match inside a STRING (as opposed to a
|
|
114
|
+
* comment, stripped above) reads as code — e.g. this module's own test
|
|
115
|
+
* fixtures, which embed example call shapes as string literals for the
|
|
116
|
+
* regex to parse, are themselves indistinguishable from real call sites by
|
|
117
|
+
* a text scan. Resolving that needs a real parser, the same larger job
|
|
118
|
+
* `symbol-collisions.ts` declines for the identical reason.
|
|
119
|
+
*/
|
|
120
|
+
function extractReferences(path: string, content: string): ExtractedReference[] {
|
|
121
|
+
const dir = dirOf(path)
|
|
122
|
+
const scrubbed = stripComments(content)
|
|
123
|
+
const out: ExtractedReference[] = []
|
|
124
|
+
for (const pattern of [READ_CALL, NEW_URL_IMPORT_META_URL]) {
|
|
125
|
+
pattern.lastIndex = 0
|
|
126
|
+
let m: RegExpExecArray | null = pattern.exec(scrubbed)
|
|
127
|
+
while (m !== null) {
|
|
128
|
+
const literal = m[2] as string
|
|
129
|
+
if (!literal.includes('${')) {
|
|
130
|
+
out.push({ line: lineAtOffset(scrubbed, m.index), reference: literal, resolved: resolveRelative(dir, literal) })
|
|
131
|
+
}
|
|
132
|
+
m = pattern.exec(scrubbed)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return out
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Static filesystem references that either resolve outside the citing
|
|
140
|
+
* file's own workspace package, or to a repo path that does not exist at
|
|
141
|
+
* all. `knownPaths` is every repo-relative path that exists on disk,
|
|
142
|
+
* collected by the adapter — this module never touches the filesystem.
|
|
143
|
+
* `workspaceDirs` defaults to this repo's own `apps`/`packages` layout; a
|
|
144
|
+
* file outside every workspace dir is skipped entirely (there is no "own
|
|
145
|
+
* package" for it to escape).
|
|
146
|
+
*/
|
|
147
|
+
export function findWorkspaceEscapes(
|
|
148
|
+
files: readonly WorkspaceEscapeSourceFile[],
|
|
149
|
+
knownPaths: ReadonlySet<string>,
|
|
150
|
+
workspaceDirs: readonly string[] = DEFAULT_WORKSPACE_DIRS
|
|
151
|
+
): WorkspaceEscapeFinding[] {
|
|
152
|
+
const findings: WorkspaceEscapeFinding[] = []
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
const own = workspacePackageOf(file.path, workspaceDirs)
|
|
155
|
+
if (own === null) continue
|
|
156
|
+
for (const ref of extractReferences(file.path, file.content)) {
|
|
157
|
+
const target = workspacePackageOf(ref.resolved, workspaceDirs)
|
|
158
|
+
const escapes = target !== own
|
|
159
|
+
const missing = !knownPaths.has(ref.resolved)
|
|
160
|
+
if (escapes || missing) {
|
|
161
|
+
findings.push({
|
|
162
|
+
file: file.path,
|
|
163
|
+
line: ref.line,
|
|
164
|
+
reference: ref.reference,
|
|
165
|
+
resolved: ref.resolved,
|
|
166
|
+
reason: escapes ? 'escapes-workspace-package' : 'path-not-found'
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return findings
|
|
172
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5xy_GA7G5KRuE3HDumIiw
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
"static/chunks/0cz1d0mv5g_q7.js"
|
|
8
8
|
],
|
|
9
9
|
"lowPriorityFiles": [
|
|
10
|
-
"static/
|
|
11
|
-
"static/
|
|
12
|
-
"static/
|
|
10
|
+
"static/5xy_GA7G5KRuE3HDumIiw/_buildManifest.js",
|
|
11
|
+
"static/5xy_GA7G5KRuE3HDumIiw/_ssgManifest.js",
|
|
12
|
+
"static/5xy_GA7G5KRuE3HDumIiw/_clientMiddlewareManifest.js"
|
|
13
13
|
],
|
|
14
14
|
"rootMainFiles": [
|
|
15
15
|
"static/chunks/1hklle-my0ly_.js",
|