@attalabs/vinaya 0.24.0 → 0.25.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 +25 -2
- package/aeg-root/contracts/developer-reviewer.md +3 -0
- package/aeg-root/contracts/planner-brief.md +13 -0
- package/aeg-root/enforcement.md +11 -7
- package/aeg-root/process.md +4 -4
- package/aeg-root/roles/brief-author.md +4 -2
- package/aeg-root/roles/developer.md +20 -14
- package/aeg-root/roles/planner.md +14 -0
- package/aeg-root/roles/principal.md +3 -0
- package/aeg-root/roles/reviewer.md +23 -7
- package/aeg-root/roles/security.md +21 -6
- package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
- package/aeg-root/state-machine.md +2 -2
- package/aeg-root/templates/brief-template.md +20 -4
- package/aeg-root/templates/issue-rationale-template.md +34 -1
- package/aeg-root/templates/pr-report-template.md +5 -13
- package/dist/checks/bin/check-body-bare-digits.js +572 -127
- package/dist/checks/bin/check-branch-topology.js +629 -73
- package/dist/checks/bin/check-brief-shape.js +1675 -70
- package/dist/checks/bin/check-changeset-coverage.js +584 -127
- package/dist/checks/bin/check-closes-n.js +629 -73
- package/dist/checks/bin/check-coherence.js +696 -134
- package/dist/checks/bin/check-dead-branch-push.js +505 -66
- package/dist/checks/bin/check-dispatch-readiness.js +696 -134
- package/dist/checks/bin/check-doc-coverage-push.js +572 -127
- package/dist/checks/bin/check-doc-coverage.js +572 -127
- package/dist/checks/bin/check-doctrine-no-procedures.js +572 -127
- package/dist/checks/bin/check-doctrine-portability.js +572 -127
- package/dist/checks/bin/check-evidence-fresh.js +572 -127
- package/dist/checks/bin/check-exec-bits.js +572 -127
- package/dist/checks/bin/check-first-push-dispatch.js +696 -134
- package/dist/checks/bin/check-issue-assignment.js +629 -73
- package/dist/checks/bin/check-main-branch-refusal.js +505 -66
- package/dist/checks/bin/check-no-disk-state.js +505 -66
- package/dist/checks/bin/check-pr-report-density.js +505 -66
- package/dist/checks/bin/check-quoted-command.js +572 -127
- package/dist/checks/bin/check-reader-resolvable-prose.js +610 -133
- package/dist/checks/bin/check-registry-gates.js +517 -69
- package/dist/checks/bin/check-retired-vocabulary.js +572 -127
- package/dist/checks/bin/check-review-gate.js +646 -130
- package/dist/checks/bin/check-single-plan-pr.js +505 -66
- package/dist/checks/bin/check-test-plan.js +505 -66
- package/dist/checks/bin/check-token-collection-wired.js +505 -66
- package/dist/checks/bin/check-token-report.js +505 -66
- package/dist/checks/bin/check-workspace-escape.js +582 -129
- package/dist/index.js +7740 -4283
- package/package.json +2 -2
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +2 -2
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +26 -1
- 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/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]__1pndh3_._.js → [root-of-the-server]__0p8q38b._.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]__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/_0wxycau._.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/chunks/ssr/packages_ui_topbar_index_tsx_1h1gs1y._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3m1kgax7j2vgs.js → 0bn8c8v5q429o.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0doqwpd81sjzn.js → 0keji7wvbe1d1.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1maoxbrl3pv5d.css +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1uogpj5w2n5ju.js → 3-6a3tinbdb-7.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0zebsmmk1bvnb.js → 3_9xytdmiv1xi.js} +4 -4
- package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/3qc67qhcvbg0o.css +0 -1
- /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/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/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_ssgManifest.js +0 -0
|
@@ -1941,13 +1941,18 @@ function parseInlineFieldList(section) {
|
|
|
1941
1941
|
}
|
|
1942
1942
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1943
1943
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1944
|
-
|
|
1944
|
+
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1945
|
+
function firstFiveLines(comment) {
|
|
1945
1946
|
return comment.split(`
|
|
1946
|
-
`).slice(0,
|
|
1947
|
+
`).slice(0, 5).join(`
|
|
1947
1948
|
`);
|
|
1948
1949
|
}
|
|
1949
1950
|
function extractHeadSha(comment) {
|
|
1950
|
-
const m =
|
|
1951
|
+
const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
|
|
1952
|
+
return m ? m[1].toLowerCase() : null;
|
|
1953
|
+
}
|
|
1954
|
+
function extractObjectivesVersion(comment) {
|
|
1955
|
+
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1951
1956
|
return m ? m[1].toLowerCase() : null;
|
|
1952
1957
|
}
|
|
1953
1958
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
@@ -1956,21 +1961,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1956
1961
|
return {
|
|
1957
1962
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1958
1963
|
headSha: null,
|
|
1964
|
+
objectivesVersion: null,
|
|
1959
1965
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1960
1966
|
};
|
|
1961
1967
|
}
|
|
1962
1968
|
const latest = candidates[candidates.length - 1];
|
|
1963
|
-
const m =
|
|
1969
|
+
const m = firstFiveLines(latest).match(valuePattern);
|
|
1964
1970
|
if (!m) {
|
|
1965
1971
|
return {
|
|
1966
|
-
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first
|
|
1972
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1967
1973
|
headSha: null,
|
|
1968
|
-
|
|
1974
|
+
objectivesVersion: null,
|
|
1975
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1969
1976
|
};
|
|
1970
1977
|
}
|
|
1971
1978
|
return {
|
|
1972
1979
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1973
1980
|
headSha: extractHeadSha(latest),
|
|
1981
|
+
objectivesVersion: extractObjectivesVersion(latest),
|
|
1974
1982
|
danglingNote: null
|
|
1975
1983
|
};
|
|
1976
1984
|
}
|
|
@@ -2327,8 +2335,105 @@ function overrideActive(opts) {
|
|
|
2327
2335
|
return true;
|
|
2328
2336
|
return false;
|
|
2329
2337
|
}
|
|
2330
|
-
// ../../packages/aeg-core/src/
|
|
2338
|
+
// ../../packages/aeg-core/src/objectives.ts
|
|
2331
2339
|
import { createHash as createHash2 } from "node:crypto";
|
|
2340
|
+
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2341
|
+
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2342
|
+
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2343
|
+
function hasBacktickedPath(text) {
|
|
2344
|
+
let i = 0;
|
|
2345
|
+
while (i < text.length) {
|
|
2346
|
+
const start = text.indexOf("`", i);
|
|
2347
|
+
if (start === -1)
|
|
2348
|
+
return false;
|
|
2349
|
+
const end = text.indexOf("`", start + 1);
|
|
2350
|
+
if (end === -1)
|
|
2351
|
+
return false;
|
|
2352
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
2353
|
+
return true;
|
|
2354
|
+
i = end + 1;
|
|
2355
|
+
}
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
2359
|
+
function stripObjectiveBackticks(text) {
|
|
2360
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
2361
|
+
}
|
|
2362
|
+
function wordCount(text) {
|
|
2363
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2364
|
+
}
|
|
2365
|
+
function objectivesSectionText(body) {
|
|
2366
|
+
const heading = HEADING_RE.exec(body);
|
|
2367
|
+
if (!heading)
|
|
2368
|
+
return null;
|
|
2369
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
2370
|
+
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2371
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
2372
|
+
}
|
|
2373
|
+
function hasObjectivesHeading(body) {
|
|
2374
|
+
return HEADING_RE.test(body);
|
|
2375
|
+
}
|
|
2376
|
+
function objectivesOf(body) {
|
|
2377
|
+
const section = objectivesSectionText(body);
|
|
2378
|
+
if (section === null) {
|
|
2379
|
+
return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
|
|
2380
|
+
}
|
|
2381
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2382
|
+
if (lines.length === 0) {
|
|
2383
|
+
return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
|
|
2384
|
+
}
|
|
2385
|
+
const objectives = [];
|
|
2386
|
+
const errors = [];
|
|
2387
|
+
for (const line of lines) {
|
|
2388
|
+
const m = OBJECTIVE_LINE_RE.exec(line);
|
|
2389
|
+
if (!m) {
|
|
2390
|
+
errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
|
|
2391
|
+
continue;
|
|
2392
|
+
}
|
|
2393
|
+
const n = m[1];
|
|
2394
|
+
const text = m[2].trim();
|
|
2395
|
+
if (text.length === 0) {
|
|
2396
|
+
errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
2400
|
+
errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Brief Author maps it to files).`);
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
objectives.push({ id: `O${n}`, text });
|
|
2404
|
+
}
|
|
2405
|
+
for (let i = 0;i < objectives.length; i++) {
|
|
2406
|
+
const expected = i + 1;
|
|
2407
|
+
const actual = Number.parseInt(objectives[i].id.slice(1), 10);
|
|
2408
|
+
if (actual !== expected) {
|
|
2409
|
+
errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
|
|
2410
|
+
break;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
if (errors.length > 0)
|
|
2414
|
+
return { ok: false, errors };
|
|
2415
|
+
return { ok: true, objectives };
|
|
2416
|
+
}
|
|
2417
|
+
function normalizeText(text) {
|
|
2418
|
+
return text.trim().replace(/\s+/g, " ");
|
|
2419
|
+
}
|
|
2420
|
+
function objectivesVersion(objectives) {
|
|
2421
|
+
const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
|
|
2422
|
+
`);
|
|
2423
|
+
return createHash2("sha256").update(normalized).digest("hex");
|
|
2424
|
+
}
|
|
2425
|
+
function isIssueNotFoundError(err) {
|
|
2426
|
+
const stderr = err?.stderr;
|
|
2427
|
+
const haystack = [
|
|
2428
|
+
err?.message ?? "",
|
|
2429
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
2430
|
+
].join(`
|
|
2431
|
+
`);
|
|
2432
|
+
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// ../../packages/aeg-core/src/premise-check.ts
|
|
2436
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2332
2437
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2333
2438
|
function isPremiseHeader(line) {
|
|
2334
2439
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2399,7 +2504,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2399
2504
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2400
2505
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2401
2506
|
} else if (a.kind === "sha256") {
|
|
2402
|
-
const actual =
|
|
2507
|
+
const actual = createHash3("sha256").update(content).digest("hex");
|
|
2403
2508
|
if (actual !== a.value.toLowerCase()) {
|
|
2404
2509
|
failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
|
|
2405
2510
|
}
|
|
@@ -2760,8 +2865,55 @@ function checkDefeatCases(prBody) {
|
|
|
2760
2865
|
]
|
|
2761
2866
|
};
|
|
2762
2867
|
}
|
|
2868
|
+
var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
|
|
2869
|
+
var OBJECTIVE_REF_RE = /O(\d+)/g;
|
|
2870
|
+
function citedObjectiveIds(section6) {
|
|
2871
|
+
const ids = new Set;
|
|
2872
|
+
for (const m of section6.matchAll(PART_CITATION_RE)) {
|
|
2873
|
+
for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
|
|
2874
|
+
ids.add(Number.parseInt(r[1], 10));
|
|
2875
|
+
}
|
|
2876
|
+
return ids;
|
|
2877
|
+
}
|
|
2878
|
+
function checkObjectivesCopy(prBody, issueObjectives) {
|
|
2879
|
+
const parsed = objectivesOf(prBody);
|
|
2880
|
+
if (!parsed.ok) {
|
|
2881
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
|
|
2882
|
+
}
|
|
2883
|
+
if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
|
|
2884
|
+
return {
|
|
2885
|
+
status: "fail",
|
|
2886
|
+
errors: [
|
|
2887
|
+
"brief-validation objectives copy: the brief's `## Objectives` section does not match the Issue's — copy the Issue's `## Objectives` section (aeg-root/roles/developer.md)."
|
|
2888
|
+
]
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
return { status: "pass", errors: [] };
|
|
2892
|
+
}
|
|
2893
|
+
function checkObjectivesCoverage(prBody) {
|
|
2894
|
+
const parsed = objectivesOf(prBody);
|
|
2895
|
+
if (!parsed.ok) {
|
|
2896
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
|
|
2897
|
+
}
|
|
2898
|
+
const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
2899
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2900
|
+
const cited = citedObjectiveIds(section6);
|
|
2901
|
+
const maxDeclared = Math.max(...declaredIds, 0);
|
|
2902
|
+
const errors = [];
|
|
2903
|
+
for (const id of declaredIds) {
|
|
2904
|
+
if (!cited.has(id)) {
|
|
2905
|
+
errors.push(`brief-validation objectives coverage: O${id} is not cited by any Part in §6 — every objective must be cited by at least one Part.`);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
for (const id of cited) {
|
|
2909
|
+
if (!declaredIds.has(id)) {
|
|
2910
|
+
errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2914
|
+
}
|
|
2763
2915
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2764
|
-
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2916
|
+
const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
|
|
2765
2917
|
const results = [
|
|
2766
2918
|
checkTierField(prBody, readTier),
|
|
2767
2919
|
checkTestPlan(prBody),
|
|
@@ -2779,6 +2931,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2779
2931
|
checkCommandsCarryOutput(prBody),
|
|
2780
2932
|
checkConsumerTests(prBody, consumersOf),
|
|
2781
2933
|
checkDefeatCases(prBody),
|
|
2934
|
+
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2782
2935
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2783
2936
|
];
|
|
2784
2937
|
return { errors: results.flatMap((r) => r.errors) };
|
|
@@ -2977,14 +3130,14 @@ function checkSectionDensity(prBody, heading) {
|
|
|
2977
3130
|
]
|
|
2978
3131
|
};
|
|
2979
3132
|
}
|
|
2980
|
-
function
|
|
2981
|
-
return checkSectionDensity(prBody, "
|
|
3133
|
+
function checkDecisionsDensity(prBody) {
|
|
3134
|
+
return checkSectionDensity(prBody, "Decisions");
|
|
2982
3135
|
}
|
|
2983
3136
|
function checkScopeDensity(prBody) {
|
|
2984
3137
|
return checkSectionDensity(prBody, "Scope");
|
|
2985
3138
|
}
|
|
2986
3139
|
function checkPrReportDensity(prBody) {
|
|
2987
|
-
const results = [
|
|
3140
|
+
const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
|
|
2988
3141
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2989
3142
|
}
|
|
2990
3143
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
@@ -3016,6 +3169,15 @@ function checkIssueRationale(body) {
|
|
|
3016
3169
|
}
|
|
3017
3170
|
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3018
3171
|
}
|
|
3172
|
+
var OBJECTIVES_SINCE_ISSUE = 404;
|
|
3173
|
+
function checkIssueObjectives(body, issueNumber) {
|
|
3174
|
+
if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
|
|
3175
|
+
return { status: "pass", errors: [] };
|
|
3176
|
+
const result = objectivesOf(body);
|
|
3177
|
+
if (result.ok)
|
|
3178
|
+
return { status: "pass", errors: [] };
|
|
3179
|
+
return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
|
|
3180
|
+
}
|
|
3019
3181
|
function isTaskIssueLabelSet(labels) {
|
|
3020
3182
|
return hasLabel("tranche", labels);
|
|
3021
3183
|
}
|
|
@@ -3277,7 +3439,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3277
3439
|
continue;
|
|
3278
3440
|
const errors = [
|
|
3279
3441
|
...checkIssueRationale(issue.body).errors,
|
|
3280
|
-
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
|
|
3442
|
+
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3443
|
+
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3281
3444
|
];
|
|
3282
3445
|
if (errors.length === 0)
|
|
3283
3446
|
continue;
|
|
@@ -3392,6 +3555,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
3392
3555
|
}
|
|
3393
3556
|
return { ok: true, expectedIssue };
|
|
3394
3557
|
}
|
|
3558
|
+
// ../../packages/aeg-core/src/archive-task.ts
|
|
3559
|
+
function closesRefs(text) {
|
|
3560
|
+
return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
|
|
3561
|
+
}
|
|
3562
|
+
function extractIssue(body) {
|
|
3563
|
+
const stripped = stripCode(body);
|
|
3564
|
+
const bodyNums = closesRefs(stripped);
|
|
3565
|
+
const anchored = anchoredRegion(stripped, "CLOSES");
|
|
3566
|
+
if (anchored !== null) {
|
|
3567
|
+
const anchorNums = closesRefs(anchored);
|
|
3568
|
+
if (anchorNums.length === 0)
|
|
3569
|
+
return { issue: null, extraIssues: bodyNums, outsideHeader: false };
|
|
3570
|
+
const issue2 = anchorNums[0];
|
|
3571
|
+
return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
|
|
3572
|
+
}
|
|
3573
|
+
const headerNums = closesRefs(headerRegion(stripped));
|
|
3574
|
+
if (bodyNums.length === 0)
|
|
3575
|
+
return { issue: null, extraIssues: [], outsideHeader: false };
|
|
3576
|
+
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3577
|
+
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3578
|
+
}
|
|
3395
3579
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
3396
3580
|
function isBoundToHead(extraction, headSha) {
|
|
3397
3581
|
if (!extraction.headSha)
|
|
@@ -3410,6 +3594,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
|
3410
3594
|
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3411
3595
|
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3412
3596
|
}
|
|
3597
|
+
function isBoundToObjectives(extraction, currentVersion) {
|
|
3598
|
+
if (currentVersion === null)
|
|
3599
|
+
return true;
|
|
3600
|
+
return extraction.objectivesVersion === currentVersion;
|
|
3601
|
+
}
|
|
3413
3602
|
function checkReviewGate(input) {
|
|
3414
3603
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3415
3604
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3436,7 +3625,9 @@ function checkReviewGate(input) {
|
|
|
3436
3625
|
const securityClean = security.value === "PASS";
|
|
3437
3626
|
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3438
3627
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3439
|
-
|
|
3628
|
+
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3629
|
+
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3630
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
|
|
3440
3631
|
return {
|
|
3441
3632
|
verdict: "pass",
|
|
3442
3633
|
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.`,
|
|
@@ -3448,11 +3639,15 @@ function checkReviewGate(input) {
|
|
|
3448
3639
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
3449
3640
|
} else if (!codeReviewBound) {
|
|
3450
3641
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3642
|
+
} else if (!codeReviewObjectivesBound) {
|
|
3643
|
+
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3451
3644
|
}
|
|
3452
3645
|
if (!securityClean) {
|
|
3453
3646
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
3454
3647
|
} else if (!securityBound) {
|
|
3455
3648
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3649
|
+
} else if (!securityObjectivesBound) {
|
|
3650
|
+
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3456
3651
|
}
|
|
3457
3652
|
if (!mechanicalChecksClean) {
|
|
3458
3653
|
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(", ")}`);
|
|
@@ -3688,55 +3883,20 @@ function checkG6(rows, registeredCheckNames) {
|
|
|
3688
3883
|
}
|
|
3689
3884
|
return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3690
3885
|
}
|
|
3691
|
-
// ../../packages/aeg-core/src/
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
return {
|
|
3702
|
-
verdict: "refuse",
|
|
3703
|
-
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.`
|
|
3704
|
-
};
|
|
3705
|
-
}
|
|
3706
|
-
if (prState === "OPEN") {
|
|
3707
|
-
return {
|
|
3708
|
-
verdict: "allow",
|
|
3709
|
-
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
3710
|
-
};
|
|
3711
|
-
}
|
|
3712
|
-
if (prState === "NONE") {
|
|
3713
|
-
return {
|
|
3714
|
-
verdict: "allow",
|
|
3715
|
-
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
3716
|
-
};
|
|
3717
|
-
}
|
|
3718
|
-
return {
|
|
3719
|
-
verdict: "allow",
|
|
3720
|
-
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
3721
|
-
};
|
|
3722
|
-
}
|
|
3723
|
-
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
3724
|
-
function captureBaseline(counts, capturedAt) {
|
|
3725
|
-
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
3886
|
+
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3887
|
+
var PRODUCT_SLUG_SCOPE = [
|
|
3888
|
+
"apps/cli/src",
|
|
3889
|
+
".github/workflows",
|
|
3890
|
+
".vinaya",
|
|
3891
|
+
"apps/cli/README.md",
|
|
3892
|
+
"packages/sources/README.md"
|
|
3893
|
+
];
|
|
3894
|
+
function isUnderOrEqual(path, prefix) {
|
|
3895
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
3726
3896
|
}
|
|
3727
|
-
function
|
|
3728
|
-
|
|
3729
|
-
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
3730
|
-
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
3731
|
-
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
3732
|
-
});
|
|
3733
|
-
return {
|
|
3734
|
-
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
3735
|
-
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
3736
|
-
perTool
|
|
3737
|
-
};
|
|
3897
|
+
function isProductScopeFile(path) {
|
|
3898
|
+
return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
|
|
3738
3899
|
}
|
|
3739
|
-
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3740
3900
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3741
3901
|
function shipsArchivePrefix(shipsPrefix) {
|
|
3742
3902
|
return `${shipsPrefix}tranches/completed/`;
|
|
@@ -3756,6 +3916,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
|
|
|
3756
3916
|
}
|
|
3757
3917
|
if (isSpecFile2(path) || isClaudeMdFile(path))
|
|
3758
3918
|
return "internal";
|
|
3919
|
+
if (isProductScopeFile(path))
|
|
3920
|
+
return "product";
|
|
3759
3921
|
return null;
|
|
3760
3922
|
}
|
|
3761
3923
|
var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3768,6 +3930,9 @@ function stripNonProse(path, content) {
|
|
|
3768
3930
|
}
|
|
3769
3931
|
return content;
|
|
3770
3932
|
}
|
|
3933
|
+
function stripNonProseForProduct(path, content) {
|
|
3934
|
+
return path.endsWith(".md") ? stripNonProse(path, content) : content;
|
|
3935
|
+
}
|
|
3771
3936
|
function lineAt(content, index) {
|
|
3772
3937
|
let line = 1;
|
|
3773
3938
|
for (let i = 0;i < index; i++) {
|
|
@@ -3792,12 +3957,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3792
3957
|
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
|
|
3793
3958
|
...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
|
|
3794
3959
|
];
|
|
3960
|
+
const productPatterns = [
|
|
3961
|
+
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
|
|
3962
|
+
];
|
|
3795
3963
|
for (const file of files) {
|
|
3796
3964
|
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
3797
|
-
if (!cls ||
|
|
3965
|
+
if (!cls || cls === "internal")
|
|
3798
3966
|
continue;
|
|
3799
|
-
|
|
3800
|
-
|
|
3967
|
+
if (cls !== "product" && !SWEPT_CLASSES.has(cls))
|
|
3968
|
+
continue;
|
|
3969
|
+
const blocking = cls === "product";
|
|
3970
|
+
const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
|
|
3971
|
+
const patternsToRun = blocking ? productPatterns : patterns;
|
|
3972
|
+
for (const { pattern, what, group } of patternsToRun) {
|
|
3801
3973
|
pattern.lastIndex = 0;
|
|
3802
3974
|
let match = pattern.exec(scrubbed);
|
|
3803
3975
|
while (match !== null) {
|
|
@@ -3805,7 +3977,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3805
3977
|
findings.push({
|
|
3806
3978
|
file: file.path,
|
|
3807
3979
|
line: lineAt(scrubbed, match.index),
|
|
3808
|
-
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve
|
|
3980
|
+
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
|
|
3981
|
+
blocking
|
|
3809
3982
|
});
|
|
3810
3983
|
match = pattern.exec(scrubbed);
|
|
3811
3984
|
}
|
|
@@ -3855,7 +4028,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
|
|
|
3855
4028
|
findings.push({
|
|
3856
4029
|
file: file.path,
|
|
3857
4030
|
line,
|
|
3858
|
-
message: `uses coined term "${term}" without defining it inline or linking the glossary
|
|
4031
|
+
message: `uses coined term "${term}" without defining it inline or linking the glossary`,
|
|
4032
|
+
blocking: false
|
|
3859
4033
|
});
|
|
3860
4034
|
}
|
|
3861
4035
|
}
|
|
@@ -3878,6 +4052,7 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
3878
4052
|
}
|
|
3879
4053
|
return terms;
|
|
3880
4054
|
}
|
|
4055
|
+
|
|
3881
4056
|
// ../../packages/aeg-core/src/quoted-command.ts
|
|
3882
4057
|
var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
|
|
3883
4058
|
var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3972,6 +4147,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
|
|
|
3972
4147
|
}
|
|
3973
4148
|
return findings;
|
|
3974
4149
|
}
|
|
4150
|
+
// ../../packages/aeg-core/src/dead-branch-push-guard.ts
|
|
4151
|
+
function checkDeadBranchPush(input) {
|
|
4152
|
+
const { branch, prState, prNumber } = input;
|
|
4153
|
+
if (prState === "MERGED") {
|
|
4154
|
+
return {
|
|
4155
|
+
verdict: "refuse",
|
|
4156
|
+
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.`
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
if (prState === "CLOSED") {
|
|
4160
|
+
return {
|
|
4161
|
+
verdict: "refuse",
|
|
4162
|
+
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.`
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
if (prState === "OPEN") {
|
|
4166
|
+
return {
|
|
4167
|
+
verdict: "allow",
|
|
4168
|
+
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
if (prState === "NONE") {
|
|
4172
|
+
return {
|
|
4173
|
+
verdict: "allow",
|
|
4174
|
+
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
4175
|
+
};
|
|
4176
|
+
}
|
|
4177
|
+
return {
|
|
4178
|
+
verdict: "allow",
|
|
4179
|
+
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
4180
|
+
};
|
|
4181
|
+
}
|
|
4182
|
+
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
4183
|
+
function captureBaseline(counts, capturedAt) {
|
|
4184
|
+
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
4185
|
+
}
|
|
4186
|
+
function compareToBaseline(current, baseline) {
|
|
4187
|
+
const perTool = current.map((c) => {
|
|
4188
|
+
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
4189
|
+
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
4190
|
+
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
4191
|
+
});
|
|
4192
|
+
return {
|
|
4193
|
+
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
4194
|
+
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
4195
|
+
perTool
|
|
4196
|
+
};
|
|
4197
|
+
}
|
|
3975
4198
|
// ../../packages/aeg-core/src/retired-vocabulary.ts
|
|
3976
4199
|
var RETIRED_PATTERNS = [
|
|
3977
4200
|
String.raw`\bD-[0-9]{3}\b`,
|
|
@@ -4441,6 +4664,222 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4441
4664
|
}
|
|
4442
4665
|
return findings;
|
|
4443
4666
|
}
|
|
4667
|
+
// ../../packages/aeg-core/src/log/schema.ts
|
|
4668
|
+
import { z } from "zod";
|
|
4669
|
+
var ROLE_VALUES = [
|
|
4670
|
+
"planner",
|
|
4671
|
+
"brief-author",
|
|
4672
|
+
"developer",
|
|
4673
|
+
"code-reviewer",
|
|
4674
|
+
"security",
|
|
4675
|
+
"principal",
|
|
4676
|
+
"archivist",
|
|
4677
|
+
"architect"
|
|
4678
|
+
];
|
|
4679
|
+
var RoleSchema = z.enum(ROLE_VALUES);
|
|
4680
|
+
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4681
|
+
var HostSchema = z.enum(HOST_VALUES);
|
|
4682
|
+
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4683
|
+
var HeaderMetaSchema = z.object({
|
|
4684
|
+
schema: z.literal(1),
|
|
4685
|
+
ts: z.string(),
|
|
4686
|
+
run_id: z.string().regex(RUN_ID_PATTERN),
|
|
4687
|
+
seq: z.number().int().nonnegative(),
|
|
4688
|
+
repo: z.string().nullable(),
|
|
4689
|
+
vinaya: z.string(),
|
|
4690
|
+
doctrine: z.string(),
|
|
4691
|
+
host: HostSchema,
|
|
4692
|
+
machine: z.string()
|
|
4693
|
+
}).strict();
|
|
4694
|
+
var SubjectSchema = z.object({
|
|
4695
|
+
issue: z.number().int().nullable(),
|
|
4696
|
+
pr: z.number().int().optional(),
|
|
4697
|
+
sha: z.string().optional(),
|
|
4698
|
+
role: z.union([RoleSchema, z.literal("unattributed")]),
|
|
4699
|
+
round: z.number().int().optional(),
|
|
4700
|
+
objectives_version: z.string().optional()
|
|
4701
|
+
}).strict();
|
|
4702
|
+
var HeaderSchema = z.object({
|
|
4703
|
+
meta: HeaderMetaSchema,
|
|
4704
|
+
subject: SubjectSchema
|
|
4705
|
+
}).strict();
|
|
4706
|
+
var envelopeTail = {
|
|
4707
|
+
duration_ms: z.number().nonnegative().optional(),
|
|
4708
|
+
payload: z.object({}).strict()
|
|
4709
|
+
};
|
|
4710
|
+
var DispatchOutcomeSchema = z.discriminatedUnion("type", [
|
|
4711
|
+
z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
|
|
4712
|
+
z.object({
|
|
4713
|
+
type: z.literal("round_pushed"),
|
|
4714
|
+
pr: z.number().int(),
|
|
4715
|
+
head: z.string(),
|
|
4716
|
+
comment_id: z.number().int()
|
|
4717
|
+
}).strict(),
|
|
4718
|
+
z.object({
|
|
4719
|
+
type: z.literal("verdict"),
|
|
4720
|
+
verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
4721
|
+
head: z.string(),
|
|
4722
|
+
comment_id: z.number().int(),
|
|
4723
|
+
objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
|
|
4724
|
+
findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
|
|
4725
|
+
}).strict(),
|
|
4726
|
+
z.object({
|
|
4727
|
+
type: z.literal("escalation"),
|
|
4728
|
+
class: z.enum(["authority", "strategy", "product"]),
|
|
4729
|
+
comment_id: z.number().int()
|
|
4730
|
+
}).strict(),
|
|
4731
|
+
z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
|
|
4732
|
+
z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
|
|
4733
|
+
z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
|
|
4734
|
+
]);
|
|
4735
|
+
var dispatchShared = {
|
|
4736
|
+
meta: HeaderMetaSchema,
|
|
4737
|
+
subject: SubjectSchema,
|
|
4738
|
+
kind: z.literal("dispatch"),
|
|
4739
|
+
...envelopeTail,
|
|
4740
|
+
target_role: RoleSchema,
|
|
4741
|
+
model: z.string(),
|
|
4742
|
+
round: z.number().int().optional(),
|
|
4743
|
+
effect_id: z.string()
|
|
4744
|
+
};
|
|
4745
|
+
var DispatchEventSchema = z.discriminatedUnion("event", [
|
|
4746
|
+
z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
|
|
4747
|
+
z.object({
|
|
4748
|
+
...dispatchShared,
|
|
4749
|
+
event: z.literal("outcome_received"),
|
|
4750
|
+
outcome: DispatchOutcomeSchema,
|
|
4751
|
+
usage: z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable()
|
|
4752
|
+
}).strict(),
|
|
4753
|
+
z.object({
|
|
4754
|
+
...dispatchShared,
|
|
4755
|
+
event: z.literal("dispatch_failed"),
|
|
4756
|
+
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
|
|
4757
|
+
}).strict()
|
|
4758
|
+
]);
|
|
4759
|
+
var loopShared = {
|
|
4760
|
+
meta: HeaderMetaSchema,
|
|
4761
|
+
subject: SubjectSchema,
|
|
4762
|
+
kind: z.literal("dev_review_loop"),
|
|
4763
|
+
...envelopeTail,
|
|
4764
|
+
loop_id: z.string()
|
|
4765
|
+
};
|
|
4766
|
+
var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
4767
|
+
z.object({
|
|
4768
|
+
...loopShared,
|
|
4769
|
+
event: z.literal("loop_started"),
|
|
4770
|
+
task: z.number().int(),
|
|
4771
|
+
policy: z.object({
|
|
4772
|
+
max_rounds: z.number().int().nonnegative(),
|
|
4773
|
+
reviewers: z.array(RoleSchema),
|
|
4774
|
+
models: z.record(RoleSchema, z.string())
|
|
4775
|
+
}).strict()
|
|
4776
|
+
}).strict(),
|
|
4777
|
+
z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
|
|
4778
|
+
z.object({
|
|
4779
|
+
...loopShared,
|
|
4780
|
+
event: z.literal("gate_result_read"),
|
|
4781
|
+
round: z.number().int(),
|
|
4782
|
+
head: z.string(),
|
|
4783
|
+
green: z.boolean()
|
|
4784
|
+
}).strict(),
|
|
4785
|
+
z.object({
|
|
4786
|
+
...loopShared,
|
|
4787
|
+
event: z.literal("verdicts_read"),
|
|
4788
|
+
round: z.number().int(),
|
|
4789
|
+
head: z.string(),
|
|
4790
|
+
all_approve: z.boolean(),
|
|
4791
|
+
blockers: z.number().int().nonnegative()
|
|
4792
|
+
}).strict(),
|
|
4793
|
+
z.object({
|
|
4794
|
+
...loopShared,
|
|
4795
|
+
event: z.literal("findings_compared"),
|
|
4796
|
+
round: z.number().int(),
|
|
4797
|
+
open: z.array(z.string()),
|
|
4798
|
+
resolved: z.array(z.string()),
|
|
4799
|
+
new: z.array(z.string()),
|
|
4800
|
+
recurring: z.array(z.string())
|
|
4801
|
+
}).strict(),
|
|
4802
|
+
z.object({
|
|
4803
|
+
...loopShared,
|
|
4804
|
+
event: z.literal("stop_condition_met"),
|
|
4805
|
+
round: z.number().int(),
|
|
4806
|
+
condition: z.enum([
|
|
4807
|
+
"green",
|
|
4808
|
+
"max_rounds",
|
|
4809
|
+
"no_progress",
|
|
4810
|
+
"escalated",
|
|
4811
|
+
"principal_stop",
|
|
4812
|
+
"confidence",
|
|
4813
|
+
"reappearance"
|
|
4814
|
+
])
|
|
4815
|
+
}).strict(),
|
|
4816
|
+
z.object({
|
|
4817
|
+
...loopShared,
|
|
4818
|
+
event: z.literal("paused"),
|
|
4819
|
+
round: z.number().int(),
|
|
4820
|
+
reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
4821
|
+
}).strict(),
|
|
4822
|
+
z.object({
|
|
4823
|
+
...loopShared,
|
|
4824
|
+
event: z.literal("resumed"),
|
|
4825
|
+
round: z.number().int(),
|
|
4826
|
+
by: z.literal("principal")
|
|
4827
|
+
}).strict(),
|
|
4828
|
+
z.object({
|
|
4829
|
+
...loopShared,
|
|
4830
|
+
event: z.literal("round_ended"),
|
|
4831
|
+
round: z.number().int(),
|
|
4832
|
+
base_head: z.string(),
|
|
4833
|
+
head: z.string(),
|
|
4834
|
+
files_changed: z.number().int().nonnegative(),
|
|
4835
|
+
insertions: z.number().int().nonnegative(),
|
|
4836
|
+
deletions: z.number().int().nonnegative(),
|
|
4837
|
+
wall_ms: z.number().nonnegative(),
|
|
4838
|
+
outcome: z.enum(["green", "changes_requested", "escalated"])
|
|
4839
|
+
}).strict(),
|
|
4840
|
+
z.object({
|
|
4841
|
+
...loopShared,
|
|
4842
|
+
event: z.literal("journal_finalized"),
|
|
4843
|
+
rounds: z.number().int().nonnegative(),
|
|
4844
|
+
total_wall_ms: z.number().nonnegative(),
|
|
4845
|
+
time_to_green_ms: z.number().nonnegative().nullable(),
|
|
4846
|
+
files_changed_total: z.number().int().nonnegative(),
|
|
4847
|
+
final_head: z.string(),
|
|
4848
|
+
result: z.enum(["merged_ready", "stopped"])
|
|
4849
|
+
}).strict()
|
|
4850
|
+
]);
|
|
4851
|
+
var ForgeOpSchema = z.enum([
|
|
4852
|
+
"pr.create",
|
|
4853
|
+
"pr.comment",
|
|
4854
|
+
"pr.body.replace",
|
|
4855
|
+
"pr.refreeze",
|
|
4856
|
+
"issue.create",
|
|
4857
|
+
"issue.edit",
|
|
4858
|
+
"issue.comment",
|
|
4859
|
+
"milestone.create",
|
|
4860
|
+
"milestone.edit",
|
|
4861
|
+
"milestone.close",
|
|
4862
|
+
"label.add",
|
|
4863
|
+
"label.remove"
|
|
4864
|
+
]);
|
|
4865
|
+
var ForgeWriteTargetSchema = z.object({
|
|
4866
|
+
issue: z.number().int().optional(),
|
|
4867
|
+
pr: z.number().int().optional()
|
|
4868
|
+
}).strict();
|
|
4869
|
+
var forgeWriteShared = {
|
|
4870
|
+
meta: HeaderMetaSchema,
|
|
4871
|
+
subject: SubjectSchema,
|
|
4872
|
+
kind: z.literal("forge_write"),
|
|
4873
|
+
...envelopeTail,
|
|
4874
|
+
op: ForgeOpSchema,
|
|
4875
|
+
target: ForgeWriteTargetSchema
|
|
4876
|
+
};
|
|
4877
|
+
var ForgeWriteEventSchema = z.discriminatedUnion("event", [
|
|
4878
|
+
z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
|
|
4879
|
+
z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
|
|
4880
|
+
z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
|
|
4881
|
+
]);
|
|
4882
|
+
var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
|
|
4444
4883
|
// ../../packages/sources/src/commands.ts
|
|
4445
4884
|
var COMMANDS = [
|
|
4446
4885
|
{
|
|
@@ -4554,11 +4993,24 @@ var COMMANDS = [
|
|
|
4554
4993
|
{ flag: "--out <path>", description: "Write the rendered brief to a file instead of stdout" }
|
|
4555
4994
|
],
|
|
4556
4995
|
details: [
|
|
4557
|
-
"Reads the task Issue (`vinaya/tranche:<tranche>`-labeled, id `<n>`) and the tree, and fills every section a program can derive: the header `Project:`/`Tier:`/`Closes #N`, the Step 0 worktree line, the dispatch-gate status as the pre-flight line, §4's file list (with consumer packages and a `sha256` premise pin per file), §7 from the `.vinaya/doc-owners` derivation, and every remaining section from the Issue's eight-field Planner rationale. Refuses — naming the missing
|
|
4996
|
+
"Reads the task Issue (`vinaya/tranche:<tranche>`-labeled, id `<n>`) and the tree, and fills every section a program can derive: the header `Project:`/`Tier:`/`Closes #N`, the Step 0 worktree line, the dispatch-gate status as the pre-flight line, §4's file list (with consumer packages and a `sha256` premise pin per file, and Out of surface from the Issue's own `## Surface` `out:` list), §6 from the Issue's `## Parts`, §7 from the `.vinaya/doc-owners` derivation, §9 from the Issue's `## Test plan`, §10 from the Issue's `## Stop conditions` plus the rationale's Stop-and-escalate field, and every remaining section from the Issue's eight-field Planner rationale. Refuses — naming the missing section — when it cannot be derived: no Issue, the dispatch gate not clear, a `--surfaces` glob matching no tracked file, or the Issue missing/malformed `## Surface`/`## Parts`/`## Test plan`/`## Stop conditions` — never a bracketed placeholder.",
|
|
4558
4997
|
"Never writes under `aeg-root/` or to the Issue — a brief is pasted to the Developer, never committed. Reads `aeg-root/templates/brief-template.md` at run time for its one fixed sentence and the standing autonomy clause; every other byte is generated from the forge/tree facts."
|
|
4559
4998
|
],
|
|
4560
4999
|
status: "shipped"
|
|
4561
5000
|
},
|
|
5001
|
+
{
|
|
5002
|
+
name: "task dispatch",
|
|
5003
|
+
description: "Render, pin, and post the brief on the Issue as the frozen original; start the developer",
|
|
5004
|
+
flags: [
|
|
5005
|
+
{ flag: "--agent <claude|codex|gemini>", description: "Start the developer through dispatchRole once posted" }
|
|
5006
|
+
],
|
|
5007
|
+
details: [
|
|
5008
|
+
"Renders the brief from the Issue and the tree (the same assembly `brief render` uses) and posts it once as an Issue comment whose first line is `<!-- aeg:brief:v1 -->` and whose second line is `Brief hash: <sha256>` — the hash covers only the body below those two lines, so any reader recomputes it. Refuses outright, naming the existing comment's URL, when a `v1` comment already exists on the Issue — the brief is frozen by design, never overwritten or silently reissued.",
|
|
5009
|
+
"With `--agent`, starts the Developer through `dispatchRole` when `apps/cli/src/lib/dispatch.ts` exports it; otherwise prints the rendered brief and the manual dispatch instruction and exits `0` — a soft dependency, never a hard block.",
|
|
5010
|
+
"Principal-only, with or without `--agent`: refuses before any render, forge read, or post when the authenticated `gh` identity is not on the Principal allowlist (or cannot be resolved at all) — dispatching is the `todo → in-flight` transition, not a general-purpose comment poster."
|
|
5011
|
+
],
|
|
5012
|
+
status: "shipped"
|
|
5013
|
+
},
|
|
4562
5014
|
{
|
|
4563
5015
|
name: "pr create",
|
|
4564
5016
|
description: "Open a pull request after full brief-schema validation",
|
|
@@ -4611,6 +5063,19 @@ var COMMANDS = [
|
|
|
4611
5063
|
],
|
|
4612
5064
|
status: "shipped"
|
|
4613
5065
|
},
|
|
5066
|
+
{
|
|
5067
|
+
name: "pr rule",
|
|
5068
|
+
description: "Post a Principal ruling on a PR, marked and versioned — never mistaken for a review verdict",
|
|
5069
|
+
flags: [
|
|
5070
|
+
{ flag: "--file", description: "Path to the ruling file to post as a PR comment" },
|
|
5071
|
+
{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }
|
|
5072
|
+
],
|
|
5073
|
+
details: [
|
|
5074
|
+
"Refuses before posting when the file's first line reads as an escalation (`ESCALATE:`), or when the file carries verdict grammar anywhere `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` would treat as a candidate — a `VERDICT:` line past the extractor's own first-five-line window still counts, since the whole-body candidate test is what disqualifies the file, not merely a clean read.",
|
|
5075
|
+
"Marker numbers (`<!-- aeg:principal:ruling:<pr>-<k> -->`) are counted on the forge at post time from `gh pr view --json comments`, never derived from a local file — two rulings racing to the same number are a known, undocumented-lock case."
|
|
5076
|
+
],
|
|
5077
|
+
status: "shipped"
|
|
5078
|
+
},
|
|
4614
5079
|
{
|
|
4615
5080
|
name: "pr verify-evidence",
|
|
4616
5081
|
description: "Prove a pull request's AEG:EVIDENCE region was machine-generated — regenerate it and compare",
|
|
@@ -4652,6 +5117,44 @@ var COMMANDS = [
|
|
|
4652
5117
|
],
|
|
4653
5118
|
status: "shipped"
|
|
4654
5119
|
},
|
|
5120
|
+
{
|
|
5121
|
+
name: "issue objectives edit",
|
|
5122
|
+
description: "Rewrite a task Issue's `## Objectives` section by command — versioned, findable on the forge",
|
|
5123
|
+
flags: [
|
|
5124
|
+
{ flag: "--add", description: "Append a new objective as `O<max+1>` with the given sentence" },
|
|
5125
|
+
{ flag: "--drop", description: "Remove an objective by id (`O<k>`) — never renumbers the survivors" },
|
|
5126
|
+
{
|
|
5127
|
+
flag: "--replace",
|
|
5128
|
+
description: 'Replace an objective\'s sentence in place, keeping its id (`O<k>` "<sentence>")'
|
|
5129
|
+
},
|
|
5130
|
+
{ flag: "--reason", description: "Required, non-empty: why this change is being made" },
|
|
5131
|
+
{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }
|
|
5132
|
+
],
|
|
5133
|
+
details: [
|
|
5134
|
+
"Exactly one of `--add`/`--drop`/`--replace` is required. Every edit goes through the same validated `issue edit` write path (`writeValidatedIssueEdit`) as `vinaya issue edit` itself — no second, unvalidated write path.",
|
|
5135
|
+
"A `--drop` that leaves the surviving objectives non-contiguous from `O1` is refused with `objectivesOf`'s own parser message: dropping never renumbers survivors, and task 1's contiguous-from-O1 grammar is the one parser everything else reads, so a live contradiction between the two stops here for a Principal ruling rather than silently renumbering.",
|
|
5136
|
+
"Splices the rendered section back in place (`## Objectives` heading through the next `##` heading, or end of body) — every other byte of the Issue body is untouched. Posts one comment marked `<!-- aeg:objectives:v<k> -->` carrying the previous list, the new list, the reason, and the new `objectivesVersion`; `k` is counted on the forge at post time, never derived from a local file."
|
|
5137
|
+
],
|
|
5138
|
+
status: "shipped"
|
|
5139
|
+
},
|
|
5140
|
+
{
|
|
5141
|
+
name: "log flush",
|
|
5142
|
+
description: "Post a target Issue or PR's outbox as one or more marked comments, then truncate what the forge confirmed",
|
|
5143
|
+
flags: [
|
|
5144
|
+
{ flag: "--issue", description: "Flush this Issue's outbox (`~/.vinaya/outbox/<owner>-<repo>/<n>.ndjson`)" },
|
|
5145
|
+
{
|
|
5146
|
+
flag: "--pr",
|
|
5147
|
+
description: "Flush the Issue named by this PR's `Closes #N` line, posting the comments on the PR"
|
|
5148
|
+
},
|
|
5149
|
+
{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }
|
|
5150
|
+
],
|
|
5151
|
+
details: [
|
|
5152
|
+
"Exactly one of `--issue`/`--pr` is required. `--pr` resolves the Issue from the PR body's `Closes #N` line — the same anchor every gate reads — and refuses with a check error naming the missing line when the body carries none.",
|
|
5153
|
+
"Splits the outbox first at `run_id` boundaries (a maximal run of consecutive lines sharing one `run_id`, never a global group-by, so an interleaved outbox never produces a range spanning a gap), then at `FORGE_COMMENT_MAX_CHARS` (65536) within each run. Each comment opens with `<!-- aeg:log:<run_id>:<seq_from>-<seq_to> -->` on its own line, then one fenced `ndjson` block, one outbox line per line, verbatim. A single line too large to fit alone is refused by name, never split across two comments.",
|
|
5154
|
+
"Logs its own `forge_write` line — `validated` before posting, `written` (with every returned comment id) after the last post succeeds, or `refused` (with gh's error) on any failure — through the same `log()` every other family uses, into the same outbox, before truncating. The outbox is truncated only to the lines confirmed posted; a failed chunk's lines, and anything appended to the outbox during the flush (including the flush's own `validated`/`written`/`refused` line), always survive to ride the next flush."
|
|
5155
|
+
],
|
|
5156
|
+
status: "shipped"
|
|
5157
|
+
},
|
|
4655
5158
|
{
|
|
4656
5159
|
name: "milestone create",
|
|
4657
5160
|
description: "Create a GitHub Milestone from a validated body",
|
|
@@ -4744,6 +5247,10 @@ var COMMANDS = [
|
|
|
4744
5247
|
flag: "--findings-file",
|
|
4745
5248
|
description: "One finding per line: `SEVERITY|file:line|description` (`|`-delimited). A re-review names a prior id in the description as `F<n> <class> <state>:`. Omit for zero findings."
|
|
4746
5249
|
},
|
|
5250
|
+
{
|
|
5251
|
+
flag: "--objectives-file",
|
|
5252
|
+
description: "One line per objective: `O<n>|MET|<evidence>` or `O<n>|NOT MET|<evidence>` (`|`-delimited; evidence is the rest of the line). Required whenever the closed Issue (or the PR body's own `## Objectives`) has an objectives list to judge; its ids must cover that list exactly, and a re-review must restate every prior objective. Never together with `--escalate`."
|
|
5253
|
+
},
|
|
4747
5254
|
{ flag: "--brief-conformance", description: "code-reviewer only: the BRIEF CONFORMANCE line" },
|
|
4748
5255
|
{ flag: "--spec-conformance", description: "code-reviewer only: the SPEC CONFORMANCE line" },
|
|
4749
5256
|
{ flag: "--scope", description: "code-reviewer only: the SCOPE line" },
|
|
@@ -4774,8 +5281,9 @@ var COMMANDS = [
|
|
|
4774
5281
|
"`--print-only` runs the exact same render-then-self-check path as a real post, then returns before `gh pr comment` — closes atta-labs/vinaya#184, where the old command silently posted a verdict anyway after a reviewer guessed this flag existed.",
|
|
4775
5282
|
"Every structural line (`VERDICT:`, `Judged head:`) is rendered from this command's own validated enum/sha inputs — never from a caller-supplied string — so a Reviewer's free-typed prose can no longer produce a shape the merge gate's line-anchored regex fails to see.",
|
|
4776
5283
|
"The verdict is derived, not typed: a BLOCKER (or CRITICAL/HIGH) finding forces REQUEST_CHANGES/FAIL and its absence forces APPROVE/PASS, before posting anything — an explicit `--verdict` that disagrees is refused naming the derived value. When a same-role verdict comment already exists on the PR, a new findings file must carry every prior id with a state and no non-blocking finding outside the diff since that comment's judged head, or the post is refused.",
|
|
4777
|
-
"
|
|
4778
|
-
"
|
|
5284
|
+
"Renders an `OBJECTIVES:` block (one `O<n>: MET | NOT MET — <evidence>` line per objective) after `SPEC CONFORMANCE:`/before `CONFIG SCAN:`, and an `Objectives version:` line at line 5 — resolved from the closed Issue's `## Objectives` list, or the PR body's own section when it closes none. A clean verdict (`APPROVE`/`PASS`) is refused alongside any `NOT MET`; an Issue below the objectives cutover renders neither line at all, matching the merge gate's own skip.",
|
|
5285
|
+
"Before the post ever reaches the forge, runs the exact `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` functions `checkReviewGate` calls over its own rendered text and refuses (exit 2) unless exactly the intended verdict extracts and the other role extracts none — an escalation requires both to extract none. Both extractors read only a comment's first five lines; a code-review or security render's caller-supplied fields never open one of those lines, but an escalation's `--summary` can (pre-cutover, it becomes line 5 unprefixed) — this pre-post re-parse, not the render's construction, is what catches that case before it ever reaches the forge.",
|
|
5286
|
+
"After posting, re-fetches the PR's comments and runs them through the same extractors `checkReviewGate` calls — the same functions, not a second implementation — and exits non-zero naming precisely what failed to re-parse if the post does not come back clean, bound to the resolved head and objectives version, and free of the other role's verdict. There is no `--skip-verify` escape."
|
|
4779
5287
|
],
|
|
4780
5288
|
status: "shipped"
|
|
4781
5289
|
},
|
|
@@ -4944,6 +5452,54 @@ var COMMANDS = [
|
|
|
4944
5452
|
'This is the one procedure named in `apps/cli/specs/self-hosting.md`, "How the published version is produced" — publishing itself stays manual and human-triggered; this command only removes the hand-typed four-step recipe.'
|
|
4945
5453
|
],
|
|
4946
5454
|
status: "shipped"
|
|
5455
|
+
},
|
|
5456
|
+
{
|
|
5457
|
+
name: "dispatch",
|
|
5458
|
+
description: "Start a role's headless agent session (claude/codex/gemini) with attribution set on its environment, recording the outcome through the Vinaya Log",
|
|
5459
|
+
flags: [
|
|
5460
|
+
{
|
|
5461
|
+
flag: "--agent",
|
|
5462
|
+
description: "Vendor to start: `claude`, `codex`, or `gemini` (falls back to `dispatch.agent` in config)"
|
|
5463
|
+
},
|
|
5464
|
+
{ flag: "--prompt-file", description: "Path to the prompt text sent to the agent (never on argv)" },
|
|
5465
|
+
{ flag: "--task", description: "This dispatch's task Issue number — mutually exclusive with `--pr`" },
|
|
5466
|
+
{
|
|
5467
|
+
flag: "--pr",
|
|
5468
|
+
description: "Flush the outbox to this PR's Issue after the dispatch — mutually exclusive with `--task`"
|
|
5469
|
+
},
|
|
5470
|
+
{ flag: "--round", description: "Round number, for a dispatch inside a review loop" },
|
|
5471
|
+
{
|
|
5472
|
+
flag: "--resume <id>",
|
|
5473
|
+
description: "Resume the vendor's own session/thread from a prior dispatch's returned `resumeId`, instead of starting a fresh one"
|
|
5474
|
+
},
|
|
5475
|
+
{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }
|
|
5476
|
+
],
|
|
5477
|
+
details: [
|
|
5478
|
+
"Sets `VINAYA_RUN_ID`/`VINAYA_ROLE`/`VINAYA_TASK`/`VINAYA_ROUND` on the child only — never on this process's own environment — and refuses by name, before any spawn attempt, when the named vendor binary is absent from `PATH` or present but not executable.",
|
|
5479
|
+
"Records `dispatched` (with the prompt's sha256), `outcome_received` (duration, the vendor's own usage when its stdout prints a recognizable shape), or `dispatch_failed` (`timeout`, `crash`, or `refused`) through the Vinaya Log's one `dispatch` family writer, `dispatchRole`. A wall-time ceiling (`dispatch.timeoutMs` in config, default one hour) sends `SIGTERM` then, after a grace window, `SIGKILL`.",
|
|
5480
|
+
"When `--task` or `--pr` is given, flushes that outbox via `vinaya log flush` immediately after the child settles — `--task` and `--pr` are mutually exclusive here, matching `log flush`'s own single-target rule. Without either, the dispatch still runs and logs; nothing is flushed, and the lines ride to the next flush.",
|
|
5481
|
+
"A successful dispatch's `DispatchHandle` carries `resumeId` — the vendor's own session/thread identifier (claude/gemini: `session_id`; codex: `thread_id`), parsed from its stdout, `null` on any failure. Passing that value as `--resume <id>` on a later call swaps in that vendor's own resume invocation (`claude -p -r <id> ...`; `codex exec resume <id> ...`; `gemini ... --resume <id> ...`) in place of its first-dispatch args."
|
|
5482
|
+
],
|
|
5483
|
+
status: "shipped"
|
|
5484
|
+
},
|
|
5485
|
+
{
|
|
5486
|
+
name: "dev-review-loop",
|
|
5487
|
+
description: "Dispatch the developer, run review rounds against the forge, resume the same developer session every round, and hold every verdict until the policy says publish",
|
|
5488
|
+
flags: [
|
|
5489
|
+
{ flag: "--task <n>", description: "This task's Issue number — its frozen `aeg:brief:v1` comment is the brief" },
|
|
5490
|
+
{
|
|
5491
|
+
flag: "--agent <claude|codex|gemini>",
|
|
5492
|
+
description: "Vendor for every dispatch this loop makes (falls back to `dispatch.agent` in config)"
|
|
5493
|
+
},
|
|
5494
|
+
{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }
|
|
5495
|
+
],
|
|
5496
|
+
details: [
|
|
5497
|
+
"Dispatches the developer through `dispatchRole` with the brief read from the Issue, waits for the PR it opens, then runs `assessRound` (`@attalabs/aeg-core`) — the entire policy — against observations this command reads from the forge: the head via `git ls-remote` only (never `gh pr view headRefOid`, which can lag a push), CI conclusion via the check-runs API (never run locally), and ruling comments matching `<!-- aeg:principal:ruling:<pr>-<k> -->`.",
|
|
5498
|
+
"Nothing is posted to the PR before the policy decides `publish`: each round's reviewer and security verdicts are dispatched fresh (never a resumed session) through `dispatchRole`, rendered through `review post`'s own render functions, and written to a local file under the outbox — never `gh pr comment`/`gh pr review`. Posting the held verdicts is a separate, later task.",
|
|
5499
|
+
"The developer's session is resumed every round via `dispatchRole`'s `resumeId` — never a fresh session — for every vendor; a round whose resume fails for a vendor that resumed successfully the round before stops the loop rather than silently falling back to a fresh developer session.",
|
|
5500
|
+
"Every dispatch and round transition is a log line through the Vinaya Log, flushed to the forge at each round boundary."
|
|
5501
|
+
],
|
|
5502
|
+
status: "shipped"
|
|
4947
5503
|
}
|
|
4948
5504
|
];
|
|
4949
5505
|
// ../../packages/sources/src/forge-adapter.ts
|
|
@@ -4955,10 +5511,10 @@ function createForgeSource(config) {
|
|
|
4955
5511
|
};
|
|
4956
5512
|
}
|
|
4957
5513
|
// ../../packages/sources/src/select-source.ts
|
|
4958
|
-
import { z } from "zod";
|
|
4959
|
-
var StateSourceConfigSchema =
|
|
4960
|
-
|
|
4961
|
-
|
|
5514
|
+
import { z as z2 } from "zod";
|
|
5515
|
+
var StateSourceConfigSchema = z2.discriminatedUnion("kind", [
|
|
5516
|
+
z2.object({ kind: z2.literal("forge"), owner: z2.string(), repo: z2.string() }),
|
|
5517
|
+
z2.object({ kind: z2.literal("file"), root: z2.string().optional() })
|
|
4962
5518
|
]);
|
|
4963
5519
|
// src/checks/contract.ts
|
|
4964
5520
|
var CHECK_SCHEMA_VERSION = 1;
|
|
@@ -4972,27 +5528,27 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
4972
5528
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
4973
5529
|
import { homedir } from "node:os";
|
|
4974
5530
|
import { dirname, join, resolve } from "node:path";
|
|
4975
|
-
import { z as
|
|
5531
|
+
import { z as z3 } from "zod";
|
|
4976
5532
|
|
|
4977
5533
|
// src/lib/agent-vendors.ts
|
|
4978
5534
|
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
4979
5535
|
|
|
4980
5536
|
// src/lib/config.ts
|
|
4981
|
-
var EnvEntrySchema =
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
5537
|
+
var EnvEntrySchema = z3.union([
|
|
5538
|
+
z3.literal(true),
|
|
5539
|
+
z3.object({ optional: z3.literal(true) }),
|
|
5540
|
+
z3.object({ anyOf: z3.array(z3.string()).min(2) }),
|
|
5541
|
+
z3.string()
|
|
4986
5542
|
]);
|
|
4987
|
-
var CheckEntrySchema =
|
|
4988
|
-
run:
|
|
4989
|
-
scope:
|
|
4990
|
-
include:
|
|
4991
|
-
args:
|
|
4992
|
-
timeoutMs:
|
|
4993
|
-
env:
|
|
4994
|
-
requiresOpenPr:
|
|
4995
|
-
ownWorkflow:
|
|
5543
|
+
var CheckEntrySchema = z3.object({
|
|
5544
|
+
run: z3.string(),
|
|
5545
|
+
scope: z3.enum(["diff", "full"]),
|
|
5546
|
+
include: z3.array(z3.string()).optional(),
|
|
5547
|
+
args: z3.array(z3.string()).optional(),
|
|
5548
|
+
timeoutMs: z3.number().optional(),
|
|
5549
|
+
env: z3.record(z3.string(), EnvEntrySchema).optional(),
|
|
5550
|
+
requiresOpenPr: z3.boolean().optional(),
|
|
5551
|
+
ownWorkflow: z3.boolean().optional()
|
|
4996
5552
|
}).superRefine((entry2, ctx) => {
|
|
4997
5553
|
if (!entry2.env)
|
|
4998
5554
|
return;
|
|
@@ -5002,22 +5558,22 @@ var CheckEntrySchema = z2.object({
|
|
|
5002
5558
|
const members = value.anyOf;
|
|
5003
5559
|
if (new Set(members).size !== members.length) {
|
|
5004
5560
|
ctx.addIssue({
|
|
5005
|
-
code:
|
|
5561
|
+
code: z3.ZodIssueCode.custom,
|
|
5006
5562
|
message: `env.${key}.anyOf has duplicate members`,
|
|
5007
5563
|
path: ["env", key, "anyOf"]
|
|
5008
5564
|
});
|
|
5009
5565
|
}
|
|
5010
5566
|
if (!members.includes(key)) {
|
|
5011
5567
|
ctx.addIssue({
|
|
5012
|
-
code:
|
|
5568
|
+
code: z3.ZodIssueCode.custom,
|
|
5013
5569
|
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
5014
5570
|
path: ["env", key, "anyOf"]
|
|
5015
5571
|
});
|
|
5016
5572
|
}
|
|
5017
5573
|
}
|
|
5018
5574
|
});
|
|
5019
|
-
var RoleEntrySchema =
|
|
5020
|
-
contract:
|
|
5575
|
+
var RoleEntrySchema = z3.object({
|
|
5576
|
+
contract: z3.string().refine((p) => p.includes("/"), {
|
|
5021
5577
|
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
5022
5578
|
})
|
|
5023
5579
|
});
|
|
@@ -5036,19 +5592,21 @@ var BRIEF_BUILTINS = [
|
|
|
5036
5592
|
"closesN",
|
|
5037
5593
|
"premiseCoverage",
|
|
5038
5594
|
"issueRationale",
|
|
5595
|
+
"objectives",
|
|
5596
|
+
"briefSections",
|
|
5039
5597
|
"milestoneShape"
|
|
5040
5598
|
];
|
|
5041
|
-
var BriefSectionSchema =
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5599
|
+
var BriefSectionSchema = z3.union([
|
|
5600
|
+
z3.object({ builtin: z3.enum(BRIEF_BUILTINS) }),
|
|
5601
|
+
z3.object({ heading: z3.string().min(1), name: z3.string().optional() }),
|
|
5602
|
+
z3.object({ field: z3.string().min(1), name: z3.string().optional() }),
|
|
5603
|
+
z3.object({ phrase: z3.string().min(1), name: z3.string().optional() })
|
|
5046
5604
|
]);
|
|
5047
|
-
var BriefSchemaSchema =
|
|
5048
|
-
pr:
|
|
5049
|
-
issue:
|
|
5050
|
-
milestone:
|
|
5051
|
-
ack:
|
|
5605
|
+
var BriefSchemaSchema = z3.object({
|
|
5606
|
+
pr: z3.object({ sections: z3.array(BriefSectionSchema) }).optional(),
|
|
5607
|
+
issue: z3.object({ sections: z3.array(BriefSectionSchema) }).optional(),
|
|
5608
|
+
milestone: z3.object({ sections: z3.array(BriefSectionSchema) }).optional(),
|
|
5609
|
+
ack: z3.array(z3.enum(BRIEF_BUILTINS)).optional()
|
|
5052
5610
|
});
|
|
5053
5611
|
function isSafeRepoRelPath(p) {
|
|
5054
5612
|
if (p.length === 0)
|
|
@@ -5057,7 +5615,7 @@ function isSafeRepoRelPath(p) {
|
|
|
5057
5615
|
return false;
|
|
5058
5616
|
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
5059
5617
|
}
|
|
5060
|
-
var SafeRepoRelPath =
|
|
5618
|
+
var SafeRepoRelPath = z3.string().refine(isSafeRepoRelPath, {
|
|
5061
5619
|
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
5062
5620
|
});
|
|
5063
5621
|
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
@@ -5067,22 +5625,22 @@ function isCanonicalHookBlockPath(p) {
|
|
|
5067
5625
|
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
5068
5626
|
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
5069
5627
|
});
|
|
5070
|
-
var ManagedBlockRecordSchema =
|
|
5628
|
+
var ManagedBlockRecordSchema = z3.object({
|
|
5071
5629
|
path: ManagedHookBlockPath,
|
|
5072
|
-
marker:
|
|
5073
|
-
comment:
|
|
5630
|
+
marker: z3.string(),
|
|
5631
|
+
comment: z3.enum(["hash", "html"])
|
|
5074
5632
|
});
|
|
5075
|
-
var ManagedManifestSchema =
|
|
5076
|
-
version:
|
|
5077
|
-
files:
|
|
5078
|
-
blocks:
|
|
5079
|
-
labels:
|
|
5080
|
-
agents:
|
|
5633
|
+
var ManagedManifestSchema = z3.object({
|
|
5634
|
+
version: z3.number().int().positive(),
|
|
5635
|
+
files: z3.array(SafeRepoRelPath),
|
|
5636
|
+
blocks: z3.array(ManagedBlockRecordSchema),
|
|
5637
|
+
labels: z3.array(z3.string()),
|
|
5638
|
+
agents: z3.array(z3.enum(AGENT_VENDORS)).optional()
|
|
5081
5639
|
});
|
|
5082
|
-
var ProjectEntrySchema =
|
|
5083
|
-
name:
|
|
5084
|
-
description:
|
|
5085
|
-
path:
|
|
5640
|
+
var ProjectEntrySchema = z3.object({
|
|
5641
|
+
name: z3.string().min(1),
|
|
5642
|
+
description: z3.string().min(1).optional(),
|
|
5643
|
+
path: z3.string().min(1).optional()
|
|
5086
5644
|
});
|
|
5087
5645
|
function parseTokensCollectDeclaration(value) {
|
|
5088
5646
|
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
@@ -5093,31 +5651,35 @@ function parseTokensCollectDeclaration(value) {
|
|
|
5093
5651
|
return null;
|
|
5094
5652
|
return { interpreter, script };
|
|
5095
5653
|
}
|
|
5096
|
-
var VinayaConfigSchema =
|
|
5097
|
-
rings:
|
|
5098
|
-
ring1_forgeWriteInterception:
|
|
5099
|
-
ring2_asyncAudits:
|
|
5654
|
+
var VinayaConfigSchema = z3.object({
|
|
5655
|
+
rings: z3.object({
|
|
5656
|
+
ring1_forgeWriteInterception: z3.boolean(),
|
|
5657
|
+
ring2_asyncAudits: z3.boolean()
|
|
5100
5658
|
}).optional(),
|
|
5101
|
-
checks:
|
|
5102
|
-
roles:
|
|
5659
|
+
checks: z3.record(z3.string(), CheckEntrySchema).optional(),
|
|
5660
|
+
roles: z3.record(z3.string(), RoleEntrySchema).optional(),
|
|
5103
5661
|
briefSchema: BriefSchemaSchema.optional(),
|
|
5104
5662
|
managed: ManagedManifestSchema.optional(),
|
|
5105
|
-
principals:
|
|
5106
|
-
releaseActor:
|
|
5107
|
-
ci:
|
|
5108
|
-
tokens:
|
|
5109
|
-
collect:
|
|
5663
|
+
principals: z3.array(z3.string()).min(1).optional(),
|
|
5664
|
+
releaseActor: z3.string().min(1).optional(),
|
|
5665
|
+
ci: z3.object({ setup: z3.string().min(1) }).optional(),
|
|
5666
|
+
tokens: z3.object({
|
|
5667
|
+
collect: z3.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
5110
5668
|
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
5111
5669
|
})
|
|
5112
5670
|
}).optional(),
|
|
5113
|
-
blastRadius:
|
|
5114
|
-
proseGates:
|
|
5115
|
-
doctrineRoot:
|
|
5116
|
-
readerFacingPrefix:
|
|
5117
|
-
readerFacingSuffix:
|
|
5118
|
-
legacySlugDir:
|
|
5671
|
+
blastRadius: z3.object({ extraDomains: z3.array(z3.string()).optional() }).optional(),
|
|
5672
|
+
proseGates: z3.object({
|
|
5673
|
+
doctrineRoot: z3.string().min(1).optional(),
|
|
5674
|
+
readerFacingPrefix: z3.string().min(1).optional(),
|
|
5675
|
+
readerFacingSuffix: z3.string().min(1).optional(),
|
|
5676
|
+
legacySlugDir: z3.string().min(1).optional()
|
|
5119
5677
|
}).optional(),
|
|
5120
|
-
projects:
|
|
5678
|
+
projects: z3.array(ProjectEntrySchema).optional(),
|
|
5679
|
+
dispatch: z3.object({
|
|
5680
|
+
timeoutMs: z3.number().int().positive().optional(),
|
|
5681
|
+
agent: z3.enum(["claude", "codex", "gemini"]).optional()
|
|
5682
|
+
}).optional()
|
|
5121
5683
|
});
|
|
5122
5684
|
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
5123
5685
|
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|