@attalabs/vinaya 0.22.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 +13 -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 +552 -139
- package/dist/checks/bin/check-branch-topology.js +603 -144
- package/dist/checks/bin/check-brief-shape.js +593 -142
- package/dist/checks/bin/check-changeset-coverage.js +935 -161
- package/dist/checks/bin/check-closes-n.js +603 -144
- package/dist/checks/bin/check-coherence.js +614 -147
- package/dist/checks/bin/check-dead-branch-push.js +552 -139
- package/dist/checks/bin/check-dispatch-readiness.js +612 -145
- package/dist/checks/bin/check-doc-coverage-push.js +689 -218
- package/dist/checks/bin/check-doc-coverage.js +689 -218
- package/dist/checks/bin/check-doctrine-no-procedures.js +4794 -0
- package/dist/checks/bin/check-doctrine-portability.js +684 -146
- package/dist/checks/bin/check-evidence-fresh.js +994 -174
- package/dist/checks/bin/check-exec-bits.js +4931 -0
- package/dist/checks/bin/check-first-push-dispatch.js +603 -144
- package/dist/checks/bin/check-issue-assignment.js +603 -144
- package/dist/checks/bin/check-main-branch-refusal.js +552 -139
- package/dist/checks/bin/check-no-disk-state.js +552 -139
- package/dist/checks/bin/check-pr-report-density.js +4461 -0
- package/dist/checks/bin/check-quoted-command.js +621 -155
- package/dist/checks/bin/check-reader-resolvable-prose.js +617 -154
- package/dist/checks/bin/check-registry-gates.js +595 -144
- package/dist/checks/bin/check-retired-vocabulary.js +617 -154
- package/dist/checks/bin/check-review-gate.js +670 -141
- package/dist/checks/bin/check-single-plan-pr.js +552 -139
- package/dist/checks/bin/check-test-plan.js +553 -140
- package/dist/checks/bin/check-token-collection-wired.js +552 -139
- package/dist/checks/bin/check-token-report.js +552 -139
- package/dist/checks/bin/check-workspace-escape.js +985 -145
- package/dist/index.js +8500 -5518
- 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]__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]__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/_1fqw88f._.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/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 → 0jer_drkl1e4r.js} +4 -4
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{267o-tsrnuwnv.js → 20x-t0a0v129z.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1hufpuody6vjv.js → 2ou_81ahqw31e.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/aeg-core}/bin/verify-coherence.ts +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
2
4
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
3
5
|
var GATE_AUDIENCE = {
|
|
4
6
|
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
@@ -207,17 +209,18 @@ function splitIds(raw) {
|
|
|
207
209
|
return [];
|
|
208
210
|
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !isEmptyMarker(s) && ID_TOKEN.test(s));
|
|
209
211
|
}
|
|
210
|
-
function resolveIds(raw
|
|
212
|
+
function resolveIds(raw) {
|
|
211
213
|
const ids = splitIds(raw);
|
|
212
214
|
const resolved = [];
|
|
215
|
+
let lastSlug = null;
|
|
213
216
|
for (const id of ids) {
|
|
214
217
|
const qualified = id.match(SLUG_QUALIFIED_ID);
|
|
215
218
|
if (qualified) {
|
|
216
|
-
lastSlug
|
|
219
|
+
lastSlug = qualified[1] ?? null;
|
|
217
220
|
resolved.push(id);
|
|
218
|
-
} else if (lastSlug
|
|
219
|
-
resolved.push(`${lastSlug
|
|
220
|
-
lastSlug
|
|
221
|
+
} else if (lastSlug) {
|
|
222
|
+
resolved.push(`${lastSlug} ${id}`);
|
|
223
|
+
lastSlug = null;
|
|
221
224
|
} else {
|
|
222
225
|
resolved.push(id);
|
|
223
226
|
}
|
|
@@ -237,12 +240,7 @@ function parseRationaleDeps(body) {
|
|
|
237
240
|
const section = extractSection(body);
|
|
238
241
|
const dependsOn = [];
|
|
239
242
|
const conflictsWith = [];
|
|
240
|
-
let current = null;
|
|
241
243
|
const assignedFields = new Set;
|
|
242
|
-
const lastSlugByField = {
|
|
243
|
-
dependsOn: { current: null },
|
|
244
|
-
conflictsWith: { current: null }
|
|
245
|
-
};
|
|
246
244
|
const spanPattern = /`([^`]*)`/g;
|
|
247
245
|
let match = spanPattern.exec(section);
|
|
248
246
|
while (match !== null) {
|
|
@@ -252,13 +250,9 @@ function parseRationaleDeps(body) {
|
|
|
252
250
|
const field = labelMatch[1]?.toLowerCase() === "conflicts-with" ? "conflictsWith" : "dependsOn";
|
|
253
251
|
if (!assignedFields.has(field)) {
|
|
254
252
|
assignedFields.add(field);
|
|
255
|
-
|
|
256
|
-
const ids = resolveIds(labelMatch[2] ?? "", lastSlugByField[field]);
|
|
253
|
+
const ids = resolveIds(labelMatch[2] ?? "");
|
|
257
254
|
pushUnique(field === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
258
255
|
}
|
|
259
|
-
} else if (current) {
|
|
260
|
-
const ids = resolveIds(content, lastSlugByField[current]);
|
|
261
|
-
pushUnique(current === "dependsOn" ? dependsOn : conflictsWith, ids);
|
|
262
256
|
}
|
|
263
257
|
match = spanPattern.exec(section);
|
|
264
258
|
}
|
|
@@ -1723,6 +1717,48 @@ function splitTableRow(row) {
|
|
|
1723
1717
|
parts.pop();
|
|
1724
1718
|
return parts.map((p) => p.split(PLACEHOLDER).join("|"));
|
|
1725
1719
|
}
|
|
1720
|
+
// ../../packages/aeg-core/src/blast-radius-domains.ts
|
|
1721
|
+
function resolveWorkspaceEntry(entry2, listDirs) {
|
|
1722
|
+
if (entry2.endsWith("/*")) {
|
|
1723
|
+
const baseDir = entry2.slice(0, -2);
|
|
1724
|
+
return listDirs(baseDir).map((name) => `${baseDir}/${name}`);
|
|
1725
|
+
}
|
|
1726
|
+
if (entry2.includes("*"))
|
|
1727
|
+
return [];
|
|
1728
|
+
return [entry2];
|
|
1729
|
+
}
|
|
1730
|
+
// ../../packages/aeg-core/src/consumer-enumeration.ts
|
|
1731
|
+
function deriveWorkspaceMemberDirs(workspaces, listDirs) {
|
|
1732
|
+
const positive = workspaces.filter((entry2) => !entry2.startsWith("!"));
|
|
1733
|
+
const negative = workspaces.filter((entry2) => entry2.startsWith("!")).map((entry2) => entry2.slice(1));
|
|
1734
|
+
const excluded = new Set;
|
|
1735
|
+
for (const entry2 of negative) {
|
|
1736
|
+
for (const member of resolveWorkspaceEntry(entry2, listDirs))
|
|
1737
|
+
excluded.add(member);
|
|
1738
|
+
}
|
|
1739
|
+
const members = new Set;
|
|
1740
|
+
for (const entry2 of positive) {
|
|
1741
|
+
for (const member of resolveWorkspaceEntry(entry2, listDirs)) {
|
|
1742
|
+
if (!excluded.has(member))
|
|
1743
|
+
members.add(member);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return [...members].sort();
|
|
1747
|
+
}
|
|
1748
|
+
function buildConsumersOf(workspaces, listDirs, readManifest) {
|
|
1749
|
+
const members = deriveWorkspaceMemberDirs(workspaces, listDirs);
|
|
1750
|
+
return (pkg) => {
|
|
1751
|
+
const target = `@attalabs/${pkg}`;
|
|
1752
|
+
return members.filter((dir) => {
|
|
1753
|
+
if (dir === `packages/${pkg}`)
|
|
1754
|
+
return false;
|
|
1755
|
+
const manifest = readManifest(dir);
|
|
1756
|
+
if (!manifest)
|
|
1757
|
+
return false;
|
|
1758
|
+
return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {});
|
|
1759
|
+
});
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1726
1762
|
// ../../packages/aeg-core/src/state-machine-model.ts
|
|
1727
1763
|
var FORGE_FACT_INPUTS = [
|
|
1728
1764
|
{
|
|
@@ -1827,6 +1863,22 @@ function isEmDashOrDash(s) {
|
|
|
1827
1863
|
const t = s.trim();
|
|
1828
1864
|
return t === "—" || t === "-" || t === "–";
|
|
1829
1865
|
}
|
|
1866
|
+
// ../../packages/aeg-core/src/waiver-label.ts
|
|
1867
|
+
var WAIVER_LABEL = label("waiver-docs");
|
|
1868
|
+
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
1869
|
+
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
1870
|
+
function isPrincipal(login, principalAllowlist) {
|
|
1871
|
+
if (login === null)
|
|
1872
|
+
return false;
|
|
1873
|
+
const normalized = login.toLowerCase();
|
|
1874
|
+
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
1875
|
+
}
|
|
1876
|
+
function isWaiverLabelActorVerified(opts) {
|
|
1877
|
+
if (!opts.labels.includes(opts.label))
|
|
1878
|
+
return false;
|
|
1879
|
+
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1830
1882
|
// ../../packages/aeg-core/src/parse-token-report.ts
|
|
1831
1883
|
var TOKEN_REPORT_HEADING = /^\s*(?:#{1,6}\s*Token report\s*|\*\*Token report\*\*)\s*$/i;
|
|
1832
1884
|
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;
|
|
@@ -1881,6 +1933,47 @@ function parseInlineFieldList(section) {
|
|
|
1881
1933
|
return null;
|
|
1882
1934
|
return rowFromCells([m[1], m[2], m[3], m[4], m[5], m[6], m[7]]);
|
|
1883
1935
|
}
|
|
1936
|
+
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1937
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1938
|
+
function firstThreeLines(comment) {
|
|
1939
|
+
return comment.split(`
|
|
1940
|
+
`).slice(0, 3).join(`
|
|
1941
|
+
`);
|
|
1942
|
+
}
|
|
1943
|
+
function extractHeadSha(comment) {
|
|
1944
|
+
const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
|
|
1945
|
+
return m ? m[1].toLowerCase() : null;
|
|
1946
|
+
}
|
|
1947
|
+
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1948
|
+
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1949
|
+
if (candidates.length === 0) {
|
|
1950
|
+
return {
|
|
1951
|
+
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1952
|
+
headSha: null,
|
|
1953
|
+
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
const latest = candidates[candidates.length - 1];
|
|
1957
|
+
const m = firstThreeLines(latest).match(valuePattern);
|
|
1958
|
+
if (!m) {
|
|
1959
|
+
return {
|
|
1960
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
|
|
1961
|
+
headSha: null,
|
|
1962
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
return {
|
|
1966
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1967
|
+
headSha: extractHeadSha(latest),
|
|
1968
|
+
danglingNote: null
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
function extractCodeReviewVerdict(comments) {
|
|
1972
|
+
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
1973
|
+
}
|
|
1974
|
+
function extractSecurityReviewVerdict(comments) {
|
|
1975
|
+
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
1976
|
+
}
|
|
1884
1977
|
// ../../packages/aeg-core/src/report-tokens.ts
|
|
1885
1978
|
var DASH_LOOKALIKES = {
|
|
1886
1979
|
"-": "‑",
|
|
@@ -2045,22 +2138,6 @@ function isCodeFile(p) {
|
|
|
2045
2138
|
return false;
|
|
2046
2139
|
return /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|sql|css)$/.test(p);
|
|
2047
2140
|
}
|
|
2048
|
-
// ../../packages/aeg-core/src/waiver-label.ts
|
|
2049
|
-
var WAIVER_LABEL = label("waiver-docs");
|
|
2050
|
-
var WAIVER_LABEL_REVIEW = label("waiver-review");
|
|
2051
|
-
var PRINCIPAL_ALLOWLIST = ["daniboomerang"];
|
|
2052
|
-
function isPrincipal(login, principalAllowlist) {
|
|
2053
|
-
if (login === null)
|
|
2054
|
-
return false;
|
|
2055
|
-
const normalized = login.toLowerCase();
|
|
2056
|
-
return principalAllowlist.some((p) => p.toLowerCase() === normalized);
|
|
2057
|
-
}
|
|
2058
|
-
function isWaiverLabelActorVerified(opts) {
|
|
2059
|
-
if (!opts.labels.includes(opts.label))
|
|
2060
|
-
return false;
|
|
2061
|
-
return isPrincipal(opts.labelActor, opts.principalAllowlist);
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
2141
|
// ../../packages/aeg-core/src/doc-owners.ts
|
|
2065
2142
|
var DOC_OWNERS_PATH = ".vinaya/doc-owners";
|
|
2066
2143
|
function parseDocOwners(content) {
|
|
@@ -2277,6 +2354,32 @@ function parsePremiseBlock(prBody) {
|
|
|
2277
2354
|
}
|
|
2278
2355
|
return assertions;
|
|
2279
2356
|
}
|
|
2357
|
+
function premiseBlockText(prBody) {
|
|
2358
|
+
const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
|
|
2359
|
+
const lines = searchIn.split(/\r?\n/);
|
|
2360
|
+
let startIdx = -1;
|
|
2361
|
+
let endIdxExclusive = lines.length;
|
|
2362
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2363
|
+
const line = lines[i].trim();
|
|
2364
|
+
if (startIdx === -1) {
|
|
2365
|
+
if (isPremiseHeader(line))
|
|
2366
|
+
startIdx = i;
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2369
|
+
if (line === "") {
|
|
2370
|
+
endIdxExclusive = i;
|
|
2371
|
+
break;
|
|
2372
|
+
}
|
|
2373
|
+
if (!PREMISE_LINE.test(line)) {
|
|
2374
|
+
endIdxExclusive = i;
|
|
2375
|
+
break;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
if (startIdx === -1)
|
|
2379
|
+
return null;
|
|
2380
|
+
return lines.slice(startIdx, endIdxExclusive).join(`
|
|
2381
|
+
`);
|
|
2382
|
+
}
|
|
2280
2383
|
function checkPremises(assertions, fileReader) {
|
|
2281
2384
|
const failures = [];
|
|
2282
2385
|
for (const a of assertions) {
|
|
@@ -2363,15 +2466,18 @@ function testPlanRegion(prBody) {
|
|
|
2363
2466
|
return located.found ? located.section : prBody;
|
|
2364
2467
|
}
|
|
2365
2468
|
function checkTestPlan(prBody) {
|
|
2366
|
-
const
|
|
2469
|
+
const located = locateTestPlanSection(prBody);
|
|
2470
|
+
const region = located.found ? located.section : prBody;
|
|
2367
2471
|
if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
|
|
2368
2472
|
return { status: "pass", errors: [] };
|
|
2369
2473
|
if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
|
|
2370
2474
|
return { status: "pass", errors: [] };
|
|
2475
|
+
if (located.found && extractFencedBlocks(region).length > 0)
|
|
2476
|
+
return { status: "pass", errors: [] };
|
|
2371
2477
|
return {
|
|
2372
2478
|
status: "fail",
|
|
2373
2479
|
errors: [
|
|
2374
|
-
"brief-validation Test Plan: no Test Plan section found — expected `Test Plan: unit-tests-only`, or at least one `**[agent]**`/`**[principal]**`-tagged checklist item."
|
|
2480
|
+
"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."
|
|
2375
2481
|
]
|
|
2376
2482
|
};
|
|
2377
2483
|
}
|
|
@@ -2494,8 +2600,162 @@ function isBriefShaped(prBody) {
|
|
|
2494
2600
|
const stripped = stripCode(prBody);
|
|
2495
2601
|
return BRIEF_SHAPE_MARKERS.filter((check) => check(stripped).status === "pass").length >= 2;
|
|
2496
2602
|
}
|
|
2603
|
+
var COMMAND_WORDS = ["export", "bun", "gh", "git", "grep", "sed", "cat", "diff", "vinaya"];
|
|
2604
|
+
var BRIEF_RULES_SINCE_PR = 394;
|
|
2605
|
+
var AGENT_BOXES_REFUSED_SINCE_PR = 396;
|
|
2606
|
+
var AGENT_BOX_LINE_RE = /^-\s*\[[ xX]\]\s*\*{2}\[agent\]\*{2}/im;
|
|
2607
|
+
function checkNoAgentBoxes(prBody) {
|
|
2608
|
+
const region = testPlanRegion(prBody);
|
|
2609
|
+
if (!AGENT_BOX_LINE_RE.test(region))
|
|
2610
|
+
return { status: "pass", errors: [] };
|
|
2611
|
+
return {
|
|
2612
|
+
status: "fail",
|
|
2613
|
+
errors: [
|
|
2614
|
+
"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."
|
|
2615
|
+
]
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
function rolloutThresholdFor(message) {
|
|
2619
|
+
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:")) {
|
|
2620
|
+
return BRIEF_RULES_SINCE_PR;
|
|
2621
|
+
}
|
|
2622
|
+
if (message.startsWith("brief-validation no agent boxes:"))
|
|
2623
|
+
return AGENT_BOXES_REFUSED_SINCE_PR;
|
|
2624
|
+
return null;
|
|
2625
|
+
}
|
|
2626
|
+
function partitionBriefErrorsByRollout(errors, prNumber) {
|
|
2627
|
+
const blocking = [];
|
|
2628
|
+
const info = [];
|
|
2629
|
+
for (const e of errors) {
|
|
2630
|
+
const threshold = rolloutThresholdFor(e);
|
|
2631
|
+
const grandfathered = threshold !== null && prNumber !== null && prNumber < threshold;
|
|
2632
|
+
(grandfathered ? info : blocking).push(e);
|
|
2633
|
+
}
|
|
2634
|
+
return { blocking, info };
|
|
2635
|
+
}
|
|
2636
|
+
function extractFencedBlocks(text) {
|
|
2637
|
+
const re = /^[ \t]*(`{3,}|~{3,})([^\n]*)\n([\s\S]*?)^[ \t]*\1[ \t]*$/gm;
|
|
2638
|
+
const blocks = [];
|
|
2639
|
+
let m = re.exec(text);
|
|
2640
|
+
while (m !== null) {
|
|
2641
|
+
const lang = m[2].trim().toLowerCase().split(/\s+/)[0] ?? "";
|
|
2642
|
+
blocks.push({ start: m.index, end: m.index + m[0].length, lang, content: m[3] });
|
|
2643
|
+
m = re.exec(text);
|
|
2644
|
+
}
|
|
2645
|
+
return blocks;
|
|
2646
|
+
}
|
|
2647
|
+
function firstNonBlankLine(text) {
|
|
2648
|
+
const line = text.split(`
|
|
2649
|
+
`).find((l) => l.trim().length > 0);
|
|
2650
|
+
return line?.trim() ?? "";
|
|
2651
|
+
}
|
|
2652
|
+
var FILE_LINE_RE = /\b[\w./-]+\.(?:tsx?|jsx?|mjs|cjs|md|mdx|json|ya?ml|sh|py|toml):\d+\b/g;
|
|
2653
|
+
function checkNoUnpinnedCodeClaims(prBody) {
|
|
2654
|
+
const premiseText = premiseBlockText(prBody);
|
|
2655
|
+
const withoutPremise = premiseText ? prBody.replace(premiseText, "") : prBody;
|
|
2656
|
+
const scanned = stripCode(withoutPremise, { inlineSpans: "keep" });
|
|
2657
|
+
const matches = scanned.match(FILE_LINE_RE) ?? [];
|
|
2658
|
+
if (matches.length === 0)
|
|
2659
|
+
return { status: "pass", errors: [] };
|
|
2660
|
+
return {
|
|
2661
|
+
status: "fail",
|
|
2662
|
+
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.`)
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
function extractNumberedSection(prBody, num) {
|
|
2666
|
+
const startRe = new RegExp(`^#{1,4}\\s*(?:\\*\\*)?${num}[a-z]?\\.\\s`, "im");
|
|
2667
|
+
const start = startRe.exec(prBody);
|
|
2668
|
+
if (!start)
|
|
2669
|
+
return null;
|
|
2670
|
+
const afterStart = start.index + start[0].length;
|
|
2671
|
+
const rest = prBody.slice(afterStart);
|
|
2672
|
+
const next = /^#{1,4}\s/m.exec(rest);
|
|
2673
|
+
return next ? rest.slice(0, next.index) : rest;
|
|
2674
|
+
}
|
|
2675
|
+
function commandWordOf(line) {
|
|
2676
|
+
return line.trim().split(/\s+/)[0] ?? "";
|
|
2677
|
+
}
|
|
2678
|
+
function isCommandBlock(content) {
|
|
2679
|
+
return COMMAND_WORDS.includes(commandWordOf(firstNonBlankLine(content)));
|
|
2680
|
+
}
|
|
2681
|
+
function isStep0Block(content) {
|
|
2682
|
+
return firstNonBlankLine(content).startsWith("git worktree add");
|
|
2683
|
+
}
|
|
2684
|
+
function checkCommandsCarryOutput(prBody) {
|
|
2685
|
+
const errors = [];
|
|
2686
|
+
for (const [label2, num] of [
|
|
2687
|
+
["5", 5],
|
|
2688
|
+
["6", 6]
|
|
2689
|
+
]) {
|
|
2690
|
+
const section = extractNumberedSection(prBody, num);
|
|
2691
|
+
if (!section)
|
|
2692
|
+
continue;
|
|
2693
|
+
const blocks = extractFencedBlocks(section);
|
|
2694
|
+
for (let i = 0;i < blocks.length; i++) {
|
|
2695
|
+
const block = blocks[i];
|
|
2696
|
+
if (isStep0Block(block.content) || !isCommandBlock(block.content))
|
|
2697
|
+
continue;
|
|
2698
|
+
const next = blocks[i + 1];
|
|
2699
|
+
if (!next || isCommandBlock(next.content)) {
|
|
2700
|
+
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.`);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2705
|
+
}
|
|
2706
|
+
var CONSUMER_TESTS_SENTINEL_RE = /consumer-tests\s*:\s*none\s*[-—–]\s*\S/i;
|
|
2707
|
+
function packagesNamedIn(text) {
|
|
2708
|
+
const re = /\bpackages\/([A-Za-z0-9_-]+)\//g;
|
|
2709
|
+
const pkgs = new Set;
|
|
2710
|
+
let m = re.exec(text);
|
|
2711
|
+
while (m !== null) {
|
|
2712
|
+
pkgs.add(m[1]);
|
|
2713
|
+
m = re.exec(text);
|
|
2714
|
+
}
|
|
2715
|
+
return [...pkgs];
|
|
2716
|
+
}
|
|
2717
|
+
function hasTestPathForConsumer(text, consumerDir) {
|
|
2718
|
+
const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2719
|
+
const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
|
|
2720
|
+
return re.test(text);
|
|
2721
|
+
}
|
|
2722
|
+
function checkConsumerTests(prBody, consumersOf) {
|
|
2723
|
+
const section4 = extractNumberedSection(prBody, 4);
|
|
2724
|
+
if (!section4)
|
|
2725
|
+
return { status: "pass", errors: [] };
|
|
2726
|
+
if (CONSUMER_TESTS_SENTINEL_RE.test(section4))
|
|
2727
|
+
return { status: "pass", errors: [] };
|
|
2728
|
+
const errors = [];
|
|
2729
|
+
for (const pkg of packagesNamedIn(section4)) {
|
|
2730
|
+
for (const consumerDir of consumersOf(pkg)) {
|
|
2731
|
+
if (hasTestPathForConsumer(section4, consumerDir))
|
|
2732
|
+
continue;
|
|
2733
|
+
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>\`.`);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2737
|
+
}
|
|
2738
|
+
var CHECK_NAME_RE = /\bcheck-[a-z0-9-]+(?:\.ts)?\b/;
|
|
2739
|
+
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;
|
|
2740
|
+
var DEFEAT_CASES_RE = /defeat cases\s*:/i;
|
|
2741
|
+
function checkDefeatCases(prBody) {
|
|
2742
|
+
const section4 = extractNumberedSection(prBody, 4);
|
|
2743
|
+
if (!section4)
|
|
2744
|
+
return { status: "pass", errors: [] };
|
|
2745
|
+
if (!CHECK_NAME_RE.test(section4) && !FORGE_WRITE_COMMAND_RE.test(section4))
|
|
2746
|
+
return { status: "pass", errors: [] };
|
|
2747
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2748
|
+
if (DEFEAT_CASES_RE.test(section6))
|
|
2749
|
+
return { status: "pass", errors: [] };
|
|
2750
|
+
return {
|
|
2751
|
+
status: "fail",
|
|
2752
|
+
errors: [
|
|
2753
|
+
"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."
|
|
2754
|
+
]
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2497
2757
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2498
|
-
const { requireClosesN = true } = options;
|
|
2758
|
+
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2499
2759
|
const results = [
|
|
2500
2760
|
checkTierField(prBody, readTier),
|
|
2501
2761
|
checkTestPlan(prBody),
|
|
@@ -2508,10 +2768,219 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2508
2768
|
checkAutonomyClause(prBody),
|
|
2509
2769
|
checkProjectField(prBody),
|
|
2510
2770
|
checkForField(prBody),
|
|
2771
|
+
checkNoUnpinnedCodeClaims(prBody),
|
|
2772
|
+
checkNoAgentBoxes(prBody),
|
|
2773
|
+
checkCommandsCarryOutput(prBody),
|
|
2774
|
+
checkConsumerTests(prBody, consumersOf),
|
|
2775
|
+
checkDefeatCases(prBody),
|
|
2511
2776
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2512
2777
|
];
|
|
2513
2778
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2514
2779
|
}
|
|
2780
|
+
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2781
|
+
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
2782
|
+
var STATIC_PORTABLE_PREFIXES = [
|
|
2783
|
+
"roles/",
|
|
2784
|
+
"contracts/",
|
|
2785
|
+
"skills/",
|
|
2786
|
+
".github/",
|
|
2787
|
+
".vinaya/",
|
|
2788
|
+
".claude/",
|
|
2789
|
+
".git/",
|
|
2790
|
+
".husky/"
|
|
2791
|
+
];
|
|
2792
|
+
var EXEMPT_LITERALS = new Set([
|
|
2793
|
+
"path/inside/the/shipped/diff.ts",
|
|
2794
|
+
"path/inside/the/surface.ts",
|
|
2795
|
+
"apps/x/specs/..."
|
|
2796
|
+
]);
|
|
2797
|
+
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
2798
|
+
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
2799
|
+
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";
|
|
2800
|
+
var VENDOR_EXAMPLE_START = /<!--\s*AEG:VENDOR-EXAMPLE:START\s*-->/;
|
|
2801
|
+
var VENDOR_EXAMPLE_END = /<!--\s*AEG:VENDOR-EXAMPLE:END\s*-->/;
|
|
2802
|
+
function maskVendorExampleRegion(masked) {
|
|
2803
|
+
const start = VENDOR_EXAMPLE_START.exec(masked);
|
|
2804
|
+
if (!start)
|
|
2805
|
+
return masked;
|
|
2806
|
+
const afterStart = start.index + start[0].length;
|
|
2807
|
+
const end = VENDOR_EXAMPLE_END.exec(masked.slice(afterStart));
|
|
2808
|
+
if (!end)
|
|
2809
|
+
return masked;
|
|
2810
|
+
const regionEnd = afterStart + end.index + end[0].length;
|
|
2811
|
+
const region = masked.slice(start.index, regionEnd);
|
|
2812
|
+
const blanked = region.replace(/[^\n]/g, " ");
|
|
2813
|
+
return masked.slice(0, start.index) + blanked + masked.slice(regionEnd);
|
|
2814
|
+
}
|
|
2815
|
+
function extractVendorMentions(content) {
|
|
2816
|
+
const scoped = maskVendorExampleRegion(maskCode(content));
|
|
2817
|
+
const found = [];
|
|
2818
|
+
const pattern = new RegExp(VENDOR_NAME_SOURCE, "gi");
|
|
2819
|
+
let match = pattern.exec(scoped);
|
|
2820
|
+
while (match !== null) {
|
|
2821
|
+
found.push({ name: match[0], index: match.index });
|
|
2822
|
+
match = pattern.exec(scoped);
|
|
2823
|
+
}
|
|
2824
|
+
return found;
|
|
2825
|
+
}
|
|
2826
|
+
function extractCitedPaths(content) {
|
|
2827
|
+
const found = [];
|
|
2828
|
+
const spanPattern = /`([^`\n]+)`/g;
|
|
2829
|
+
let match = spanPattern.exec(content);
|
|
2830
|
+
while (match !== null) {
|
|
2831
|
+
const cited = match[1] ?? "";
|
|
2832
|
+
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
2833
|
+
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
2834
|
+
found.push({ cited, index: match.index });
|
|
2835
|
+
}
|
|
2836
|
+
match = spanPattern.exec(content);
|
|
2837
|
+
}
|
|
2838
|
+
return found;
|
|
2839
|
+
}
|
|
2840
|
+
function lineAtIndex(content, index) {
|
|
2841
|
+
let line = 1;
|
|
2842
|
+
for (let i = 0;i < index; i++) {
|
|
2843
|
+
if (content.charCodeAt(i) === 10)
|
|
2844
|
+
line++;
|
|
2845
|
+
}
|
|
2846
|
+
return line;
|
|
2847
|
+
}
|
|
2848
|
+
function isPortable(cited, shipsPrefix) {
|
|
2849
|
+
if (cited.startsWith(shipsPrefix))
|
|
2850
|
+
return true;
|
|
2851
|
+
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
2852
|
+
}
|
|
2853
|
+
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
2854
|
+
const findings = [];
|
|
2855
|
+
for (const file of files) {
|
|
2856
|
+
if (!file.path.startsWith(shipsPrefix))
|
|
2857
|
+
continue;
|
|
2858
|
+
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
2859
|
+
if (EXEMPT_LITERALS.has(cited))
|
|
2860
|
+
continue;
|
|
2861
|
+
if (isPortable(cited, shipsPrefix))
|
|
2862
|
+
continue;
|
|
2863
|
+
findings.push({
|
|
2864
|
+
file: file.path,
|
|
2865
|
+
line: lineAtIndex(file.content, index),
|
|
2866
|
+
cited,
|
|
2867
|
+
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`,
|
|
2868
|
+
kind: "path"
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
for (const { name, index } of extractVendorMentions(file.content)) {
|
|
2872
|
+
findings.push({
|
|
2873
|
+
file: file.path,
|
|
2874
|
+
line: lineAtIndex(file.content, index),
|
|
2875
|
+
cited: name,
|
|
2876
|
+
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`,
|
|
2877
|
+
kind: "vendor-name"
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
return findings;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// ../../packages/aeg-core/src/doctrine-no-procedures.ts
|
|
2885
|
+
function vendorExampleSpan(content) {
|
|
2886
|
+
const start = VENDOR_EXAMPLE_START.exec(content);
|
|
2887
|
+
if (!start)
|
|
2888
|
+
return null;
|
|
2889
|
+
const afterStart = start.index + start[0].length;
|
|
2890
|
+
const end = VENDOR_EXAMPLE_END.exec(content.slice(afterStart));
|
|
2891
|
+
if (!end)
|
|
2892
|
+
return null;
|
|
2893
|
+
return { start: start.index, end: afterStart + end.index + end[0].length };
|
|
2894
|
+
}
|
|
2895
|
+
function isCommandLine(line) {
|
|
2896
|
+
const word = line.trim().split(/\s+/)[0] ?? "";
|
|
2897
|
+
return COMMAND_WORDS.includes(word);
|
|
2898
|
+
}
|
|
2899
|
+
var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
|
|
2900
|
+
function procedureLineNumberAt(content, index) {
|
|
2901
|
+
return content.slice(0, index).split(`
|
|
2902
|
+
`).length;
|
|
2903
|
+
}
|
|
2904
|
+
function isTemplatePath(path) {
|
|
2905
|
+
return /(^|\/)templates\//.test(path);
|
|
2906
|
+
}
|
|
2907
|
+
function checkDoctrineNoProcedures(files) {
|
|
2908
|
+
const findings = [];
|
|
2909
|
+
for (const file of files) {
|
|
2910
|
+
if (isTemplatePath(file.path))
|
|
2911
|
+
continue;
|
|
2912
|
+
const span = vendorExampleSpan(file.content);
|
|
2913
|
+
for (const block of extractFencedBlocks(file.content)) {
|
|
2914
|
+
if (span && block.start >= span.start && block.end <= span.end)
|
|
2915
|
+
continue;
|
|
2916
|
+
if (!SHELL_LANGS.has(block.lang))
|
|
2917
|
+
continue;
|
|
2918
|
+
const commandLineCount = block.content.split(`
|
|
2919
|
+
`).filter(isCommandLine).length;
|
|
2920
|
+
if (commandLineCount >= 2) {
|
|
2921
|
+
findings.push({
|
|
2922
|
+
file: file.path,
|
|
2923
|
+
line: procedureLineNumberAt(file.content, block.start),
|
|
2924
|
+
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).`
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
return findings;
|
|
2930
|
+
}
|
|
2931
|
+
// ../../packages/aeg-core/src/pr-report-density.ts
|
|
2932
|
+
function headingSectionBody(prBody, heading) {
|
|
2933
|
+
const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "im");
|
|
2934
|
+
const m = headingRe.exec(prBody);
|
|
2935
|
+
if (!m)
|
|
2936
|
+
return null;
|
|
2937
|
+
const start = m.index + m[0].length;
|
|
2938
|
+
const rest = prBody.slice(start);
|
|
2939
|
+
const nextHeading = /^#{1,2}\s/m.exec(rest);
|
|
2940
|
+
return nextHeading ? rest.slice(0, nextHeading.index) : rest;
|
|
2941
|
+
}
|
|
2942
|
+
function stripAnchorBlocks(sectionText) {
|
|
2943
|
+
let result = sectionText;
|
|
2944
|
+
for (const field of ["CLOSES", "PROJECT", "TIER", "PREMISE", "TEST-PLAN", "EVIDENCE"]) {
|
|
2945
|
+
for (;; ) {
|
|
2946
|
+
const bounds = anchoredRegionBounds(result, field);
|
|
2947
|
+
if (!bounds)
|
|
2948
|
+
break;
|
|
2949
|
+
result = result.slice(0, bounds.outerStart) + result.slice(bounds.outerEnd);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
return result;
|
|
2953
|
+
}
|
|
2954
|
+
function paragraphCount(sectionText) {
|
|
2955
|
+
const stripped = stripCode(stripAnchorBlocks(sectionText)).trim();
|
|
2956
|
+
if (stripped.length === 0)
|
|
2957
|
+
return 0;
|
|
2958
|
+
return stripped.split(/\n[ \t]*\n/).map((block) => block.trim()).filter((block) => block.length > 0).length;
|
|
2959
|
+
}
|
|
2960
|
+
function checkSectionDensity(prBody, heading) {
|
|
2961
|
+
const body = headingSectionBody(prBody, heading);
|
|
2962
|
+
if (body === null)
|
|
2963
|
+
return { status: "pass", errors: [] };
|
|
2964
|
+
const count = paragraphCount(body);
|
|
2965
|
+
if (count <= 1)
|
|
2966
|
+
return { status: "pass", errors: [] };
|
|
2967
|
+
return {
|
|
2968
|
+
status: "fail",
|
|
2969
|
+
errors: [
|
|
2970
|
+
`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.`
|
|
2971
|
+
]
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
function checkSummaryDensity(prBody) {
|
|
2975
|
+
return checkSectionDensity(prBody, "Summary");
|
|
2976
|
+
}
|
|
2977
|
+
function checkScopeDensity(prBody) {
|
|
2978
|
+
return checkSectionDensity(prBody, "Scope");
|
|
2979
|
+
}
|
|
2980
|
+
function checkPrReportDensity(prBody) {
|
|
2981
|
+
const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
|
|
2982
|
+
return { errors: results.flatMap((r) => r.errors) };
|
|
2983
|
+
}
|
|
2515
2984
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
2516
2985
|
function hasRationaleField(body, labelPattern) {
|
|
2517
2986
|
const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})`, "im");
|
|
@@ -2763,6 +3232,17 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
2763
3232
|
const depEntry = resolveDepEntry(dep, e.trancheSlug, issueToEntry, taskToEntry);
|
|
2764
3233
|
if (!depEntry)
|
|
2765
3234
|
continue;
|
|
3235
|
+
const sameTask = depEntry.trancheSlug === e.trancheSlug && depEntry.task.id === e.task.id;
|
|
3236
|
+
const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
|
|
3237
|
+
if (sameTask || sameIssue) {
|
|
3238
|
+
failures.push({
|
|
3239
|
+
issue: e.task.issue,
|
|
3240
|
+
tranche: e.trancheSlug,
|
|
3241
|
+
task: e.task.id,
|
|
3242
|
+
reason: `INTERNAL: parsed a self-dependency (depends-on ${dep}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream.`
|
|
3243
|
+
});
|
|
3244
|
+
continue;
|
|
3245
|
+
}
|
|
2766
3246
|
const depFacts = depEntry.facts;
|
|
2767
3247
|
const depClosed = depFacts?.issueState === "closed";
|
|
2768
3248
|
if (!depClosed) {
|
|
@@ -2906,41 +3386,24 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2906
3386
|
}
|
|
2907
3387
|
return { ok: true, expectedIssue };
|
|
2908
3388
|
}
|
|
2909
|
-
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2910
|
-
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2911
|
-
function extractHeadSha(comment) {
|
|
2912
|
-
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2913
|
-
return m ? m[1].toLowerCase() : null;
|
|
2914
|
-
}
|
|
2915
|
-
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2916
|
-
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2917
|
-
if (clearHits.length > 0) {
|
|
2918
|
-
const latest = clearHits[clearHits.length - 1];
|
|
2919
|
-
const m = latest.match(valuePattern);
|
|
2920
|
-
return {
|
|
2921
|
-
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2922
|
-
headSha: extractHeadSha(latest),
|
|
2923
|
-
danglingNote: null
|
|
2924
|
-
};
|
|
2925
|
-
}
|
|
2926
|
-
return {
|
|
2927
|
-
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2928
|
-
headSha: null,
|
|
2929
|
-
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2930
|
-
};
|
|
2931
|
-
}
|
|
2932
|
-
function extractCodeReviewVerdict(comments) {
|
|
2933
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im, "code-reviewer");
|
|
2934
|
-
}
|
|
2935
|
-
function extractSecurityReviewVerdict(comments) {
|
|
2936
|
-
return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
|
|
2937
|
-
}
|
|
2938
3389
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
2939
3390
|
function isBoundToHead(extraction, headSha) {
|
|
2940
3391
|
if (!extraction.headSha)
|
|
2941
3392
|
return false;
|
|
2942
3393
|
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2943
3394
|
}
|
|
3395
|
+
function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
3396
|
+
if (patchIdOf === undefined || !extraction.headSha)
|
|
3397
|
+
return false;
|
|
3398
|
+
const judged = patchIdOf(extraction.headSha);
|
|
3399
|
+
const current = patchIdOf(headSha);
|
|
3400
|
+
if (judged === null || current === null)
|
|
3401
|
+
return false;
|
|
3402
|
+
return judged === current;
|
|
3403
|
+
}
|
|
3404
|
+
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3405
|
+
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3406
|
+
}
|
|
2944
3407
|
function checkReviewGate(input) {
|
|
2945
3408
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2946
3409
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2956,6 +3419,8 @@ function checkReviewGate(input) {
|
|
|
2956
3419
|
waived: true
|
|
2957
3420
|
};
|
|
2958
3421
|
}
|
|
3422
|
+
const reportedMechanicalChecks = input.mechanicalChecks.filter((c) => c.bucket !== "skipping" && c.bucket !== "neutral");
|
|
3423
|
+
const mechanicalChecksClean = reportedMechanicalChecks.length > 0 && reportedMechanicalChecks.every((c) => c.bucket === "pass");
|
|
2959
3424
|
const verified = input.comments.filter((c) => isPrincipal(c.author, principalAllowlist));
|
|
2960
3425
|
const ignoredCount = input.comments.filter((c) => !isPrincipal(c.author, principalAllowlist) && c.body.includes("VERDICT")).length;
|
|
2961
3426
|
const verifiedBodies = verified.map((c) => c.body);
|
|
@@ -2963,12 +3428,12 @@ function checkReviewGate(input) {
|
|
|
2963
3428
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2964
3429
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2965
3430
|
const securityClean = security.value === "PASS";
|
|
2966
|
-
const codeReviewBound =
|
|
2967
|
-
const securityBound =
|
|
2968
|
-
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
3431
|
+
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3432
|
+
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3433
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
|
|
2969
3434
|
return {
|
|
2970
3435
|
verdict: "pass",
|
|
2971
|
-
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
3436
|
+
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.`,
|
|
2972
3437
|
waived: false
|
|
2973
3438
|
};
|
|
2974
3439
|
}
|
|
@@ -2983,6 +3448,9 @@ function checkReviewGate(input) {
|
|
|
2983
3448
|
} else if (!securityBound) {
|
|
2984
3449
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2985
3450
|
}
|
|
3451
|
+
if (!mechanicalChecksClean) {
|
|
3452
|
+
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(", ")}`);
|
|
3453
|
+
}
|
|
2986
3454
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2987
3455
|
return {
|
|
2988
3456
|
verdict: "fail",
|
|
@@ -3262,72 +3730,6 @@ function compareToBaseline(current, baseline) {
|
|
|
3262
3730
|
perTool
|
|
3263
3731
|
};
|
|
3264
3732
|
}
|
|
3265
|
-
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
3266
|
-
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
3267
|
-
var STATIC_PORTABLE_PREFIXES = [
|
|
3268
|
-
"roles/",
|
|
3269
|
-
"contracts/",
|
|
3270
|
-
"skills/",
|
|
3271
|
-
".github/",
|
|
3272
|
-
".vinaya/",
|
|
3273
|
-
".claude/",
|
|
3274
|
-
".git/",
|
|
3275
|
-
".husky/"
|
|
3276
|
-
];
|
|
3277
|
-
var EXEMPT_LITERALS = new Set([
|
|
3278
|
-
"path/inside/the/shipped/diff.ts",
|
|
3279
|
-
"path/inside/the/surface.ts",
|
|
3280
|
-
"apps/x/specs/..."
|
|
3281
|
-
]);
|
|
3282
|
-
var CITED_PATH_PATTERN = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]*)+$/;
|
|
3283
|
-
var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
|
|
3284
|
-
function extractCitedPaths(content) {
|
|
3285
|
-
const found = [];
|
|
3286
|
-
const spanPattern = /`([^`\n]+)`/g;
|
|
3287
|
-
let match = spanPattern.exec(content);
|
|
3288
|
-
while (match !== null) {
|
|
3289
|
-
const cited = match[1] ?? "";
|
|
3290
|
-
const topSegment = cited.slice(0, cited.indexOf("/"));
|
|
3291
|
-
if (CITED_PATH_PATTERN.test(cited) && !NON_PATH_TOP_SEGMENTS.has(topSegment)) {
|
|
3292
|
-
found.push({ cited, index: match.index });
|
|
3293
|
-
}
|
|
3294
|
-
match = spanPattern.exec(content);
|
|
3295
|
-
}
|
|
3296
|
-
return found;
|
|
3297
|
-
}
|
|
3298
|
-
function lineAtIndex(content, index) {
|
|
3299
|
-
let line = 1;
|
|
3300
|
-
for (let i = 0;i < index; i++) {
|
|
3301
|
-
if (content.charCodeAt(i) === 10)
|
|
3302
|
-
line++;
|
|
3303
|
-
}
|
|
3304
|
-
return line;
|
|
3305
|
-
}
|
|
3306
|
-
function isPortable(cited, shipsPrefix) {
|
|
3307
|
-
if (cited.startsWith(shipsPrefix))
|
|
3308
|
-
return true;
|
|
3309
|
-
return STATIC_PORTABLE_PREFIXES.some((prefix) => cited.startsWith(prefix));
|
|
3310
|
-
}
|
|
3311
|
-
function checkDoctrinePortability(files, shipsPrefix = DEFAULT_SHIPS_PREFIX) {
|
|
3312
|
-
const findings = [];
|
|
3313
|
-
for (const file of files) {
|
|
3314
|
-
if (!file.path.startsWith(shipsPrefix))
|
|
3315
|
-
continue;
|
|
3316
|
-
for (const { cited, index } of extractCitedPaths(file.content)) {
|
|
3317
|
-
if (EXEMPT_LITERALS.has(cited))
|
|
3318
|
-
continue;
|
|
3319
|
-
if (isPortable(cited, shipsPrefix))
|
|
3320
|
-
continue;
|
|
3321
|
-
findings.push({
|
|
3322
|
-
file: file.path,
|
|
3323
|
-
line: lineAtIndex(file.content, index),
|
|
3324
|
-
cited,
|
|
3325
|
-
message: `cites "${cited}", a path that only exists in the authoring repository — not portable doctrine`
|
|
3326
|
-
});
|
|
3327
|
-
}
|
|
3328
|
-
}
|
|
3329
|
-
return findings;
|
|
3330
|
-
}
|
|
3331
3733
|
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3332
3734
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3333
3735
|
function shipsArchivePrefix(shipsPrefix) {
|
|
@@ -3663,6 +4065,12 @@ function scanRetiredVocabulary(files) {
|
|
|
3663
4065
|
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
3664
4066
|
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
3665
4067
|
}
|
|
4068
|
+
function isSelfDependency(dep, task, issue) {
|
|
4069
|
+
if (dep.issue !== null && issue !== null && dep.issue === issue.number)
|
|
4070
|
+
return true;
|
|
4071
|
+
const bare = dep.id.trim().replace(/^#/, "");
|
|
4072
|
+
return bare.toLowerCase() === task.id.trim().toLowerCase();
|
|
4073
|
+
}
|
|
3666
4074
|
function checkDispatchReadiness(input) {
|
|
3667
4075
|
const { trancheSlug, task } = input;
|
|
3668
4076
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
@@ -3677,6 +4085,11 @@ function checkDispatchReadiness(input) {
|
|
|
3677
4085
|
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.`);
|
|
3678
4086
|
}
|
|
3679
4087
|
for (const dep of input.dependsOn) {
|
|
4088
|
+
if (isSelfDependency(dep, task, input.issue)) {
|
|
4089
|
+
const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
|
|
4090
|
+
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.`);
|
|
4091
|
+
continue;
|
|
4092
|
+
}
|
|
3680
4093
|
if (dep.resolved === false) {
|
|
3681
4094
|
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.`);
|
|
3682
4095
|
continue;
|
|
@@ -3916,27 +4329,27 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
3916
4329
|
};
|
|
3917
4330
|
}
|
|
3918
4331
|
const checkboxLines = section.split(`
|
|
3919
|
-
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]
|
|
4332
|
+
`).map((line) => line.trimStart()).filter((line) => /^[-*]\s+\[[ xX]\]\s*\*{0,2}\[principal\]\*{0,2}/i.test(line));
|
|
3920
4333
|
if (checkboxLines.length === 0) {
|
|
3921
4334
|
return {
|
|
3922
4335
|
verdict: "pass",
|
|
3923
4336
|
messages: [
|
|
3924
|
-
"Test Plan section found but has no checkbox items.",
|
|
4337
|
+
"Test Plan section found but has no `[principal]` checkbox items.",
|
|
3925
4338
|
"PASS (advisory) — the section may be empty or expressed in prose; verification is the Principal's call."
|
|
3926
4339
|
]
|
|
3927
4340
|
};
|
|
3928
4341
|
}
|
|
3929
4342
|
const unchecked = checkboxLines.filter((line) => /^[-*]\s+\[\s\]/.test(line));
|
|
3930
4343
|
const checked = checkboxLines.filter((line) => /^[-*]\s+\[[xX]\]/.test(line));
|
|
3931
|
-
const messages = [`Test Plan items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
4344
|
+
const messages = [`Test Plan [principal] items: ${checked.length} ticked, ${unchecked.length} unticked.`];
|
|
3932
4345
|
if (unchecked.length > 0) {
|
|
3933
|
-
messages.push("", "FAIL — the following Test Plan items are unticked:");
|
|
4346
|
+
messages.push("", "FAIL — the following [principal] Test Plan items are unticked:");
|
|
3934
4347
|
for (const line of unchecked)
|
|
3935
4348
|
messages.push(` ${line}`);
|
|
3936
|
-
messages.push("", "Per aeg-root/roles/developer.md (Verification), a PR is not mergeable while any Test Plan box is unticked.", "
|
|
4349
|
+
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.");
|
|
3937
4350
|
return { verdict: "fail", messages };
|
|
3938
4351
|
}
|
|
3939
|
-
messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
4352
|
+
messages.push("All [principal] Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
3940
4353
|
return { verdict: "pass", messages };
|
|
3941
4354
|
}
|
|
3942
4355
|
// ../../packages/aeg-core/src/workspace-escape.ts
|
|
@@ -4038,7 +4451,7 @@ function main() {
|
|
|
4038
4451
|
check: CHECK_NAME,
|
|
4039
4452
|
severity: "error",
|
|
4040
4453
|
message,
|
|
4041
|
-
agent_recovery_prompt: "
|
|
4454
|
+
agent_recovery_prompt: "Run each `[agent]` Test Plan command from the PR head and let `vinaya pr report` write its output into the AEG:EVIDENCE block; leave `[principal]` boxes for the Principal to tick after verifying in a real signed-in browser. Re-run `vinaya check test-plan` afterwards."
|
|
4042
4455
|
});
|
|
4043
4456
|
}
|
|
4044
4457
|
process.exit(1);
|