@attalabs/vinaya 0.22.0 → 0.24.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 +14 -3
- 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 +26 -20
- 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 +1 -1
- 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 +10 -7
- package/aeg-root/tranche-model.md +6 -2
- package/dist/checks/bin/check-body-bare-digits.js +559 -140
- package/dist/checks/bin/check-branch-topology.js +630 -145
- package/dist/checks/bin/check-brief-shape.js +600 -143
- package/dist/checks/bin/check-changeset-coverage.js +942 -162
- package/dist/checks/bin/check-closes-n.js +630 -145
- package/dist/checks/bin/check-coherence.js +641 -148
- package/dist/checks/bin/check-dead-branch-push.js +559 -140
- package/dist/checks/bin/check-dispatch-readiness.js +639 -146
- package/dist/checks/bin/check-doc-coverage-push.js +696 -219
- package/dist/checks/bin/check-doc-coverage.js +696 -219
- package/dist/checks/bin/check-doctrine-no-procedures.js +4800 -0
- package/dist/checks/bin/check-doctrine-portability.js +691 -147
- package/dist/checks/bin/check-evidence-fresh.js +1048 -222
- package/dist/checks/bin/check-exec-bits.js +4937 -0
- package/dist/checks/bin/check-first-push-dispatch.js +630 -145
- package/dist/checks/bin/check-issue-assignment.js +630 -145
- package/dist/checks/bin/check-main-branch-refusal.js +565 -141
- package/dist/checks/bin/check-no-disk-state.js +559 -140
- package/dist/checks/bin/check-pr-report-density.js +4467 -0
- package/dist/checks/bin/check-quoted-command.js +656 -164
- package/dist/checks/bin/check-reader-resolvable-prose.js +652 -163
- package/dist/checks/bin/check-registry-gates.js +602 -145
- package/dist/checks/bin/check-retired-vocabulary.js +652 -163
- package/dist/checks/bin/check-review-gate.js +677 -142
- package/dist/checks/bin/check-single-plan-pr.js +559 -140
- package/dist/checks/bin/check-test-plan.js +560 -141
- package/dist/checks/bin/check-token-collection-wired.js +559 -140
- package/dist/checks/bin/check-token-report.js +559 -140
- package/dist/checks/bin/check-workspace-escape.js +992 -146
- package/dist/index.js +8884 -5627
- 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 +118 -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 +198 -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-render.ts +462 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-validation.ts +912 -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 +806 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/consumer-enumeration.ts +75 -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 +268 -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-no-procedures.ts +94 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-portability.ts +293 -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 +171 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/index.ts +323 -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 +177 -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-report-density.ts +116 -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 +160 -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 +378 -0
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-status.ts +200 -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 +117 -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 +199 -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]__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]__069-_41._.js → [root-of-the-server]__1pndh3_._.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/_1fqw88f._.js +2 -2
- 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_projects_[name]_tranches_[slug]_05ufo8i._.js +3 -3
- 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/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/{37hnluhzj9bei.js → 0zebsmmk1bvnb.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{267o-tsrnuwnv.js → 1uogpj5w2n5ju.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1hufpuody6vjv.js → 3m1kgax7j2vgs.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
- /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `checkConsumerTests`'s consumer enumeration (`brief-validation.ts`, task 10,
|
|
3
|
+
* Issue #385; corrected by the round-2 ruling item 3) — every workspace
|
|
4
|
+
* member whose own `package.json` depends on `@attalabs/<pkg>`, for "every
|
|
5
|
+
* workspace package depending on `@attalabs/<pkg>`" to mean what it says.
|
|
6
|
+
*
|
|
7
|
+
* The first cut reused `deriveWorkspacePackageDomains` (`blast-radius-domains.ts`),
|
|
8
|
+
* which deliberately collapses to `packages/*` members only — correct for
|
|
9
|
+
* that module's own collision-domain purpose, wrong here: it silently
|
|
10
|
+
* exempted `apps/cli`, a real `@attalabs/aeg-core` consumer, from a rule
|
|
11
|
+
* that promises "every workspace package" (found live reviewing this PR's
|
|
12
|
+
* own diff). This module resolves every workspace glob instead —
|
|
13
|
+
* `apps/*` and `packages/*` alike — via `resolveWorkspaceEntry`, the same
|
|
14
|
+
* glob grammar `deriveWorkspacePackageDomains` uses, reused rather than
|
|
15
|
+
* re-derived a second time.
|
|
16
|
+
*
|
|
17
|
+
* Zero I/O: `listDirs`/`readManifest` are injected by each of the two real
|
|
18
|
+
* entry points — `packages/aeg-core/bin/verify-brief.ts` (authoring-time,
|
|
19
|
+
* pre-dispatch) and `apps/cli`'s `check-brief-shape.ts` (CI) — so both share
|
|
20
|
+
* ONE enumeration rather than two independently-maintained copies that can
|
|
21
|
+
* silently diverge (round-2 ruling item 4).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { resolveWorkspaceEntry } from './blast-radius-domains'
|
|
25
|
+
|
|
26
|
+
export type PackageManifest = { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Every workspace member directory (`apps/cli`, `packages/aeg-core`, …),
|
|
30
|
+
* resolved from `workspaces` (positive entries minus any `!`-negated ones,
|
|
31
|
+
* same semantics as `deriveWorkspacePackageDomains`) — but NOT collapsed to
|
|
32
|
+
* `packages/*` only, since a real consumer can live anywhere in the
|
|
33
|
+
* workspace.
|
|
34
|
+
*/
|
|
35
|
+
export function deriveWorkspaceMemberDirs(workspaces: string[], listDirs: (dir: string) => string[]): string[] {
|
|
36
|
+
const positive = workspaces.filter((entry) => !entry.startsWith('!'))
|
|
37
|
+
const negative = workspaces.filter((entry) => entry.startsWith('!')).map((entry) => entry.slice(1))
|
|
38
|
+
|
|
39
|
+
const excluded = new Set<string>()
|
|
40
|
+
for (const entry of negative) {
|
|
41
|
+
for (const member of resolveWorkspaceEntry(entry, listDirs)) excluded.add(member)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const members = new Set<string>()
|
|
45
|
+
for (const entry of positive) {
|
|
46
|
+
for (const member of resolveWorkspaceEntry(entry, listDirs)) {
|
|
47
|
+
if (!excluded.has(member)) members.add(member)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [...members].sort()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Builds `checkConsumerTests`'s `consumersOf` callback: for a package `pkg`
|
|
55
|
+
* (as named in a brief's `§4`, e.g. `packages/aeg-core/` → `'aeg-core'`),
|
|
56
|
+
* every workspace member directory (self excluded) whose own manifest lists
|
|
57
|
+
* `@attalabs/<pkg>` in `dependencies` or `devDependencies`.
|
|
58
|
+
*/
|
|
59
|
+
export function buildConsumersOf(
|
|
60
|
+
workspaces: string[],
|
|
61
|
+
listDirs: (dir: string) => string[],
|
|
62
|
+
readManifest: (dir: string) => PackageManifest | null
|
|
63
|
+
): (pkg: string) => string[] {
|
|
64
|
+
const members = deriveWorkspaceMemberDirs(workspaces, listDirs)
|
|
65
|
+
|
|
66
|
+
return (pkg: string): string[] => {
|
|
67
|
+
const target = `@attalabs/${pkg}`
|
|
68
|
+
return members.filter((dir) => {
|
|
69
|
+
if (dir === `packages/${pkg}`) return false
|
|
70
|
+
const manifest = readManifest(dir)
|
|
71
|
+
if (!manifest) return false
|
|
72
|
+
return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {})
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead-branch-push AUDIT (aeg-governance-hardening task 24, #364, Part 4 —
|
|
3
|
+
* item 2). Distinct from the ring-0 `dead-branch-push-guard.ts` (task 18):
|
|
4
|
+
* that predicate answers "should THIS push be allowed right now?" at push
|
|
5
|
+
* time; this one answers "did commits land on a branch AFTER its PR already
|
|
6
|
+
* resolved?" retrospectively, across every remote `task/*` branch — the
|
|
7
|
+
* ring-2 backstop for the class the ring-0 hook is fail-open/hook-dependent
|
|
8
|
+
* against (a writer without the hook installed, or a hook bypass). Pure —
|
|
9
|
+
* no `fs`, no `gh`/`git` shell-outs. The CLI shim (`bin/dead-branch-audit.ts`,
|
|
10
|
+
* joining task 23's `daily-drift` job) gathers each branch's most recent
|
|
11
|
+
* PR (any state) plus its resolution/latest-commit timestamps and passes
|
|
12
|
+
* them in here.
|
|
13
|
+
*
|
|
14
|
+
* Exists to catch the "six topology rows landed on a merged plan PR's
|
|
15
|
+
* branch" incident class (2026-07-03) server-side, within a day, instead of
|
|
16
|
+
* never — never-red discipline: `daily-drift` (the CI job that calls this)
|
|
17
|
+
* must never fail red.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type DeadBranchFact = {
|
|
21
|
+
branch: string
|
|
22
|
+
prNumber: number
|
|
23
|
+
prState: 'MERGED' | 'CLOSED'
|
|
24
|
+
/** ISO timestamp the branch's PR was merged (MERGED) or closed (CLOSED). */
|
|
25
|
+
resolvedAt: string
|
|
26
|
+
/** ISO commit date of the branch's current remote tip. */
|
|
27
|
+
latestCommitAt: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type DeadBranchPush = {
|
|
31
|
+
branch: string
|
|
32
|
+
prNumber: number
|
|
33
|
+
prState: 'MERGED' | 'CLOSED'
|
|
34
|
+
resolvedAt: string
|
|
35
|
+
latestCommitAt: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Flags a branch whose current tip commit is strictly newer than its own
|
|
40
|
+
* PR's resolution time — i.e. commits kept landing on a branch after the
|
|
41
|
+
* forge already considered its work done. A branch whose tip predates (or
|
|
42
|
+
* exactly matches) its PR's resolution is normal: the branch simply hasn't
|
|
43
|
+
* been deleted yet, which is not itself a violation.
|
|
44
|
+
*/
|
|
45
|
+
export function findDeadBranchPushes(facts: DeadBranchFact[]): DeadBranchPush[] {
|
|
46
|
+
return facts
|
|
47
|
+
.filter((f) => new Date(f.latestCommitAt).getTime() > new Date(f.resolvedAt).getTime())
|
|
48
|
+
.map(({ branch, prNumber, prState, resolvedAt, latestCommitAt }) => ({
|
|
49
|
+
branch,
|
|
50
|
+
prNumber,
|
|
51
|
+
prState,
|
|
52
|
+
resolvedAt,
|
|
53
|
+
latestCommitAt
|
|
54
|
+
}))
|
|
55
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead-branch push guard (aeg-governance-hardening task 18, #335). Pure — no
|
|
3
|
+
* `fs`, no `gh`/`git` shell-outs. The CLI shim (`bin/check-push-target.ts`)
|
|
4
|
+
* makes the one batched forge call (`gh pr list --head <branch> --state all
|
|
5
|
+
* --json number,state --limit 1`), maps its result to a `PrStateFact`, and
|
|
6
|
+
* passes it in here.
|
|
7
|
+
*
|
|
8
|
+
* Exists to answer, deterministically, "is it safe to push more commits to
|
|
9
|
+
* this `task/<tranche>/<n>` branch?" — closing the live-fire incident
|
|
10
|
+
* class where stale local work landed on a branch whose PR had already
|
|
11
|
+
* merged, silently reopening/confusing forge state with no push-time signal.
|
|
12
|
+
*
|
|
13
|
+
* `UNKNOWN` (forge unreachable — network flake, auth issue, rate limit) maps
|
|
14
|
+
* to `allow`, the same as `NONE` (no PR exists yet — a brand-new branch
|
|
15
|
+
* legitimately has none). This is a deliberate fail-open choice: a guard
|
|
16
|
+
* that can block *all* pushes on a transient forge-reachability issue is
|
|
17
|
+
* worse than the bug it fixes. See `aeg-root/enforcement.md`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type PrStateFact = 'MERGED' | 'CLOSED' | 'OPEN' | 'NONE' | 'UNKNOWN'
|
|
21
|
+
|
|
22
|
+
export type DeadBranchPushInput = {
|
|
23
|
+
/** The `task/<tranche>/<n>` branch being pushed. */
|
|
24
|
+
branch: string
|
|
25
|
+
/** State of the branch's most recent PR (any state), or the fact that none/forge-unreachable applies. */
|
|
26
|
+
prState: PrStateFact
|
|
27
|
+
/** The PR number that `prState` refers to, or `null` when no PR exists / could not be determined. */
|
|
28
|
+
prNumber: number | null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type DeadBranchPushVerdict = 'allow' | 'refuse'
|
|
32
|
+
|
|
33
|
+
export type DeadBranchPushResult = { verdict: DeadBranchPushVerdict; reason: string }
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `allow` — `OPEN`/`NONE`/`UNKNOWN`: an in-flight PR, no PR yet, or the
|
|
37
|
+
* forge couldn't be reached — all legitimate reasons to push.
|
|
38
|
+
* `refuse` — `MERGED`/`CLOSED`: the branch's most recent PR is already
|
|
39
|
+
* resolved. Pushing here would silently resurrect resolved forge
|
|
40
|
+
* state instead of starting a fresh branch.
|
|
41
|
+
*/
|
|
42
|
+
export function checkDeadBranchPush(input: DeadBranchPushInput): DeadBranchPushResult {
|
|
43
|
+
const { branch, prState, prNumber } = input
|
|
44
|
+
|
|
45
|
+
if (prState === 'MERGED') {
|
|
46
|
+
return {
|
|
47
|
+
verdict: 'refuse',
|
|
48
|
+
reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (prState === 'CLOSED') {
|
|
53
|
+
return {
|
|
54
|
+
verdict: 'refuse',
|
|
55
|
+
reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (prState === 'OPEN') {
|
|
60
|
+
return {
|
|
61
|
+
verdict: 'allow',
|
|
62
|
+
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (prState === 'NONE') {
|
|
67
|
+
return {
|
|
68
|
+
verdict: 'allow',
|
|
69
|
+
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
verdict: 'allow',
|
|
75
|
+
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive the floor of a task's §7 doc-update list by matching its intended
|
|
3
|
+
* surface globs against `.vinaya/doc-owners` bindings (state-machine.md
|
|
4
|
+
* Section 15). Pure — parses doc-owners content via `parseDocOwners` (no
|
|
5
|
+
* second parser) and does segment-wise glob overlap between each intended
|
|
6
|
+
* surface and each binding's code glob. A Planner/Brief-Author aid only —
|
|
7
|
+
* not wired into any CI gate.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { parseDocOwners } from './doc-owners'
|
|
11
|
+
|
|
12
|
+
export type Section7Match = { surface: string; glob: string; pointer: string; lineNum: number }
|
|
13
|
+
|
|
14
|
+
export function deriveSection7(
|
|
15
|
+
intendedSurfaces: string[],
|
|
16
|
+
docOwnersContent: string
|
|
17
|
+
): { pointers: string[]; matches: Section7Match[]; errors: string[] } {
|
|
18
|
+
const { bindings, errors } = parseDocOwners(docOwnersContent)
|
|
19
|
+
const matches: Section7Match[] = []
|
|
20
|
+
for (const surface of intendedSurfaces) {
|
|
21
|
+
for (const binding of bindings) {
|
|
22
|
+
if (globsOverlap(binding.glob, surface)) {
|
|
23
|
+
matches.push({ surface, glob: binding.glob, pointer: binding.pointer, lineNum: binding.lineNum })
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const pointers: string[] = []
|
|
29
|
+
for (const m of matches) {
|
|
30
|
+
if (!pointers.includes(m.pointer)) pointers.push(m.pointer)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { pointers, matches, errors }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* True if two doc-owners-grammar globs can match at least one common path,
|
|
38
|
+
* compared segment-by-segment (split on `/`). `**` absorbs zero or more
|
|
39
|
+
* remaining segments on either side — checked at each step, even once one
|
|
40
|
+
* side has run out of segments, so a trailing `**` still absorbs. `*`
|
|
41
|
+
* matches exactly one segment regardless of the other side's literal/`*`.
|
|
42
|
+
* Two literal segments must be byte-equal. If one side runs out of segments
|
|
43
|
+
* before the other and the next pending segment on either side isn't `**`,
|
|
44
|
+
* there is no overlap.
|
|
45
|
+
*/
|
|
46
|
+
export function globsOverlap(a: string, b: string): boolean {
|
|
47
|
+
const segsA = a.split('/')
|
|
48
|
+
const segsB = b.split('/')
|
|
49
|
+
let i = 0
|
|
50
|
+
let j = 0
|
|
51
|
+
while (i < segsA.length || j < segsB.length) {
|
|
52
|
+
const sa = i < segsA.length ? segsA[i] : undefined
|
|
53
|
+
const sb = j < segsB.length ? segsB[j] : undefined
|
|
54
|
+
if (sa === '**' || sb === '**') return true
|
|
55
|
+
if (sa === undefined || sb === undefined) return false
|
|
56
|
+
if (sa === '*' || sb === '*') {
|
|
57
|
+
i++
|
|
58
|
+
j++
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
if (sa !== sb) return false
|
|
62
|
+
i++
|
|
63
|
+
j++
|
|
64
|
+
}
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { deriveStatusFromModel } from './state-machine-model'
|
|
2
|
+
import type { DerivedTranche, DerivedStatus, DerivedTask, ForgeFacts, Tranche, Task, UnknownEdge } from './types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compute each task's derived status, the resolved depends-on / conflicts-with
|
|
6
|
+
* graph, and dispatch-eligibility per `tranche-model.md` §3 (status table)
|
|
7
|
+
* and §8 (dispatch gates).
|
|
8
|
+
*
|
|
9
|
+
* The function is pure: `tranche` + `forgeFacts` in, `DerivedTranche` out.
|
|
10
|
+
* Edges referencing task ids not present in this tranche's table are
|
|
11
|
+
* reported via `unknownEdges` rather than throwing — see §4 traps in the brief
|
|
12
|
+
* (real files reference dropped/prose-only ids in narrative).
|
|
13
|
+
*/
|
|
14
|
+
export function deriveTranche(tranche: Tranche, forgeFacts: Map<string, ForgeFacts>): DerivedTranche {
|
|
15
|
+
const byId = new Map<string, Task>()
|
|
16
|
+
for (const task of tranche.tasks) byId.set(task.id, task)
|
|
17
|
+
|
|
18
|
+
// First pass: derive each task's own status.
|
|
19
|
+
const statuses = new Map<string, DerivedStatus>()
|
|
20
|
+
for (const task of tranche.tasks) {
|
|
21
|
+
statuses.set(task.id, deriveStatus(forgeFacts.get(task.id)))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Second pass: dispatch eligibility, which references siblings' derived
|
|
25
|
+
// statuses (so it must come after the first pass).
|
|
26
|
+
const unknownEdges: UnknownEdge[] = []
|
|
27
|
+
const derived: DerivedTask[] = []
|
|
28
|
+
for (const task of tranche.tasks) {
|
|
29
|
+
const status = statuses.get(task.id) ?? 'todo'
|
|
30
|
+
const dependsOnNotMerged: string[] = []
|
|
31
|
+
for (const dep of task.dependsOn) {
|
|
32
|
+
if (!byId.has(dep)) {
|
|
33
|
+
unknownEdges.push({ from: task.id, to: dep, kind: 'depends-on' })
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
if (statuses.get(dep) !== 'merged') dependsOnNotMerged.push(dep)
|
|
37
|
+
}
|
|
38
|
+
const conflictsWithOpenOrInFlight: string[] = []
|
|
39
|
+
for (const sib of task.conflictsWith) {
|
|
40
|
+
if (!byId.has(sib)) {
|
|
41
|
+
unknownEdges.push({ from: task.id, to: sib, kind: 'conflicts-with' })
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
const sibStatus = statuses.get(sib)
|
|
45
|
+
if (sibStatus === 'in-flight' || sibStatus === 'in-review' || sibStatus === 'changes-requested') {
|
|
46
|
+
conflictsWithOpenOrInFlight.push(sib)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
derived.push({
|
|
50
|
+
task,
|
|
51
|
+
status,
|
|
52
|
+
dispatchable: dependsOnNotMerged.length === 0 && conflictsWithOpenOrInFlight.length === 0,
|
|
53
|
+
blockers: { dependsOnNotMerged, conflictsWithOpenOrInFlight }
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { tranche, tasks: derived, unknownEdges }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Map a single task's forge facts to its derived status. Mirrors the §3 table
|
|
62
|
+
*: `blocked` wins over everything else, then `merged`, then
|
|
63
|
+
* `changes-requested`, then `in-review`, then `in-flight`, then `todo` for the
|
|
64
|
+
* open-issue cases (assigned or not — both are `todo` inside a tranche per
|
|
65
|
+
*), then the honest terminal cases for a closed-without-merge Issue.
|
|
66
|
+
*
|
|
67
|
+
* Reopened-after-merge exception: a reopened Issue can carry a stale
|
|
68
|
+
* `prState: 'merged'` fact from a closing PR that predates the reopen —
|
|
69
|
+
* `issueState` and `prState` are independently sourced, and GitHub does not
|
|
70
|
+
* retract a PR's merge history just because the Issue reopened. Without this
|
|
71
|
+
* check, such a task would permanently display `merged` even though the work
|
|
72
|
+
* it names was never actually done. Checked before the `prState === 'merged'`
|
|
73
|
+
* branch below so a currently-open Issue always wins over a historical merge.
|
|
74
|
+
*
|
|
75
|
+
* Honest terminal derivation: a closed Issue with no merged PR must
|
|
76
|
+
* never resolve to `todo` (which would imply not-started — a lie). It reads
|
|
77
|
+
* GitHub's native `stateReason`: closed `NOT_PLANNED` → `dropped` (legitimately
|
|
78
|
+
* abandoned); anything else (closed `COMPLETED` or no reason recorded) →
|
|
79
|
+
* `incoherent` (genuinely done but unprovable, or a broken close that the
|
|
80
|
+
* `Closes #N` law was built to surface).
|
|
81
|
+
*
|
|
82
|
+
* A missing `forgeFacts` entry → `todo`. Tranche tasks are committed work;
|
|
83
|
+
* `backlog` is a project-level concept only and is never emitted here.
|
|
84
|
+
*
|
|
85
|
+
* The rules themselves are no longer written here. They live as an ordered,
|
|
86
|
+
* pure-data list in `state-machine-model.ts` (the `actions.ts`
|
|
87
|
+
* discipline): one list, executed by this function and rendered by the docs,
|
|
88
|
+
* so the two cannot drift. Each rule carries the prose and the reason its
|
|
89
|
+
* position matters — change the order there, and this function's behavior
|
|
90
|
+
* changes with it.
|
|
91
|
+
*/
|
|
92
|
+
function deriveStatus(facts: ForgeFacts | undefined): DerivedStatus {
|
|
93
|
+
return deriveStatusFromModel(facts)
|
|
94
|
+
}
|