@attalabs/vinaya 0.26.0 → 0.28.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 +6 -3
- package/aeg-root/enforcement.md +5 -5
- package/aeg-root/milestone-model.md +2 -0
- package/aeg-root/process.md +1 -1
- package/aeg-root/roles/developer.md +23 -17
- package/aeg-root/roles/planner.md +4 -0
- package/aeg-root/roles/principal.md +8 -2
- package/aeg-root/roles/reviewer.md +9 -5
- package/aeg-root/roles/security.md +9 -5
- package/dist/checks/bin/check-body-bare-digits.js +1038 -257
- package/dist/checks/bin/check-branch-topology.js +963 -199
- package/dist/checks/bin/check-brief-shape.js +1276 -203
- package/dist/checks/bin/check-changeset-coverage.js +1677 -281
- package/dist/checks/bin/check-closes-n.js +967 -203
- package/dist/checks/bin/check-coherence.js +1127 -284
- package/dist/checks/bin/check-dead-branch-push.js +910 -193
- package/dist/checks/bin/check-dispatch-readiness.js +1189 -295
- package/dist/checks/bin/check-doc-coverage-push.js +1673 -277
- package/dist/checks/bin/check-doc-coverage.js +1675 -279
- package/dist/checks/bin/check-doctrine-no-procedures.js +1020 -257
- package/dist/checks/bin/check-doctrine-portability.js +1672 -276
- package/dist/checks/bin/check-evidence-fresh.js +2526 -322
- package/dist/checks/bin/check-exec-bits.js +1670 -274
- package/dist/checks/bin/check-first-push-dispatch.js +1075 -265
- package/dist/checks/bin/check-issue-assignment.js +965 -201
- package/dist/checks/bin/check-issue-milestone-attach.js +5734 -0
- package/dist/checks/bin/check-issue-objectives-numbering.js +5736 -0
- package/dist/checks/bin/check-issue-parts-coverage.js +5736 -0
- package/dist/checks/bin/check-issue-surface-globs.js +6910 -0
- package/dist/checks/bin/check-issue-title-grammar.js +5736 -0
- package/dist/checks/bin/check-issue-tranche-label.js +5736 -0
- package/dist/checks/bin/check-main-branch-refusal.js +910 -193
- package/dist/checks/bin/check-no-disk-state.js +910 -193
- package/dist/checks/bin/check-pr-premise-reassert.js +1020 -257
- package/dist/checks/bin/check-pr-report-density.js +910 -193
- package/dist/checks/bin/check-quoted-command.js +1651 -274
- package/dist/checks/bin/check-reader-resolvable-prose.js +1655 -278
- package/dist/checks/bin/check-registry-gates.js +997 -193
- package/dist/checks/bin/check-retired-vocabulary.js +1649 -272
- package/dist/checks/bin/check-review-gate.js +1163 -316
- package/dist/checks/bin/check-single-plan-pr.js +910 -193
- package/dist/checks/bin/check-surface-scope.js +973 -201
- package/dist/checks/bin/check-test-plan.js +939 -206
- package/dist/checks/bin/check-token-collection-wired.js +910 -193
- package/dist/checks/bin/check-token-report.js +910 -193
- package/dist/checks/bin/check-workspace-escape.js +1668 -272
- package/dist/index.js +10602 -5352
- package/dist/lib/pre-push-changed-files.js +57 -0
- package/dist/lib/pre-push-select-tests.js +1473 -0
- package/package.json +4 -2
|
@@ -306,7 +306,7 @@ function issueListByLabelArgs(owner, repo, label) {
|
|
|
306
306
|
"--state",
|
|
307
307
|
"all",
|
|
308
308
|
"--json",
|
|
309
|
-
"number,title,body,state,labels,milestone",
|
|
309
|
+
"number,title,body,state,labels,milestone,stateReason",
|
|
310
310
|
"--limit",
|
|
311
311
|
"200"
|
|
312
312
|
];
|
|
@@ -652,14 +652,19 @@ function resolveTaskIssueRef(title, labels) {
|
|
|
652
652
|
var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
|
|
653
653
|
var NEXT_HEADING = /^#{1,6}\s+\S/m;
|
|
654
654
|
var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
655
|
-
function
|
|
656
|
-
const text = stripCode(description, { inlineSpans: "keep" });
|
|
655
|
+
function intentsSection(text) {
|
|
657
656
|
const start = text.match(INTENTS_HEADING);
|
|
658
657
|
if (!start || start.index === undefined)
|
|
659
|
-
return
|
|
658
|
+
return null;
|
|
660
659
|
const rest = text.slice(start.index + start[0].length);
|
|
661
660
|
const next = rest.match(NEXT_HEADING);
|
|
662
|
-
|
|
661
|
+
return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
|
|
662
|
+
}
|
|
663
|
+
function intentGoalForSlug(description, slug) {
|
|
664
|
+
const text = stripCode(description, { inlineSpans: "keep" });
|
|
665
|
+
const section = intentsSection(text);
|
|
666
|
+
if (section === null)
|
|
667
|
+
return "";
|
|
663
668
|
for (const line of section.split(`
|
|
664
669
|
`)) {
|
|
665
670
|
const trimmed = line.trim();
|
|
@@ -1951,6 +1956,9 @@ function parseInlineFieldList(section) {
|
|
|
1951
1956
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1952
1957
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1953
1958
|
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1959
|
+
var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
|
|
1960
|
+
var BRIEF_HASH_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Brief hash:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1961
|
+
var POLICY_DIGEST_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Policy digest:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1954
1962
|
function firstFiveLines(comment) {
|
|
1955
1963
|
return comment.split(`
|
|
1956
1964
|
`).slice(0, 5).join(`
|
|
@@ -1964,6 +1972,35 @@ function extractObjectivesVersion(comment) {
|
|
|
1964
1972
|
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1965
1973
|
return m ? m[1].toLowerCase() : null;
|
|
1966
1974
|
}
|
|
1975
|
+
function firstSevenLines(comment) {
|
|
1976
|
+
return comment.split(`
|
|
1977
|
+
`).slice(0, 7).join(`
|
|
1978
|
+
`);
|
|
1979
|
+
}
|
|
1980
|
+
function extractRulingOrdinal(comment) {
|
|
1981
|
+
const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
|
|
1982
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
1983
|
+
}
|
|
1984
|
+
function firstElevenLines(comment) {
|
|
1985
|
+
return comment.split(`
|
|
1986
|
+
`).slice(0, 11).join(`
|
|
1987
|
+
`);
|
|
1988
|
+
}
|
|
1989
|
+
function extractBriefHash(comment) {
|
|
1990
|
+
const m = firstElevenLines(comment).match(BRIEF_HASH_PATTERN);
|
|
1991
|
+
return m ? m[1].toLowerCase() : null;
|
|
1992
|
+
}
|
|
1993
|
+
function extractPolicyDigest(comment) {
|
|
1994
|
+
const m = firstElevenLines(comment).match(POLICY_DIGEST_PATTERN);
|
|
1995
|
+
return m ? m[1].toLowerCase() : null;
|
|
1996
|
+
}
|
|
1997
|
+
var FINDING_SEVERITY_LINE = /^\d+\.\s+\[([A-Z][A-Z]*)\]\s+(.+?)\s+—/gm;
|
|
1998
|
+
function extractFindingSeverities(comment) {
|
|
1999
|
+
return [...comment.matchAll(FINDING_SEVERITY_LINE)].map((m) => ({
|
|
2000
|
+
severity: m[1],
|
|
2001
|
+
location: m[2]
|
|
2002
|
+
}));
|
|
2003
|
+
}
|
|
1967
2004
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1968
2005
|
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1969
2006
|
if (candidates.length === 0) {
|
|
@@ -1971,6 +2008,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1971
2008
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1972
2009
|
headSha: null,
|
|
1973
2010
|
objectivesVersion: null,
|
|
2011
|
+
rulingOrdinal: null,
|
|
2012
|
+
briefHash: null,
|
|
2013
|
+
policyDigest: null,
|
|
2014
|
+
findingSeverities: [],
|
|
1974
2015
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1975
2016
|
};
|
|
1976
2017
|
}
|
|
@@ -1981,6 +2022,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1981
2022
|
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1982
2023
|
headSha: null,
|
|
1983
2024
|
objectivesVersion: null,
|
|
2025
|
+
rulingOrdinal: null,
|
|
2026
|
+
briefHash: null,
|
|
2027
|
+
policyDigest: null,
|
|
2028
|
+
findingSeverities: [],
|
|
1984
2029
|
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1985
2030
|
};
|
|
1986
2031
|
}
|
|
@@ -1988,6 +2033,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1988
2033
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1989
2034
|
headSha: extractHeadSha(latest),
|
|
1990
2035
|
objectivesVersion: extractObjectivesVersion(latest),
|
|
2036
|
+
rulingOrdinal: extractRulingOrdinal(latest),
|
|
2037
|
+
briefHash: extractBriefHash(latest),
|
|
2038
|
+
policyDigest: extractPolicyDigest(latest),
|
|
2039
|
+
findingSeverities: extractFindingSeverities(latest),
|
|
1991
2040
|
danglingNote: null
|
|
1992
2041
|
};
|
|
1993
2042
|
}
|
|
@@ -2323,6 +2372,53 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
|
|
|
2323
2372
|
}
|
|
2324
2373
|
return out;
|
|
2325
2374
|
}
|
|
2375
|
+
// ../../packages/aeg-core/src/review-policy.ts
|
|
2376
|
+
var CODE_REVIEW_SEVERITY_ORDER = ["BLOCKER", "MAJOR", "MINOR"];
|
|
2377
|
+
var SECURITY_SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
2378
|
+
var DEFAULT_MAX_ROUNDS = 3;
|
|
2379
|
+
var DEFAULT_REVIEW_POLICY = {
|
|
2380
|
+
codeReviewThreshold: "BLOCKER",
|
|
2381
|
+
securityThreshold: "HIGH",
|
|
2382
|
+
maxRounds: DEFAULT_MAX_ROUNDS
|
|
2383
|
+
};
|
|
2384
|
+
var FILE_SHAPED_LOCATION = /\.[a-zA-Z0-9]{1,10}(:\d+)?\s*$/;
|
|
2385
|
+
var PROSE_LOCATION_PATTERNS = [/\bpr\s*body\b/i, /\bcomment\b/i];
|
|
2386
|
+
var ROLE_FILE_LOCATION = /(^|\/)aeg-root\/roles\//i;
|
|
2387
|
+
function isProseLocation(location) {
|
|
2388
|
+
if (ROLE_FILE_LOCATION.test(location))
|
|
2389
|
+
return true;
|
|
2390
|
+
if (FILE_SHAPED_LOCATION.test(location))
|
|
2391
|
+
return false;
|
|
2392
|
+
return PROSE_LOCATION_PATTERNS.some((pattern) => pattern.test(location));
|
|
2393
|
+
}
|
|
2394
|
+
var PROSE_CAP_SEVERITY = "MINOR";
|
|
2395
|
+
function blockingSeverities(scale, threshold) {
|
|
2396
|
+
const idx = scale.indexOf(threshold);
|
|
2397
|
+
if (idx === -1) {
|
|
2398
|
+
throw new Error(`blockingSeverities: threshold "${threshold}" is not one of ${scale.join(" > ")}`);
|
|
2399
|
+
}
|
|
2400
|
+
return scale.slice(0, idx + 1);
|
|
2401
|
+
}
|
|
2402
|
+
function evaluateReviewFindings(findings, scale, threshold) {
|
|
2403
|
+
const blocking = new Set(blockingSeverities(scale, threshold));
|
|
2404
|
+
const blockingFindings = findings.filter((f) => {
|
|
2405
|
+
if (!scale.includes(f.severity)) {
|
|
2406
|
+
throw new Error(`evaluateReviewFindings: severity "${f.severity}" is not one of ${scale.join(" > ")}`);
|
|
2407
|
+
}
|
|
2408
|
+
const effectiveSeverity = f.location !== undefined && isProseLocation(f.location) ? PROSE_CAP_SEVERITY : f.severity;
|
|
2409
|
+
return blocking.has(effectiveSeverity);
|
|
2410
|
+
});
|
|
2411
|
+
return { outcome: blockingFindings.length > 0 ? "blocked" : "clean", blockingFindings };
|
|
2412
|
+
}
|
|
2413
|
+
function evaluateCodeReview(findings, policy) {
|
|
2414
|
+
return evaluateReviewFindings(findings, CODE_REVIEW_SEVERITY_ORDER, policy.codeReviewThreshold);
|
|
2415
|
+
}
|
|
2416
|
+
function evaluateSecurityReview(findings, policy) {
|
|
2417
|
+
return evaluateReviewFindings(findings, SECURITY_SEVERITY_ORDER, policy.securityThreshold);
|
|
2418
|
+
}
|
|
2419
|
+
function isKnownSeverity(scale, value) {
|
|
2420
|
+
return scale.includes(value);
|
|
2421
|
+
}
|
|
2326
2422
|
// ../../packages/aeg-core/src/pr-tier.ts
|
|
2327
2423
|
var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
|
|
2328
2424
|
function readTierFromPrBody(prBody) {
|
|
@@ -2349,6 +2445,9 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2349
2445
|
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2350
2446
|
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2351
2447
|
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2448
|
+
function maskedForHeadingSearch(body) {
|
|
2449
|
+
return maskDetailsBlocks(maskCode(body));
|
|
2450
|
+
}
|
|
2352
2451
|
function hasBacktickedPath(text) {
|
|
2353
2452
|
let i = 0;
|
|
2354
2453
|
while (i < text.length) {
|
|
@@ -2371,16 +2470,22 @@ function stripObjectiveBackticks(text) {
|
|
|
2371
2470
|
function wordCount(text) {
|
|
2372
2471
|
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2373
2472
|
}
|
|
2374
|
-
function
|
|
2375
|
-
const
|
|
2473
|
+
function objectivesSectionBounds(body) {
|
|
2474
|
+
const masked = maskedForHeadingSearch(body);
|
|
2475
|
+
const heading = HEADING_RE.exec(masked);
|
|
2376
2476
|
if (!heading)
|
|
2377
2477
|
return null;
|
|
2378
|
-
const
|
|
2478
|
+
const start = heading.index + heading[0].length;
|
|
2479
|
+
const afterHeading = masked.slice(start);
|
|
2379
2480
|
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2380
|
-
return next ?
|
|
2481
|
+
return { start, end: next ? start + next.index : body.length };
|
|
2482
|
+
}
|
|
2483
|
+
function objectivesSectionText(body) {
|
|
2484
|
+
const bounds = objectivesSectionBounds(body);
|
|
2485
|
+
return bounds === null ? null : body.slice(bounds.start, bounds.end);
|
|
2381
2486
|
}
|
|
2382
2487
|
function hasObjectivesHeading(body) {
|
|
2383
|
-
return HEADING_RE.test(body);
|
|
2488
|
+
return HEADING_RE.test(maskedForHeadingSearch(body));
|
|
2384
2489
|
}
|
|
2385
2490
|
function objectivesOf(body) {
|
|
2386
2491
|
const section = objectivesSectionText(body);
|
|
@@ -2440,6 +2545,15 @@ function isIssueNotFoundError(err) {
|
|
|
2440
2545
|
`);
|
|
2441
2546
|
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2442
2547
|
}
|
|
2548
|
+
function resolveObjectivesSource(prBody, issue, cutoverIssue) {
|
|
2549
|
+
if (issue !== null && issue < cutoverIssue)
|
|
2550
|
+
return { kind: "none" };
|
|
2551
|
+
if (issue !== null)
|
|
2552
|
+
return { kind: "issue", issue };
|
|
2553
|
+
if (hasObjectivesHeading(prBody))
|
|
2554
|
+
return { kind: "body" };
|
|
2555
|
+
return { kind: "none" };
|
|
2556
|
+
}
|
|
2443
2557
|
|
|
2444
2558
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2445
2559
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -2675,7 +2789,7 @@ function checkForField(prBody) {
|
|
|
2675
2789
|
]
|
|
2676
2790
|
};
|
|
2677
2791
|
}
|
|
2678
|
-
function
|
|
2792
|
+
function checkClosesNPresence(prBody) {
|
|
2679
2793
|
const closesPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s{0,8}:?\s{0,8}#\d+/i;
|
|
2680
2794
|
if (closesPattern.test(stripCode(prBody))) {
|
|
2681
2795
|
return { status: "pass", errors: [] };
|
|
@@ -2707,10 +2821,22 @@ var COMMIT_TYPES = [
|
|
|
2707
2821
|
"Test"
|
|
2708
2822
|
];
|
|
2709
2823
|
var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
|
|
2824
|
+
function checkForgeTitle(title) {
|
|
2825
|
+
const taskStyle = /^\[[a-z0-9._-]+\] \S+ — \S/;
|
|
2826
|
+
if (COMMIT_TYPE_STYLE.test(title) || taskStyle.test(title))
|
|
2827
|
+
return { status: "pass", errors: [] };
|
|
2828
|
+
return {
|
|
2829
|
+
status: "fail",
|
|
2830
|
+
errors: [
|
|
2831
|
+
`brief-validation title: "${title}" matches neither title grammar — expected \`Type: description\` / \`Type(scope): description\` (commitlint types + Plan) or \`[tranche] id — description\` (task form).`
|
|
2832
|
+
]
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2710
2835
|
var BRIEF_SHAPE_MARKERS = [checkSurfaceMap, checkDocUpdateList, checkStopConditions, checkAutonomyClause];
|
|
2711
2836
|
var TASK_BRANCH_PATTERN = /^task\/[^/]+\/[^/]+$/;
|
|
2837
|
+
var TASK_ISSUE_BRANCH_PATTERN = /^task\/issue-\d+$/;
|
|
2712
2838
|
function isTaskBranch(branch) {
|
|
2713
|
-
return TASK_BRANCH_PATTERN.test(branch);
|
|
2839
|
+
return TASK_BRANCH_PATTERN.test(branch) || TASK_ISSUE_BRANCH_PATTERN.test(branch);
|
|
2714
2840
|
}
|
|
2715
2841
|
function isBriefShaped(prBody) {
|
|
2716
2842
|
const stripped = stripCode(prBody);
|
|
@@ -2832,8 +2958,9 @@ function packagesNamedIn(text) {
|
|
|
2832
2958
|
}
|
|
2833
2959
|
function hasTestPathForConsumer(text, consumerDir) {
|
|
2834
2960
|
const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2835
|
-
const
|
|
2836
|
-
|
|
2961
|
+
const filePathRe = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
|
|
2962
|
+
const testDirRe = new RegExp(`${escaped}\\/(?:[\\w-]+\\/)*(?:tests|specs)(?:\\/|\\b)`);
|
|
2963
|
+
return filePathRe.test(text) || testDirRe.test(text);
|
|
2837
2964
|
}
|
|
2838
2965
|
function checkConsumerTests(prBody, consumersOf) {
|
|
2839
2966
|
const section4 = extractNumberedSection(prBody, 4);
|
|
@@ -2937,21 +3064,44 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2937
3064
|
checkConsumerTests(prBody, consumersOf),
|
|
2938
3065
|
checkDefeatCases(prBody),
|
|
2939
3066
|
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2940
|
-
...requireClosesN ? [
|
|
3067
|
+
...requireClosesN ? [checkClosesNPresence(prBody)] : []
|
|
2941
3068
|
];
|
|
2942
3069
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2943
3070
|
}
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
`
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
3071
|
+
function contentAfterNLines(body, n) {
|
|
3072
|
+
let idx = -1;
|
|
3073
|
+
for (let i = 0;i < n; i++) {
|
|
3074
|
+
idx = body.indexOf(`
|
|
3075
|
+
`, idx + 1);
|
|
3076
|
+
if (idx === -1)
|
|
3077
|
+
return "";
|
|
3078
|
+
}
|
|
3079
|
+
return body.slice(idx + 1);
|
|
3080
|
+
}
|
|
3081
|
+
var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
|
|
3082
|
+
function parseBriefMarkerVersion(firstLine) {
|
|
3083
|
+
const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
|
|
3084
|
+
if (!m)
|
|
3085
|
+
return null;
|
|
3086
|
+
const version = Number.parseInt(m[1], 10);
|
|
3087
|
+
return Number.isInteger(version) && version >= 1 ? version : null;
|
|
3088
|
+
}
|
|
3089
|
+
function frozenBriefContent(body, version) {
|
|
3090
|
+
return contentAfterNLines(body, version === 1 ? 2 : 3);
|
|
3091
|
+
}
|
|
3092
|
+
function resolveNewestFrozenBrief(comments, allowlist) {
|
|
3093
|
+
let best = null;
|
|
3094
|
+
for (const c of comments) {
|
|
3095
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3096
|
+
continue;
|
|
3097
|
+
const version = parseBriefMarkerVersion(c.body.split(`
|
|
3098
|
+
`)[0] ?? "");
|
|
3099
|
+
if (version === null)
|
|
3100
|
+
continue;
|
|
3101
|
+
if (best === null || version > best.version)
|
|
3102
|
+
best = { ...c, version };
|
|
3103
|
+
}
|
|
3104
|
+
return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
|
|
2955
3105
|
}
|
|
2956
3106
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2957
3107
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
@@ -3204,6 +3354,28 @@ function topLevelSectionText(body, headingName) {
|
|
|
3204
3354
|
const next = /^##[ \t]/m.exec(afterHeading);
|
|
3205
3355
|
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
3206
3356
|
}
|
|
3357
|
+
function partHasBacktickedPath(text) {
|
|
3358
|
+
let i = 0;
|
|
3359
|
+
while (i < text.length) {
|
|
3360
|
+
const start = text.indexOf("`", i);
|
|
3361
|
+
if (start === -1)
|
|
3362
|
+
return false;
|
|
3363
|
+
const end = text.indexOf("`", start + 1);
|
|
3364
|
+
if (end === -1)
|
|
3365
|
+
return false;
|
|
3366
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
3367
|
+
return true;
|
|
3368
|
+
i = end + 1;
|
|
3369
|
+
}
|
|
3370
|
+
return false;
|
|
3371
|
+
}
|
|
3372
|
+
var PART_MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
3373
|
+
function stripPartBackticks(text) {
|
|
3374
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
3375
|
+
}
|
|
3376
|
+
function partWordCount(text) {
|
|
3377
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
3378
|
+
}
|
|
3207
3379
|
function looksLikeFilePath(entry2) {
|
|
3208
3380
|
const stripped = entry2.replace(/\/\*\*?$/, "");
|
|
3209
3381
|
const lastSegment = stripped.split("/").pop() ?? stripped;
|
|
@@ -3244,6 +3416,18 @@ function globCoversPath(glob, path) {
|
|
|
3244
3416
|
const p = path.replace(/\/+$/, "");
|
|
3245
3417
|
return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
|
|
3246
3418
|
}
|
|
3419
|
+
function checkSurfaceGlobsResolve(body, resolvesToFile) {
|
|
3420
|
+
const surface = parseIssueSurface(body);
|
|
3421
|
+
if (!surface.ok)
|
|
3422
|
+
return { status: "pass", errors: [] };
|
|
3423
|
+
const errors = [];
|
|
3424
|
+
for (const glob of surface.value.in) {
|
|
3425
|
+
if (!resolvesToFile(glob)) {
|
|
3426
|
+
errors.push(`issue-validation Surface: \`${glob}\` in \`## Surface\`'s \`in:\` list matches no tracked file — a Surface that cannot resolve cannot render a brief's file list.`);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3430
|
+
}
|
|
3247
3431
|
function checkSurfaceScope(changedFiles, outGlobs) {
|
|
3248
3432
|
if (outGlobs.length === 0)
|
|
3249
3433
|
return { ok: true };
|
|
@@ -3255,9 +3439,77 @@ function checkSurfaceScope(changedFiles, outGlobs) {
|
|
|
3255
3439
|
}
|
|
3256
3440
|
return violations.length > 0 ? { ok: false, violations } : { ok: true };
|
|
3257
3441
|
}
|
|
3442
|
+
var ISSUE_PART_LINE_RE = /^Part\s+(\d+)\s*\(([^)]*)\)\s*[-—–]\s*(.*)$/i;
|
|
3443
|
+
function parseIssueParts(body) {
|
|
3444
|
+
const section = topLevelSectionText(body, "Parts");
|
|
3445
|
+
if (section === null)
|
|
3446
|
+
return { ok: false, errors: ["no `## Parts` heading found in the body."] };
|
|
3447
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
3448
|
+
const parts = [];
|
|
3449
|
+
const errors = [];
|
|
3450
|
+
for (const line of lines) {
|
|
3451
|
+
const m = ISSUE_PART_LINE_RE.exec(line);
|
|
3452
|
+
if (!m) {
|
|
3453
|
+
errors.push(`"${line}" is not a well-formed Parts line — expected \`Part <n> (<refs>) — <outcome>\`.`);
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
const n = Number.parseInt(m[1], 10);
|
|
3457
|
+
const refs = m[2];
|
|
3458
|
+
const text = m[3].trim();
|
|
3459
|
+
if (text.length === 0) {
|
|
3460
|
+
errors.push(`Part ${n} has no outcome text after the dash — every Part states one observable outcome.`);
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3463
|
+
if (partHasBacktickedPath(text) && partWordCount(stripPartBackticks(text)) < PART_MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
3464
|
+
errors.push(`Part ${n} is little more than a file path — a Part names an outcome and symbols, never a bare path.`);
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3467
|
+
const objectiveIds = [...refs.matchAll(/O(\d+)/g)].map((r) => Number.parseInt(r[1], 10));
|
|
3468
|
+
parts.push({ n, objectiveIds, text });
|
|
3469
|
+
}
|
|
3470
|
+
if (parts.length === 0) {
|
|
3471
|
+
errors.push("the `## Parts` section has no well-formed `Part <n> (<refs>) — <outcome>` lines.");
|
|
3472
|
+
}
|
|
3473
|
+
if (errors.length > 0)
|
|
3474
|
+
return { ok: false, errors };
|
|
3475
|
+
return { ok: true, value: parts };
|
|
3476
|
+
}
|
|
3477
|
+
function checkPartsCiteDefinedObjectives(body) {
|
|
3478
|
+
const parts = parseIssueParts(body);
|
|
3479
|
+
const objectives = objectivesOf(body);
|
|
3480
|
+
if (!parts.ok || !objectives.ok)
|
|
3481
|
+
return { status: "pass", errors: [] };
|
|
3482
|
+
const definedIds = new Set(objectives.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
3483
|
+
const errors = [];
|
|
3484
|
+
for (const part of parts.value) {
|
|
3485
|
+
for (const objectiveId of part.objectiveIds) {
|
|
3486
|
+
if (!definedIds.has(objectiveId)) {
|
|
3487
|
+
errors.push(`issue-validation Parts: Part ${part.n} cites O${objectiveId}, which the Issue's own \`## Objectives\` section does not define.`);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3492
|
+
}
|
|
3258
3493
|
function isTaskIssueLabelSet(labels) {
|
|
3259
3494
|
return hasLabel("tranche", labels);
|
|
3260
3495
|
}
|
|
3496
|
+
function checkTrancheLabelPresence(_body, _labels) {
|
|
3497
|
+
return { status: "pass", errors: [] };
|
|
3498
|
+
}
|
|
3499
|
+
function checkMilestoneAttach(labels, currentMilestoneTitle, resolvedMilestoneTitle) {
|
|
3500
|
+
if (!isTaskIssueLabelSet(labels))
|
|
3501
|
+
return { status: "pass", errors: [] };
|
|
3502
|
+
if (resolvedMilestoneTitle === null)
|
|
3503
|
+
return { status: "pass", errors: [] };
|
|
3504
|
+
if (currentMilestoneTitle === resolvedMilestoneTitle)
|
|
3505
|
+
return { status: "pass", errors: [] };
|
|
3506
|
+
return {
|
|
3507
|
+
status: "fail",
|
|
3508
|
+
errors: [
|
|
3509
|
+
`issue-validation milestone attach: this task Issue's tranche label resolves to Milestone "${resolvedMilestoneTitle}", but its live Milestone is ${currentMilestoneTitle === null ? "unset" : `"${currentMilestoneTitle}"`} — attach it to "${resolvedMilestoneTitle}".`
|
|
3510
|
+
]
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3261
3513
|
var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
|
|
3262
3514
|
function isControlCodePoint(codePoint) {
|
|
3263
3515
|
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
@@ -3322,6 +3574,19 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
|
|
|
3322
3574
|
var DOC_PATH_RE = /(?:(?:aeg-root|apps|packages|specs|docs|tools|\.claude|\.github)\/[\w./@-]*\.(?:md|mdx)|\.claude\/(?:skills|rules)\/[\w./-]+|\b(?:docs-index|decisions|projects|state-machine|enforcement|process|README|CLAUDE)\.md\b|\b[\w-]+-(?:spec|decisions|backlog)\.md\b)/i;
|
|
3323
3575
|
var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
|
|
3324
3576
|
|
|
3577
|
+
// ../../packages/aeg-core/src/task-branch-identity.ts
|
|
3578
|
+
var ISSUE_BRANCH_PATTERN = /^task\/issue-(\d+)$/;
|
|
3579
|
+
var TRANCHE_BRANCH_PATTERN = /^task\/([^/]+)\/([^/]+)$/;
|
|
3580
|
+
function parseTaskBranchIdentity(branch) {
|
|
3581
|
+
const issueMatch = ISSUE_BRANCH_PATTERN.exec(branch);
|
|
3582
|
+
if (issueMatch)
|
|
3583
|
+
return { kind: "issue", issueNumber: Number(issueMatch[1]) };
|
|
3584
|
+
const trancheMatch = TRANCHE_BRANCH_PATTERN.exec(branch);
|
|
3585
|
+
if (trancheMatch)
|
|
3586
|
+
return { kind: "tranche", tranche: trancheMatch[1], taskId: trancheMatch[2] };
|
|
3587
|
+
return null;
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3325
3590
|
// ../../packages/aeg-core/src/coherence-checks.ts
|
|
3326
3591
|
var COHERENCE_ENFORCED_FROM = "2026-07-01";
|
|
3327
3592
|
function isGrandfathered(isoDate) {
|
|
@@ -3342,6 +3607,7 @@ function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
|
|
|
3342
3607
|
if (handClosed)
|
|
3343
3608
|
continue;
|
|
3344
3609
|
failures.push({
|
|
3610
|
+
code: "closed-without-merge",
|
|
3345
3611
|
issue: e.task.issue,
|
|
3346
3612
|
tranche: e.trancheSlug,
|
|
3347
3613
|
task: e.task.id,
|
|
@@ -3366,6 +3632,7 @@ function checkA3(entries) {
|
|
|
3366
3632
|
continue;
|
|
3367
3633
|
if (e.facts.prState === "merged" && e.facts.issueState !== "closed") {
|
|
3368
3634
|
failures.push({
|
|
3635
|
+
code: "auto-close-misfire",
|
|
3369
3636
|
issue: e.task.issue,
|
|
3370
3637
|
tranche: e.trancheSlug,
|
|
3371
3638
|
task: e.task.id,
|
|
@@ -3390,6 +3657,7 @@ function checkT1(entries) {
|
|
|
3390
3657
|
continue;
|
|
3391
3658
|
if (e.facts === undefined) {
|
|
3392
3659
|
failures.push({
|
|
3660
|
+
code: "phantom-issue-ref",
|
|
3393
3661
|
issue: e.task.issue,
|
|
3394
3662
|
tranche: e.trancheSlug,
|
|
3395
3663
|
task: e.task.id,
|
|
@@ -3408,6 +3676,7 @@ function checkT2(openIssuesBySlug, topologyIssuesBySlug, ciTrancheSlug) {
|
|
|
3408
3676
|
for (const num of openNums) {
|
|
3409
3677
|
if (!topologySet.has(num)) {
|
|
3410
3678
|
failures.push({
|
|
3679
|
+
code: "orphan-task",
|
|
3411
3680
|
issue: num,
|
|
3412
3681
|
tranche: slug,
|
|
3413
3682
|
reason: `Issue #${num} is open and labeled ${trancheLabel(slug)} but does not appear in the topology file`
|
|
@@ -3442,6 +3711,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
|
|
|
3442
3711
|
continue;
|
|
3443
3712
|
if (forgeUnavailableSlugs?.has(e.trancheSlug)) {
|
|
3444
3713
|
failures.push({
|
|
3714
|
+
code: "tbd-in-active-tranche",
|
|
3445
3715
|
issue: null,
|
|
3446
3716
|
tranche: e.trancheSlug,
|
|
3447
3717
|
task: e.task.id,
|
|
@@ -3451,6 +3721,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
|
|
|
3451
3721
|
continue;
|
|
3452
3722
|
}
|
|
3453
3723
|
failures.push({
|
|
3724
|
+
code: "tbd-in-active-tranche",
|
|
3454
3725
|
issue: null,
|
|
3455
3726
|
tranche: e.trancheSlug,
|
|
3456
3727
|
task: e.task.id,
|
|
@@ -3483,6 +3754,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
3483
3754
|
const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
|
|
3484
3755
|
if (sameTask || sameIssue) {
|
|
3485
3756
|
failures.push({
|
|
3757
|
+
code: "d1-self-dependency",
|
|
3486
3758
|
issue: e.task.issue,
|
|
3487
3759
|
tranche: e.trancheSlug,
|
|
3488
3760
|
task: e.task.id,
|
|
@@ -3494,6 +3766,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
3494
3766
|
const depClosed = depFacts?.issueState === "closed";
|
|
3495
3767
|
if (!depClosed) {
|
|
3496
3768
|
failures.push({
|
|
3769
|
+
code: "dispatched-on-unmet-deps",
|
|
3497
3770
|
issue: e.task.issue,
|
|
3498
3771
|
tranche: e.trancheSlug,
|
|
3499
3772
|
task: e.task.id,
|
|
@@ -3519,11 +3792,13 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3519
3792
|
const errors = [
|
|
3520
3793
|
...checkIssueRationale(issue.body).errors,
|
|
3521
3794
|
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3522
|
-
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3795
|
+
...checkIssueObjectives(issue.body, issue.number).errors,
|
|
3796
|
+
...checkPartsCiteDefinedObjectives(issue.body).errors
|
|
3523
3797
|
];
|
|
3524
3798
|
if (errors.length === 0)
|
|
3525
3799
|
continue;
|
|
3526
3800
|
failures.push({
|
|
3801
|
+
code: "missing-rationale-field",
|
|
3527
3802
|
issue: issue.number,
|
|
3528
3803
|
tranche: slug,
|
|
3529
3804
|
reason: `Issue #${issue.number} fails the rationale gate: ${errors.join(" | ")}`,
|
|
@@ -3552,6 +3827,7 @@ function checkL1(files, entriesBySlug) {
|
|
|
3552
3827
|
const allClosed = withFacts.every((e) => e.facts?.issueState === "closed");
|
|
3553
3828
|
if (allClosed) {
|
|
3554
3829
|
failures.push({
|
|
3830
|
+
code: "archive-recommended",
|
|
3555
3831
|
tranche: f.slug,
|
|
3556
3832
|
reason: "Active tranche has no open task-Issues — consider archiving to completed/"
|
|
3557
3833
|
});
|
|
@@ -3583,27 +3859,38 @@ function extractClosesReferences(prBody) {
|
|
|
3583
3859
|
}
|
|
3584
3860
|
return referenced;
|
|
3585
3861
|
}
|
|
3586
|
-
function
|
|
3862
|
+
function checkClosesNTopology(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
3587
3863
|
const referenced = extractClosesReferences(prBody);
|
|
3588
3864
|
if (taskIssueRefs) {
|
|
3589
3865
|
for (const n of referenced) {
|
|
3590
|
-
const
|
|
3591
|
-
if (!
|
|
3866
|
+
const ref2 = taskIssueRefs.get(n);
|
|
3867
|
+
if (!ref2)
|
|
3592
3868
|
continue;
|
|
3593
|
-
const expectedBranch = `task/${
|
|
3869
|
+
const expectedBranch = `task/${ref2.trancheSlug}/${ref2.taskId}`;
|
|
3594
3870
|
if (branch !== expectedBranch) {
|
|
3595
3871
|
return {
|
|
3596
3872
|
ok: false,
|
|
3597
|
-
message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${
|
|
3873
|
+
message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${ref2.taskId} of tranche "${ref2.trancheSlug}") but is not named "${expectedBranch}" — rename the branch and re-push, or if this work is intentionally outside AEG's dispatch flow, remove the Closes reference.`
|
|
3598
3874
|
};
|
|
3599
3875
|
}
|
|
3600
3876
|
}
|
|
3601
3877
|
}
|
|
3602
|
-
const
|
|
3603
|
-
if (!
|
|
3878
|
+
const ref = parseTaskBranchIdentity(branch);
|
|
3879
|
+
if (!ref)
|
|
3604
3880
|
return { ok: true };
|
|
3605
|
-
|
|
3606
|
-
|
|
3881
|
+
if (ref.kind === "issue") {
|
|
3882
|
+
const expectedIssue2 = ref.issueNumber;
|
|
3883
|
+
if (!referenced.has(expectedIssue2)) {
|
|
3884
|
+
return {
|
|
3885
|
+
ok: false,
|
|
3886
|
+
expectedIssue: expectedIssue2,
|
|
3887
|
+
message: `closes-n: PR body does not contain \`Closes #${expectedIssue2}\` (required for branch "${branch}"). Add it to the PR body Summary section.`
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
return { ok: true, expectedIssue: expectedIssue2 };
|
|
3891
|
+
}
|
|
3892
|
+
const trancheSlug = ref.tranche;
|
|
3893
|
+
const taskId = ref.taskId;
|
|
3607
3894
|
const trancheFile = trancheFiles.find((f) => f.slug === trancheSlug);
|
|
3608
3895
|
if (!trancheFile) {
|
|
3609
3896
|
return {
|
|
@@ -3655,29 +3942,67 @@ function extractIssue(body) {
|
|
|
3655
3942
|
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3656
3943
|
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3657
3944
|
}
|
|
3658
|
-
// ../../packages/aeg-core/src/review-
|
|
3659
|
-
|
|
3660
|
-
|
|
3945
|
+
// ../../packages/aeg-core/src/review-input-manifest.ts
|
|
3946
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3947
|
+
function briefHash(brief) {
|
|
3948
|
+
return createHash4("sha256").update(`${brief}
|
|
3949
|
+
`).digest("hex");
|
|
3950
|
+
}
|
|
3951
|
+
function policyDigest(policy) {
|
|
3952
|
+
return createHash4("sha256").update(JSON.stringify({ codeReviewThreshold: policy.codeReviewThreshold, securityThreshold: policy.securityThreshold })).digest("hex");
|
|
3953
|
+
}
|
|
3954
|
+
function isBoundToHead(echoed, headSha) {
|
|
3955
|
+
if (!echoed.headSha)
|
|
3661
3956
|
return false;
|
|
3662
|
-
return headSha.toLowerCase().startsWith(
|
|
3957
|
+
return headSha.toLowerCase().startsWith(echoed.headSha.toLowerCase());
|
|
3663
3958
|
}
|
|
3664
|
-
function isBoundByPatchIdentity(
|
|
3665
|
-
if (patchIdOf === undefined || !
|
|
3959
|
+
function isBoundByPatchIdentity(echoed, headSha, patchIdOf) {
|
|
3960
|
+
if (patchIdOf === undefined || !echoed.headSha)
|
|
3666
3961
|
return false;
|
|
3667
|
-
const judged = patchIdOf(
|
|
3962
|
+
const judged = patchIdOf(echoed.headSha);
|
|
3668
3963
|
const current = patchIdOf(headSha);
|
|
3669
3964
|
if (judged === null || current === null)
|
|
3670
3965
|
return false;
|
|
3671
3966
|
return judged === current;
|
|
3672
3967
|
}
|
|
3673
|
-
function isBoundToPatch(
|
|
3674
|
-
return isBoundToHead(
|
|
3968
|
+
function isBoundToPatch(echoed, headSha, patchIdOf) {
|
|
3969
|
+
return isBoundToHead(echoed, headSha) || isBoundByPatchIdentity(echoed, headSha, patchIdOf);
|
|
3675
3970
|
}
|
|
3676
|
-
function isBoundToObjectives(
|
|
3971
|
+
function isBoundToObjectives(echoed, currentVersion) {
|
|
3677
3972
|
if (currentVersion === null)
|
|
3678
3973
|
return true;
|
|
3679
|
-
return
|
|
3974
|
+
return echoed.objectivesVersion === currentVersion;
|
|
3975
|
+
}
|
|
3976
|
+
function isBoundToRulings(echoed, currentOrdinal) {
|
|
3977
|
+
if (echoed.rulingOrdinal === null)
|
|
3978
|
+
return currentOrdinal === 0;
|
|
3979
|
+
return echoed.rulingOrdinal === currentOrdinal;
|
|
3980
|
+
}
|
|
3981
|
+
function isBoundToBriefHash(echoed, currentHash) {
|
|
3982
|
+
if (currentHash === null)
|
|
3983
|
+
return true;
|
|
3984
|
+
return echoed.briefHash === currentHash;
|
|
3985
|
+
}
|
|
3986
|
+
function isBoundToPolicy(echoed, currentDigest) {
|
|
3987
|
+
return echoed.policyDigest === currentDigest;
|
|
3988
|
+
}
|
|
3989
|
+
function compareManifest(echoed, current, patchIdOf) {
|
|
3990
|
+
const head = isBoundToPatch(echoed, current.headSha, patchIdOf);
|
|
3991
|
+
const briefHashBound = isBoundToBriefHash(echoed, current.briefHash);
|
|
3992
|
+
const objectivesVersion2 = isBoundToObjectives(echoed, current.objectivesVersion);
|
|
3993
|
+
const rulingOrdinal = isBoundToRulings(echoed, current.rulingOrdinal);
|
|
3994
|
+
const policyDigestBound = isBoundToPolicy(echoed, current.policyDigest);
|
|
3995
|
+
return {
|
|
3996
|
+
bound: head && briefHashBound && objectivesVersion2 && rulingOrdinal && policyDigestBound,
|
|
3997
|
+
head,
|
|
3998
|
+
briefHash: briefHashBound,
|
|
3999
|
+
objectivesVersion: objectivesVersion2,
|
|
4000
|
+
rulingOrdinal,
|
|
4001
|
+
policyDigest: policyDigestBound
|
|
4002
|
+
};
|
|
3680
4003
|
}
|
|
4004
|
+
|
|
4005
|
+
// ../../packages/aeg-core/src/review-gate.ts
|
|
3681
4006
|
function checkReviewGate(input) {
|
|
3682
4007
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3683
4008
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3700,13 +4025,55 @@ function checkReviewGate(input) {
|
|
|
3700
4025
|
const verifiedBodies = verified.map((c) => c.body);
|
|
3701
4026
|
const codeReview = extractCodeReviewVerdict(verifiedBodies);
|
|
3702
4027
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
3703
|
-
const
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
4028
|
+
const policy = input.policy ?? DEFAULT_REVIEW_POLICY;
|
|
4029
|
+
let codeReviewPolicyEvaluation;
|
|
4030
|
+
let securityPolicyEvaluation;
|
|
4031
|
+
try {
|
|
4032
|
+
codeReviewPolicyEvaluation = evaluateCodeReview(codeReview.findingSeverities, policy);
|
|
4033
|
+
securityPolicyEvaluation = evaluateSecurityReview(security.findingSeverities, policy);
|
|
4034
|
+
} catch (err) {
|
|
4035
|
+
return {
|
|
4036
|
+
verdict: "fail",
|
|
4037
|
+
reason: `a verdict comment carries a finding severity this repository's policy does not recognize: ${err instanceof Error ? err.message : String(err)}`,
|
|
4038
|
+
waived: false
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
4041
|
+
const codeReviewTextClean = codeReview.value === "APPROVE";
|
|
4042
|
+
const securityTextClean = security.value === "PASS";
|
|
4043
|
+
const codeReviewPolicyClean = codeReviewPolicyEvaluation.outcome === "clean";
|
|
4044
|
+
const securityPolicyClean = securityPolicyEvaluation.outcome === "clean";
|
|
4045
|
+
const codeReviewClean = codeReviewTextClean && codeReviewPolicyClean;
|
|
4046
|
+
const securityClean = securityTextClean && securityPolicyClean;
|
|
4047
|
+
const currentManifest = {
|
|
4048
|
+
headSha: input.headSha,
|
|
4049
|
+
briefHash: input.briefHash ?? null,
|
|
4050
|
+
objectivesVersion: input.objectivesVersion,
|
|
4051
|
+
rulingOrdinal: input.rulingOrdinal,
|
|
4052
|
+
policyDigest: policyDigest(policy)
|
|
4053
|
+
};
|
|
4054
|
+
const codeReviewEchoed = {
|
|
4055
|
+
headSha: codeReview.headSha,
|
|
4056
|
+
briefHash: codeReview.briefHash,
|
|
4057
|
+
objectivesVersion: codeReview.objectivesVersion,
|
|
4058
|
+
rulingOrdinal: codeReview.rulingOrdinal,
|
|
4059
|
+
policyDigest: codeReview.policyDigest
|
|
4060
|
+
};
|
|
4061
|
+
const securityEchoed = {
|
|
4062
|
+
headSha: security.headSha,
|
|
4063
|
+
briefHash: security.briefHash,
|
|
4064
|
+
objectivesVersion: security.objectivesVersion,
|
|
4065
|
+
rulingOrdinal: security.rulingOrdinal,
|
|
4066
|
+
policyDigest: security.policyDigest
|
|
4067
|
+
};
|
|
4068
|
+
const codeReviewBinding = compareManifest(codeReviewEchoed, currentManifest, input.patchIdOf);
|
|
4069
|
+
const securityBinding = compareManifest(securityEchoed, currentManifest, input.patchIdOf);
|
|
4070
|
+
const codeReviewBound = codeReviewBinding.head;
|
|
4071
|
+
const securityBound = securityBinding.head;
|
|
4072
|
+
const codeReviewObjectivesBound = codeReviewBinding.objectivesVersion;
|
|
4073
|
+
const securityObjectivesBound = securityBinding.objectivesVersion;
|
|
4074
|
+
const codeReviewRulingsBound = codeReviewBinding.rulingOrdinal;
|
|
4075
|
+
const securityRulingsBound = securityBinding.rulingOrdinal;
|
|
4076
|
+
if (codeReviewClean && codeReviewBinding.bound && securityClean && securityBinding.bound && mechanicalChecksClean) {
|
|
3710
4077
|
return {
|
|
3711
4078
|
verdict: "pass",
|
|
3712
4079
|
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.`,
|
|
@@ -3714,19 +4081,35 @@ function checkReviewGate(input) {
|
|
|
3714
4081
|
};
|
|
3715
4082
|
}
|
|
3716
4083
|
const problems = [];
|
|
3717
|
-
if (!
|
|
4084
|
+
if (!codeReviewTextClean) {
|
|
3718
4085
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
4086
|
+
} else if (!codeReviewPolicyClean) {
|
|
4087
|
+
problems.push(`code-reviewer verdict says APPROVE but carries a finding (${codeReviewPolicyEvaluation.blockingFindings.map((f) => f.severity).join(", ")}) at or above this repository's code-review policy threshold (${policy.codeReviewThreshold}) — a reviewer's own APPROVE never overrides policy`);
|
|
3719
4088
|
} else if (!codeReviewBound) {
|
|
3720
4089
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3721
4090
|
} else if (!codeReviewObjectivesBound) {
|
|
3722
4091
|
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3723
|
-
}
|
|
3724
|
-
|
|
4092
|
+
} else if (!codeReviewRulingsBound) {
|
|
4093
|
+
problems.push(`the newest code-review verdict was cast against ruling ordinal ${codeReview.rulingOrdinal ?? "none"}, a newer ruling (ruling ${input.rulingOrdinal}) is now posted on this PR`);
|
|
4094
|
+
} else if (!codeReviewBinding.briefHash) {
|
|
4095
|
+
problems.push(`the newest code-review verdict was cast against brief hash ${codeReview.briefHash ?? "none"}, the frozen brief's current hash is ${currentManifest.briefHash ?? "none"}`);
|
|
4096
|
+
} else if (!codeReviewBinding.policyDigest) {
|
|
4097
|
+
problems.push(`the newest code-review verdict was cast against review policy digest ${codeReview.policyDigest ?? "none"}, this repository's current policy digest is ${currentManifest.policyDigest}`);
|
|
4098
|
+
}
|
|
4099
|
+
if (!securityTextClean) {
|
|
3725
4100
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
4101
|
+
} else if (!securityPolicyClean) {
|
|
4102
|
+
problems.push(`security-review verdict says PASS but carries a finding (${securityPolicyEvaluation.blockingFindings.map((f) => f.severity).join(", ")}) at or above this repository's security policy threshold (${policy.securityThreshold}) — a reviewer's own PASS never overrides policy`);
|
|
3726
4103
|
} else if (!securityBound) {
|
|
3727
4104
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3728
4105
|
} else if (!securityObjectivesBound) {
|
|
3729
4106
|
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
4107
|
+
} else if (!securityRulingsBound) {
|
|
4108
|
+
problems.push(`the newest security-review verdict was cast against ruling ordinal ${security.rulingOrdinal ?? "none"}, a newer ruling (ruling ${input.rulingOrdinal}) is now posted on this PR`);
|
|
4109
|
+
} else if (!securityBinding.briefHash) {
|
|
4110
|
+
problems.push(`the newest security-review verdict was cast against brief hash ${security.briefHash ?? "none"}, the frozen brief's current hash is ${currentManifest.briefHash ?? "none"}`);
|
|
4111
|
+
} else if (!securityBinding.policyDigest) {
|
|
4112
|
+
problems.push(`the newest security-review verdict was cast against review policy digest ${security.policyDigest ?? "none"}, this repository's current policy digest is ${currentManifest.policyDigest}`);
|
|
3730
4113
|
}
|
|
3731
4114
|
if (!mechanicalChecksClean) {
|
|
3732
4115
|
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(", ")}`);
|
|
@@ -3743,6 +4126,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
|
|
|
3743
4126
|
function isChangesetsReleasePr(branch, author, expectedAuthor) {
|
|
3744
4127
|
return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
|
|
3745
4128
|
}
|
|
4129
|
+
// ../../packages/aeg-core/src/ruling-ordinal.ts
|
|
4130
|
+
var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
|
|
4131
|
+
function newestPrincipalRulingOrdinal(comments, allowlist) {
|
|
4132
|
+
let best = 0;
|
|
4133
|
+
for (const c of comments) {
|
|
4134
|
+
if (!isPrincipal(c.author, allowlist))
|
|
4135
|
+
continue;
|
|
4136
|
+
const firstLine = (c.body.split(`
|
|
4137
|
+
`)[0] ?? "").trim();
|
|
4138
|
+
const m = RULING_MARKER_ORDINAL.exec(firstLine);
|
|
4139
|
+
if (!m)
|
|
4140
|
+
continue;
|
|
4141
|
+
const k = Number.parseInt(m[1], 10);
|
|
4142
|
+
if (k > best)
|
|
4143
|
+
best = k;
|
|
4144
|
+
}
|
|
4145
|
+
return best;
|
|
4146
|
+
}
|
|
3746
4147
|
// ../../packages/aeg-core/src/markdown-table.ts
|
|
3747
4148
|
function splitRow(line) {
|
|
3748
4149
|
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
@@ -4400,42 +4801,45 @@ function checkDispatchReadiness(input) {
|
|
|
4400
4801
|
const { trancheSlug, task } = input;
|
|
4401
4802
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
4402
4803
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
4403
|
-
const
|
|
4804
|
+
const blockerDetails = [];
|
|
4805
|
+
const push = (blockerClass, message) => {
|
|
4806
|
+
blockerDetails.push({ class: blockerClass, message });
|
|
4807
|
+
};
|
|
4404
4808
|
if (task.issue === null) {
|
|
4405
|
-
|
|
4809
|
+
push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
|
|
4406
4810
|
} else if (input.issue === null) {
|
|
4407
|
-
|
|
4811
|
+
push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
|
|
4408
4812
|
}
|
|
4409
4813
|
if (input.issue !== null && !input.issueRationalePass) {
|
|
4410
|
-
|
|
4814
|
+
push("rationale", `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.`);
|
|
4411
4815
|
}
|
|
4412
4816
|
for (const dep of input.dependsOn) {
|
|
4413
4817
|
if (isSelfDependency(dep, task, input.issue)) {
|
|
4414
4818
|
const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
|
|
4415
|
-
|
|
4819
|
+
push("internal-self-dependency", `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.`);
|
|
4416
4820
|
continue;
|
|
4417
4821
|
}
|
|
4418
4822
|
if (dep.resolved === false) {
|
|
4419
|
-
|
|
4823
|
+
push("depends-on-unresolvable", `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.`);
|
|
4420
4824
|
continue;
|
|
4421
4825
|
}
|
|
4422
4826
|
if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
|
|
4423
4827
|
const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
|
|
4424
|
-
|
|
4828
|
+
push("depends-on-not-merged", `dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
|
|
4425
4829
|
}
|
|
4426
4830
|
}
|
|
4427
4831
|
for (const c of input.conflictsWith) {
|
|
4428
4832
|
if (c.openOrInFlight) {
|
|
4429
4833
|
const issueStr = c.issue !== null ? ` (#${c.issue})` : "";
|
|
4430
|
-
|
|
4834
|
+
push("conflicts-with", `dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
|
|
4431
4835
|
}
|
|
4432
4836
|
}
|
|
4433
4837
|
for (const proj of input.priorTrancheArchival) {
|
|
4434
4838
|
if (proj.priorTrancheSlug !== null && !proj.archived) {
|
|
4435
|
-
|
|
4839
|
+
push("prior-tranche-archival", `dispatch-gate prior-tranche-archival: project \`${proj.project}\`'s previous tranche \`${proj.priorTrancheSlug}\` is not archived — the Tranche Archivist must run before new work on this product.`);
|
|
4436
4840
|
}
|
|
4437
4841
|
}
|
|
4438
|
-
return { ready:
|
|
4842
|
+
return { ready: blockerDetails.length === 0, blockers: blockerDetails.map((b) => b.message), blockerDetails };
|
|
4439
4843
|
}
|
|
4440
4844
|
// ../../packages/aeg-core/src/single-plan-pr.ts
|
|
4441
4845
|
function trancheSlugFromTopologyPath(path) {
|
|
@@ -4537,6 +4941,91 @@ function checkBranchTopology(input) {
|
|
|
4537
4941
|
reason: `Branch \`${branch}\` matches topology row \`${taskId}\` in ${topoPath}.`
|
|
4538
4942
|
};
|
|
4539
4943
|
}
|
|
4944
|
+
// ../../packages/aeg-core/src/task-tools.ts
|
|
4945
|
+
import { z } from "zod";
|
|
4946
|
+
var TASK_TOOL_ERROR_KINDS = [
|
|
4947
|
+
"validation",
|
|
4948
|
+
"authority",
|
|
4949
|
+
"precondition",
|
|
4950
|
+
"capability",
|
|
4951
|
+
"infrastructure",
|
|
4952
|
+
"cancellation",
|
|
4953
|
+
"timeout",
|
|
4954
|
+
"uncertain_effect"
|
|
4955
|
+
];
|
|
4956
|
+
var TaskToolErrorSchema = z.object({
|
|
4957
|
+
kind: z.enum(TASK_TOOL_ERROR_KINDS),
|
|
4958
|
+
message: z.string().min(1),
|
|
4959
|
+
detail: z.string().optional()
|
|
4960
|
+
});
|
|
4961
|
+
var TaskToolRefSchema = z.union([
|
|
4962
|
+
z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
|
|
4963
|
+
z.object({ issue: z.number().int().positive() })
|
|
4964
|
+
]);
|
|
4965
|
+
var MAX_PAGE_LIMIT = 100;
|
|
4966
|
+
var PageRequestSchema = z.object({
|
|
4967
|
+
cursor: z.string().optional(),
|
|
4968
|
+
limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
|
|
4969
|
+
});
|
|
4970
|
+
var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
|
|
4971
|
+
var ObservedSchema = z.object({
|
|
4972
|
+
observedAt: z.string(),
|
|
4973
|
+
freshness: FreshnessSchema
|
|
4974
|
+
});
|
|
4975
|
+
var TaskStatusInputSchema = z.object({
|
|
4976
|
+
task: TaskToolRefSchema.optional()
|
|
4977
|
+
}).merge(PageRequestSchema);
|
|
4978
|
+
var TaskStatusItemSchema = z.object({
|
|
4979
|
+
task: TaskToolRefSchema,
|
|
4980
|
+
issue: z.number().int().positive(),
|
|
4981
|
+
pr: z.number().int().positive().nullable(),
|
|
4982
|
+
state: z.string()
|
|
4983
|
+
}).merge(ObservedSchema);
|
|
4984
|
+
var TaskStatusResultSchema = z.object({
|
|
4985
|
+
items: z.array(TaskStatusItemSchema),
|
|
4986
|
+
nextCursor: z.string().nullable()
|
|
4987
|
+
});
|
|
4988
|
+
var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
|
|
4989
|
+
var EscalationInputsSchema = z.object({
|
|
4990
|
+
task: z.number().int().positive(),
|
|
4991
|
+
round: z.number().int().nonnegative(),
|
|
4992
|
+
head: z.string(),
|
|
4993
|
+
branch: z.string(),
|
|
4994
|
+
prNumber: z.number().int().positive()
|
|
4995
|
+
});
|
|
4996
|
+
var EscalationEvidenceSchema = z.object({
|
|
4997
|
+
round: z.number().int().nonnegative(),
|
|
4998
|
+
reviewer: z.string().nullable(),
|
|
4999
|
+
security: z.string().nullable()
|
|
5000
|
+
});
|
|
5001
|
+
var TaskEscalationReadInputSchema = z.object({
|
|
5002
|
+
task: TaskToolRefSchema
|
|
5003
|
+
}).merge(PageRequestSchema);
|
|
5004
|
+
var TaskEscalationPacketSchema = z.object({
|
|
5005
|
+
reason: z.string(),
|
|
5006
|
+
detail: z.string().nullable(),
|
|
5007
|
+
inputs: EscalationInputsSchema.nullable(),
|
|
5008
|
+
evidence: EscalationEvidenceSchema.nullable(),
|
|
5009
|
+
attemptedRecovery: z.string(),
|
|
5010
|
+
requestedAuthority: RequestedAuthoritySchema,
|
|
5011
|
+
permittedNextActions: z.array(z.string())
|
|
5012
|
+
}).merge(ObservedSchema);
|
|
5013
|
+
var TaskEscalationReadResultSchema = z.object({
|
|
5014
|
+
items: z.array(TaskEscalationPacketSchema),
|
|
5015
|
+
nextCursor: z.string().nullable()
|
|
5016
|
+
}).merge(ObservedSchema);
|
|
5017
|
+
var TaskStartInputSchema = z.object({
|
|
5018
|
+
tranche: z.string().min(1),
|
|
5019
|
+
id: z.string().min(1)
|
|
5020
|
+
});
|
|
5021
|
+
var TaskResumeInputSchema = z.object({
|
|
5022
|
+
task: TaskToolRefSchema
|
|
5023
|
+
});
|
|
5024
|
+
var TaskCancelInputSchema = z.object({
|
|
5025
|
+
task: TaskToolRefSchema,
|
|
5026
|
+
reason: z.string().min(1)
|
|
5027
|
+
});
|
|
5028
|
+
var NoResultSchema = z.never();
|
|
4540
5029
|
// ../../packages/aeg-core/src/first-push-dispatch-gate.ts
|
|
4541
5030
|
function parseTaskBranch(branch) {
|
|
4542
5031
|
const m = /^task\/([^/]+)\/([^/]+)$/.exec(branch);
|
|
@@ -4620,7 +5109,6 @@ function decideIssueAssignment(input) {
|
|
|
4620
5109
|
};
|
|
4621
5110
|
}
|
|
4622
5111
|
// ../../packages/aeg-core/src/test-plan-gate.ts
|
|
4623
|
-
var TASK_BRANCH_PATTERN2 = /^task\/[^/]+\/[^/]+$/;
|
|
4624
5112
|
function evaluateTestPlanGate(body, branch) {
|
|
4625
5113
|
if (!body) {
|
|
4626
5114
|
return {
|
|
@@ -4633,7 +5121,7 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
4633
5121
|
}
|
|
4634
5122
|
const located = locateTestPlanSection(body);
|
|
4635
5123
|
if (!located.found) {
|
|
4636
|
-
if (
|
|
5124
|
+
if (parseTaskBranchIdentity(branch) !== null) {
|
|
4637
5125
|
return {
|
|
4638
5126
|
verdict: "fail",
|
|
4639
5127
|
messages: [
|
|
@@ -4761,7 +5249,7 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4761
5249
|
return findings;
|
|
4762
5250
|
}
|
|
4763
5251
|
// ../../packages/aeg-core/src/log/schema.ts
|
|
4764
|
-
import { z } from "zod";
|
|
5252
|
+
import { z as z2 } from "zod";
|
|
4765
5253
|
var ROLE_VALUES = [
|
|
4766
5254
|
"planner",
|
|
4767
5255
|
"developer",
|
|
@@ -4771,136 +5259,172 @@ var ROLE_VALUES = [
|
|
|
4771
5259
|
"archivist",
|
|
4772
5260
|
"architect"
|
|
4773
5261
|
];
|
|
4774
|
-
var RoleSchema =
|
|
5262
|
+
var RoleSchema = z2.enum(ROLE_VALUES);
|
|
4775
5263
|
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4776
|
-
var HostSchema =
|
|
5264
|
+
var HostSchema = z2.enum(HOST_VALUES);
|
|
4777
5265
|
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4778
|
-
var
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
doctrine: z.string(),
|
|
5266
|
+
var headerMetaCore = {
|
|
5267
|
+
ts: z2.string(),
|
|
5268
|
+
run_id: z2.string().regex(RUN_ID_PATTERN),
|
|
5269
|
+
seq: z2.number().int().nonnegative(),
|
|
5270
|
+
repo: z2.string().nullable(),
|
|
5271
|
+
vinaya: z2.string(),
|
|
5272
|
+
doctrine: z2.string(),
|
|
4786
5273
|
host: HostSchema,
|
|
4787
|
-
machine:
|
|
5274
|
+
machine: z2.string()
|
|
5275
|
+
};
|
|
5276
|
+
var HeaderMetaV1Schema = z2.object({
|
|
5277
|
+
schema: z2.literal(1),
|
|
5278
|
+
...headerMetaCore
|
|
5279
|
+
}).strict();
|
|
5280
|
+
var LineageSchema = z2.object({
|
|
5281
|
+
run: z2.string().nullable(),
|
|
5282
|
+
attempt: z2.number().int().nullable(),
|
|
5283
|
+
parent: z2.string().nullable()
|
|
5284
|
+
}).strict();
|
|
5285
|
+
var InputVersionsSchema = z2.object({
|
|
5286
|
+
objectives_version: z2.string().nullable(),
|
|
5287
|
+
brief_hash: z2.string().nullable(),
|
|
5288
|
+
ruling_ordinal: z2.number().int().nullable(),
|
|
5289
|
+
policy_digest: z2.string().nullable()
|
|
5290
|
+
}).strict();
|
|
5291
|
+
var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
|
|
5292
|
+
var HeaderMetaV2Schema = z2.object({
|
|
5293
|
+
schema: z2.literal(2),
|
|
5294
|
+
...headerMetaCore,
|
|
5295
|
+
event_id: z2.string().min(1),
|
|
5296
|
+
process_id: z2.string().min(1),
|
|
5297
|
+
actor_id: z2.string().nullable(),
|
|
5298
|
+
lineage: LineageSchema,
|
|
5299
|
+
input_versions: InputVersionsSchema,
|
|
5300
|
+
provenance: ProvenanceSchema
|
|
4788
5301
|
}).strict();
|
|
4789
|
-
var
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
5302
|
+
var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
|
|
5303
|
+
var SubjectSchema = z2.object({
|
|
5304
|
+
issue: z2.number().int().nullable(),
|
|
5305
|
+
pr: z2.number().int().optional(),
|
|
5306
|
+
sha: z2.string().optional(),
|
|
5307
|
+
role: z2.union([RoleSchema, z2.literal("unattributed")]),
|
|
5308
|
+
round: z2.number().int().optional(),
|
|
5309
|
+
objectives_version: z2.string().optional()
|
|
4796
5310
|
}).strict();
|
|
4797
|
-
var HeaderSchema =
|
|
5311
|
+
var HeaderSchema = z2.object({
|
|
4798
5312
|
meta: HeaderMetaSchema,
|
|
4799
5313
|
subject: SubjectSchema
|
|
4800
5314
|
}).strict();
|
|
4801
5315
|
var envelopeTail = {
|
|
4802
|
-
duration_ms:
|
|
4803
|
-
payload:
|
|
5316
|
+
duration_ms: z2.number().nonnegative().optional(),
|
|
5317
|
+
payload: z2.object({}).strict()
|
|
4804
5318
|
};
|
|
4805
|
-
var
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
5319
|
+
var ReviewFindingSchema = z2.object({
|
|
5320
|
+
id: z2.string(),
|
|
5321
|
+
severity: z2.string(),
|
|
5322
|
+
state: z2.string().optional(),
|
|
5323
|
+
severity_scale: z2.string().optional(),
|
|
5324
|
+
policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
|
|
5325
|
+
confidence: z2.number().min(0).max(1).optional(),
|
|
5326
|
+
confidence_scale: z2.string().optional(),
|
|
5327
|
+
confidence_source: z2.string().optional()
|
|
5328
|
+
}).strict();
|
|
5329
|
+
var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
|
|
5330
|
+
z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
|
|
5331
|
+
z2.object({
|
|
5332
|
+
type: z2.literal("round_pushed"),
|
|
5333
|
+
pr: z2.number().int(),
|
|
5334
|
+
head: z2.string(),
|
|
5335
|
+
comment_id: z2.number().int()
|
|
4812
5336
|
}).strict(),
|
|
4813
|
-
|
|
4814
|
-
type:
|
|
4815
|
-
verdict:
|
|
4816
|
-
head:
|
|
4817
|
-
comment_id:
|
|
4818
|
-
objectives:
|
|
4819
|
-
findings:
|
|
5337
|
+
z2.object({
|
|
5338
|
+
type: z2.literal("verdict"),
|
|
5339
|
+
verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
5340
|
+
head: z2.string(),
|
|
5341
|
+
comment_id: z2.number().int(),
|
|
5342
|
+
objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
|
|
5343
|
+
findings: z2.array(ReviewFindingSchema)
|
|
4820
5344
|
}).strict(),
|
|
4821
|
-
|
|
4822
|
-
type:
|
|
4823
|
-
class:
|
|
4824
|
-
comment_id:
|
|
5345
|
+
z2.object({
|
|
5346
|
+
type: z2.literal("escalation"),
|
|
5347
|
+
class: z2.enum(["authority", "strategy", "product"]),
|
|
5348
|
+
comment_id: z2.number().int()
|
|
4825
5349
|
}).strict(),
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
5350
|
+
z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
|
|
5351
|
+
z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
|
|
5352
|
+
z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict()
|
|
4829
5353
|
]);
|
|
4830
5354
|
var dispatchShared = {
|
|
4831
5355
|
meta: HeaderMetaSchema,
|
|
4832
5356
|
subject: SubjectSchema,
|
|
4833
|
-
kind:
|
|
5357
|
+
kind: z2.literal("dispatch"),
|
|
4834
5358
|
...envelopeTail,
|
|
4835
5359
|
target_role: RoleSchema,
|
|
4836
|
-
model:
|
|
4837
|
-
round:
|
|
4838
|
-
effect_id:
|
|
5360
|
+
model: z2.string(),
|
|
5361
|
+
round: z2.number().int().optional(),
|
|
5362
|
+
effect_id: z2.string()
|
|
4839
5363
|
};
|
|
4840
|
-
var dispatchUsageField =
|
|
4841
|
-
var DispatchEventSchema =
|
|
4842
|
-
|
|
4843
|
-
|
|
5364
|
+
var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
|
|
5365
|
+
var DispatchEventSchema = z2.discriminatedUnion("event", [
|
|
5366
|
+
z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
|
|
5367
|
+
z2.object({
|
|
4844
5368
|
...dispatchShared,
|
|
4845
|
-
event:
|
|
5369
|
+
event: z2.literal("outcome_received"),
|
|
4846
5370
|
outcome: DispatchOutcomeSchema,
|
|
4847
5371
|
usage: dispatchUsageField
|
|
4848
5372
|
}).strict(),
|
|
4849
|
-
|
|
5373
|
+
z2.object({
|
|
4850
5374
|
...dispatchShared,
|
|
4851
|
-
event:
|
|
4852
|
-
reason:
|
|
5375
|
+
event: z2.literal("dispatch_failed"),
|
|
5376
|
+
reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
|
|
4853
5377
|
usage: dispatchUsageField
|
|
4854
5378
|
}).strict()
|
|
4855
5379
|
]);
|
|
4856
5380
|
var loopShared = {
|
|
4857
5381
|
meta: HeaderMetaSchema,
|
|
4858
5382
|
subject: SubjectSchema,
|
|
4859
|
-
kind:
|
|
5383
|
+
kind: z2.literal("dev_review_loop"),
|
|
4860
5384
|
...envelopeTail,
|
|
4861
|
-
loop_id:
|
|
5385
|
+
loop_id: z2.string()
|
|
4862
5386
|
};
|
|
4863
|
-
var DevReviewLoopEventSchema =
|
|
4864
|
-
|
|
5387
|
+
var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
|
|
5388
|
+
z2.object({
|
|
4865
5389
|
...loopShared,
|
|
4866
|
-
event:
|
|
4867
|
-
task:
|
|
4868
|
-
policy:
|
|
4869
|
-
max_rounds:
|
|
4870
|
-
reviewers:
|
|
4871
|
-
models:
|
|
5390
|
+
event: z2.literal("loop_started"),
|
|
5391
|
+
task: z2.number().int(),
|
|
5392
|
+
policy: z2.object({
|
|
5393
|
+
max_rounds: z2.number().int().nonnegative(),
|
|
5394
|
+
reviewers: z2.array(RoleSchema),
|
|
5395
|
+
models: z2.record(RoleSchema, z2.string())
|
|
4872
5396
|
}).strict()
|
|
4873
5397
|
}).strict(),
|
|
4874
|
-
|
|
4875
|
-
|
|
5398
|
+
z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
|
|
5399
|
+
z2.object({
|
|
4876
5400
|
...loopShared,
|
|
4877
|
-
event:
|
|
4878
|
-
round:
|
|
4879
|
-
head:
|
|
4880
|
-
green:
|
|
5401
|
+
event: z2.literal("gate_result_read"),
|
|
5402
|
+
round: z2.number().int(),
|
|
5403
|
+
head: z2.string(),
|
|
5404
|
+
green: z2.boolean()
|
|
4881
5405
|
}).strict(),
|
|
4882
|
-
|
|
5406
|
+
z2.object({
|
|
4883
5407
|
...loopShared,
|
|
4884
|
-
event:
|
|
4885
|
-
round:
|
|
4886
|
-
head:
|
|
4887
|
-
all_approve:
|
|
4888
|
-
blockers:
|
|
5408
|
+
event: z2.literal("verdicts_read"),
|
|
5409
|
+
round: z2.number().int(),
|
|
5410
|
+
head: z2.string(),
|
|
5411
|
+
all_approve: z2.boolean(),
|
|
5412
|
+
blockers: z2.number().int().nonnegative()
|
|
4889
5413
|
}).strict(),
|
|
4890
|
-
|
|
5414
|
+
z2.object({
|
|
4891
5415
|
...loopShared,
|
|
4892
|
-
event:
|
|
4893
|
-
round:
|
|
4894
|
-
open:
|
|
4895
|
-
resolved:
|
|
4896
|
-
new:
|
|
4897
|
-
recurring:
|
|
5416
|
+
event: z2.literal("findings_compared"),
|
|
5417
|
+
round: z2.number().int(),
|
|
5418
|
+
open: z2.array(z2.string()),
|
|
5419
|
+
resolved: z2.array(z2.string()),
|
|
5420
|
+
new: z2.array(z2.string()),
|
|
5421
|
+
recurring: z2.array(z2.string())
|
|
4898
5422
|
}).strict(),
|
|
4899
|
-
|
|
5423
|
+
z2.object({
|
|
4900
5424
|
...loopShared,
|
|
4901
|
-
event:
|
|
4902
|
-
round:
|
|
4903
|
-
condition:
|
|
5425
|
+
event: z2.literal("stop_condition_met"),
|
|
5426
|
+
round: z2.number().int(),
|
|
5427
|
+
condition: z2.enum([
|
|
4904
5428
|
"green",
|
|
4905
5429
|
"max_rounds",
|
|
4906
5430
|
"no_progress",
|
|
@@ -4910,42 +5434,49 @@ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
|
4910
5434
|
"reappearance"
|
|
4911
5435
|
])
|
|
4912
5436
|
}).strict(),
|
|
4913
|
-
|
|
5437
|
+
z2.object({
|
|
5438
|
+
...loopShared,
|
|
5439
|
+
event: z2.literal("paused"),
|
|
5440
|
+
round: z2.number().int(),
|
|
5441
|
+
reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
5442
|
+
}).strict(),
|
|
5443
|
+
z2.object({
|
|
4914
5444
|
...loopShared,
|
|
4915
|
-
event:
|
|
4916
|
-
round:
|
|
4917
|
-
|
|
5445
|
+
event: z2.literal("resumed"),
|
|
5446
|
+
round: z2.number().int(),
|
|
5447
|
+
by: z2.literal("principal")
|
|
4918
5448
|
}).strict(),
|
|
4919
|
-
|
|
5449
|
+
z2.object({
|
|
4920
5450
|
...loopShared,
|
|
4921
|
-
event:
|
|
4922
|
-
round:
|
|
4923
|
-
|
|
5451
|
+
event: z2.literal("unpushed_work_resume"),
|
|
5452
|
+
round: z2.number().int(),
|
|
5453
|
+
branch: z2.string(),
|
|
5454
|
+
detail: z2.string()
|
|
4924
5455
|
}).strict(),
|
|
4925
|
-
|
|
5456
|
+
z2.object({
|
|
4926
5457
|
...loopShared,
|
|
4927
|
-
event:
|
|
4928
|
-
round:
|
|
4929
|
-
base_head:
|
|
4930
|
-
head:
|
|
4931
|
-
files_changed:
|
|
4932
|
-
insertions:
|
|
4933
|
-
deletions:
|
|
4934
|
-
wall_ms:
|
|
4935
|
-
outcome:
|
|
5458
|
+
event: z2.literal("round_ended"),
|
|
5459
|
+
round: z2.number().int(),
|
|
5460
|
+
base_head: z2.string(),
|
|
5461
|
+
head: z2.string(),
|
|
5462
|
+
files_changed: z2.number().int().nonnegative(),
|
|
5463
|
+
insertions: z2.number().int().nonnegative(),
|
|
5464
|
+
deletions: z2.number().int().nonnegative(),
|
|
5465
|
+
wall_ms: z2.number().nonnegative(),
|
|
5466
|
+
outcome: z2.enum(["green", "changes_requested", "escalated"])
|
|
4936
5467
|
}).strict(),
|
|
4937
|
-
|
|
5468
|
+
z2.object({
|
|
4938
5469
|
...loopShared,
|
|
4939
|
-
event:
|
|
4940
|
-
rounds:
|
|
4941
|
-
total_wall_ms:
|
|
4942
|
-
time_to_green_ms:
|
|
4943
|
-
files_changed_total:
|
|
4944
|
-
final_head:
|
|
4945
|
-
result:
|
|
5470
|
+
event: z2.literal("journal_finalized"),
|
|
5471
|
+
rounds: z2.number().int().nonnegative(),
|
|
5472
|
+
total_wall_ms: z2.number().nonnegative(),
|
|
5473
|
+
time_to_green_ms: z2.number().nonnegative().nullable(),
|
|
5474
|
+
files_changed_total: z2.number().int().nonnegative(),
|
|
5475
|
+
final_head: z2.string(),
|
|
5476
|
+
result: z2.enum(["merged_ready", "stopped"])
|
|
4946
5477
|
}).strict()
|
|
4947
5478
|
]);
|
|
4948
|
-
var ForgeOpSchema =
|
|
5479
|
+
var ForgeOpSchema = z2.enum([
|
|
4949
5480
|
"pr.create",
|
|
4950
5481
|
"pr.comment",
|
|
4951
5482
|
"pr.body.replace",
|
|
@@ -4959,24 +5490,210 @@ var ForgeOpSchema = z.enum([
|
|
|
4959
5490
|
"label.add",
|
|
4960
5491
|
"label.remove"
|
|
4961
5492
|
]);
|
|
4962
|
-
var ForgeWriteTargetSchema =
|
|
4963
|
-
issue:
|
|
4964
|
-
pr:
|
|
5493
|
+
var ForgeWriteTargetSchema = z2.object({
|
|
5494
|
+
issue: z2.number().int().optional(),
|
|
5495
|
+
pr: z2.number().int().optional()
|
|
4965
5496
|
}).strict();
|
|
4966
5497
|
var forgeWriteShared = {
|
|
4967
5498
|
meta: HeaderMetaSchema,
|
|
4968
5499
|
subject: SubjectSchema,
|
|
4969
|
-
kind:
|
|
5500
|
+
kind: z2.literal("forge_write"),
|
|
4970
5501
|
...envelopeTail,
|
|
4971
5502
|
op: ForgeOpSchema,
|
|
4972
5503
|
target: ForgeWriteTargetSchema
|
|
4973
5504
|
};
|
|
4974
|
-
var ForgeWriteEventSchema =
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
5505
|
+
var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
|
|
5506
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
|
|
5507
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
|
|
5508
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
|
|
5509
|
+
]);
|
|
5510
|
+
var GateOutcomeSchema = z2.enum([
|
|
5511
|
+
"pass",
|
|
5512
|
+
"fail",
|
|
5513
|
+
"wait",
|
|
5514
|
+
"skip",
|
|
5515
|
+
"invalid_input",
|
|
5516
|
+
"unavailable_dependency",
|
|
5517
|
+
"timeout",
|
|
5518
|
+
"cancelled"
|
|
5519
|
+
]);
|
|
5520
|
+
var gateShared = {
|
|
5521
|
+
meta: HeaderMetaSchema,
|
|
5522
|
+
subject: SubjectSchema,
|
|
5523
|
+
kind: z2.literal("gate"),
|
|
5524
|
+
...envelopeTail,
|
|
5525
|
+
check: z2.string(),
|
|
5526
|
+
check_version: z2.string().nullable(),
|
|
5527
|
+
policy_version: z2.string().nullable(),
|
|
5528
|
+
input_fingerprint: z2.string().nullable()
|
|
5529
|
+
};
|
|
5530
|
+
var GateEventSchema = z2.discriminatedUnion("event", [
|
|
5531
|
+
z2.object({
|
|
5532
|
+
...gateShared,
|
|
5533
|
+
event: z2.literal("checked"),
|
|
5534
|
+
outcome: GateOutcomeSchema,
|
|
5535
|
+
reason: z2.string().optional()
|
|
5536
|
+
}).strict()
|
|
5537
|
+
]);
|
|
5538
|
+
var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
|
|
5539
|
+
var operationShared = {
|
|
5540
|
+
meta: HeaderMetaSchema,
|
|
5541
|
+
subject: SubjectSchema,
|
|
5542
|
+
kind: z2.literal("operation"),
|
|
5543
|
+
...envelopeTail,
|
|
5544
|
+
operation: z2.string(),
|
|
5545
|
+
target: z2.string().nullable()
|
|
5546
|
+
};
|
|
5547
|
+
var OperationEventSchema = z2.discriminatedUnion("event", [
|
|
5548
|
+
z2.object({
|
|
5549
|
+
...operationShared,
|
|
5550
|
+
event: z2.literal("completed"),
|
|
5551
|
+
result: OperationResultSchema,
|
|
5552
|
+
error_class: z2.string().nullable()
|
|
5553
|
+
}).strict()
|
|
5554
|
+
]);
|
|
5555
|
+
var UsageUnitsSchema = z2.object({
|
|
5556
|
+
input: z2.number().nonnegative().nullable(),
|
|
5557
|
+
output: z2.number().nonnegative().nullable(),
|
|
5558
|
+
cache: z2.number().nonnegative().nullable()
|
|
5559
|
+
}).strict();
|
|
5560
|
+
var usageShared = {
|
|
5561
|
+
meta: HeaderMetaSchema,
|
|
5562
|
+
subject: SubjectSchema,
|
|
5563
|
+
kind: z2.literal("usage"),
|
|
5564
|
+
...envelopeTail,
|
|
5565
|
+
model: z2.string().nullable(),
|
|
5566
|
+
source: z2.string(),
|
|
5567
|
+
semantics: z2.enum(["cumulative", "delta"])
|
|
5568
|
+
};
|
|
5569
|
+
var UsageEventSchema = z2.discriminatedUnion("event", [
|
|
5570
|
+
z2.object({
|
|
5571
|
+
...usageShared,
|
|
5572
|
+
event: z2.literal("observed"),
|
|
5573
|
+
units: UsageUnitsSchema,
|
|
5574
|
+
unknown_reason: z2.string().nullable()
|
|
5575
|
+
}).strict()
|
|
5576
|
+
]);
|
|
5577
|
+
var RoleAttemptOutcomeSchema = z2.enum([
|
|
5578
|
+
"completed",
|
|
5579
|
+
"incomplete",
|
|
5580
|
+
"infrastructure_failed",
|
|
5581
|
+
"cancelled",
|
|
5582
|
+
"timed_out",
|
|
5583
|
+
"capability_refused"
|
|
5584
|
+
]);
|
|
5585
|
+
var roleAttemptShared = {
|
|
5586
|
+
meta: HeaderMetaSchema,
|
|
5587
|
+
subject: SubjectSchema,
|
|
5588
|
+
kind: z2.literal("role_attempt"),
|
|
5589
|
+
...envelopeTail,
|
|
5590
|
+
actor: z2.string().nullable(),
|
|
5591
|
+
attempt: z2.number().int().nullable()
|
|
5592
|
+
};
|
|
5593
|
+
var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
|
|
5594
|
+
z2.object({
|
|
5595
|
+
...roleAttemptShared,
|
|
5596
|
+
event: z2.literal("attempted"),
|
|
5597
|
+
outcome: RoleAttemptOutcomeSchema,
|
|
5598
|
+
usage: dispatchUsageField
|
|
5599
|
+
}).strict()
|
|
5600
|
+
]);
|
|
5601
|
+
var handoffShared = {
|
|
5602
|
+
meta: HeaderMetaSchema,
|
|
5603
|
+
subject: SubjectSchema,
|
|
5604
|
+
kind: z2.literal("handoff"),
|
|
5605
|
+
...envelopeTail,
|
|
5606
|
+
class: z2.enum(["authority", "strategy", "product"]),
|
|
5607
|
+
reason: z2.string()
|
|
5608
|
+
};
|
|
5609
|
+
var HandoffEventSchema = z2.discriminatedUnion("event", [
|
|
5610
|
+
z2.object({
|
|
5611
|
+
...handoffShared,
|
|
5612
|
+
event: z2.literal("raised"),
|
|
5613
|
+
requested_decision: z2.string().nullable()
|
|
5614
|
+
}).strict(),
|
|
5615
|
+
z2.object({
|
|
5616
|
+
...handoffShared,
|
|
5617
|
+
event: z2.literal("resolved"),
|
|
5618
|
+
resolution: z2.string().nullable(),
|
|
5619
|
+
resolved_by: z2.string().nullable()
|
|
5620
|
+
}).strict()
|
|
4978
5621
|
]);
|
|
4979
|
-
var
|
|
5622
|
+
var EffectTargetSchema = z2.object({
|
|
5623
|
+
kind: z2.string(),
|
|
5624
|
+
ref: z2.string()
|
|
5625
|
+
}).strict();
|
|
5626
|
+
var effectShared = {
|
|
5627
|
+
meta: HeaderMetaSchema,
|
|
5628
|
+
subject: SubjectSchema,
|
|
5629
|
+
kind: z2.literal("effect"),
|
|
5630
|
+
...envelopeTail,
|
|
5631
|
+
effect_id: z2.string(),
|
|
5632
|
+
target: EffectTargetSchema
|
|
5633
|
+
};
|
|
5634
|
+
var EffectEventSchema = z2.discriminatedUnion("event", [
|
|
5635
|
+
z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
|
|
5636
|
+
z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
|
|
5637
|
+
z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
|
|
5638
|
+
]);
|
|
5639
|
+
var LogEventSchema = z2.union([
|
|
5640
|
+
DispatchEventSchema,
|
|
5641
|
+
DevReviewLoopEventSchema,
|
|
5642
|
+
ForgeWriteEventSchema,
|
|
5643
|
+
GateEventSchema,
|
|
5644
|
+
OperationEventSchema,
|
|
5645
|
+
UsageEventSchema,
|
|
5646
|
+
RoleAttemptEventSchema,
|
|
5647
|
+
HandoffEventSchema,
|
|
5648
|
+
EffectEventSchema
|
|
5649
|
+
]);
|
|
5650
|
+
// ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
|
|
5651
|
+
var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
|
|
5652
|
+
// ../../packages/aeg-core/src/control-store/records.ts
|
|
5653
|
+
import { z as z3 } from "zod";
|
|
5654
|
+
var isoTimestamp = z3.string().min(1);
|
|
5655
|
+
var taskId = z3.number().int().positive();
|
|
5656
|
+
var epochNumber = z3.number().int().nonnegative();
|
|
5657
|
+
var RunRecordSchema = z3.object({
|
|
5658
|
+
version: z3.literal(1),
|
|
5659
|
+
kind: z3.literal("run"),
|
|
5660
|
+
task: taskId,
|
|
5661
|
+
runId: z3.string().min(1),
|
|
5662
|
+
pid: z3.number().int().positive(),
|
|
5663
|
+
host: z3.string().min(1),
|
|
5664
|
+
startedAt: isoTimestamp
|
|
5665
|
+
}).strict();
|
|
5666
|
+
var InputRecordSchema = z3.object({
|
|
5667
|
+
version: z3.literal(1),
|
|
5668
|
+
kind: z3.literal("input"),
|
|
5669
|
+
task: taskId,
|
|
5670
|
+
runId: z3.string().min(1),
|
|
5671
|
+
source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
|
|
5672
|
+
pr: z3.number().int().positive().nullable(),
|
|
5673
|
+
round: z3.number().int().nonnegative(),
|
|
5674
|
+
recordedAt: isoTimestamp
|
|
5675
|
+
}).strict();
|
|
5676
|
+
var OwnershipRecordSchema = z3.object({
|
|
5677
|
+
version: z3.literal(1),
|
|
5678
|
+
kind: z3.literal("ownership"),
|
|
5679
|
+
task: taskId,
|
|
5680
|
+
epoch: epochNumber,
|
|
5681
|
+
ownerId: z3.string().min(1),
|
|
5682
|
+
pid: z3.number().int().positive(),
|
|
5683
|
+
host: z3.string().min(1),
|
|
5684
|
+
acquiredAt: isoTimestamp
|
|
5685
|
+
}).strict();
|
|
5686
|
+
var TransitionRecordSchema = z3.object({
|
|
5687
|
+
version: z3.literal(1),
|
|
5688
|
+
kind: z3.literal("transition"),
|
|
5689
|
+
task: taskId,
|
|
5690
|
+
epoch: epochNumber,
|
|
5691
|
+
seq: z3.number().int().nonnegative(),
|
|
5692
|
+
from: z3.string().min(1),
|
|
5693
|
+
to: z3.string().min(1),
|
|
5694
|
+
detail: z3.string().optional(),
|
|
5695
|
+
at: isoTimestamp
|
|
5696
|
+
}).strict();
|
|
4980
5697
|
// ../../packages/sources/src/commands.ts
|
|
4981
5698
|
var COMMANDS = [
|
|
4982
5699
|
{
|
|
@@ -5097,14 +5814,52 @@ var COMMANDS = [
|
|
|
5097
5814
|
},
|
|
5098
5815
|
{
|
|
5099
5816
|
name: "task dispatch",
|
|
5100
|
-
description: "
|
|
5817
|
+
description: "Deprecated — render, pin, and post the brief on the Issue as the frozen original; start the developer",
|
|
5101
5818
|
flags: [
|
|
5102
5819
|
{ flag: "--agent <claude|codex|gemini>", description: "Start the developer through dispatchRole once posted" }
|
|
5103
5820
|
],
|
|
5104
5821
|
details: [
|
|
5105
5822
|
"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.",
|
|
5106
5823
|
"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.",
|
|
5107
|
-
"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."
|
|
5824
|
+
"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.",
|
|
5825
|
+
"Deprecated in favor of `task brief` (preparation only) and `task run` (the full unattended loop) — kept for a documented compatibility window while callers migrate."
|
|
5826
|
+
],
|
|
5827
|
+
status: "shipped"
|
|
5828
|
+
},
|
|
5829
|
+
{
|
|
5830
|
+
name: "task brief",
|
|
5831
|
+
description: "Render and freeze the brief as the Issue's original comment — preparation only, starts nobody",
|
|
5832
|
+
details: [
|
|
5833
|
+
"The preparation half of `task dispatch`, extracted so it is callable on its own: resolves the task's Issue, renders the brief, refuses on any gap, refuses when a frozen brief already exists, and posts it once as the same `aeg:brief:v1` Issue comment `task dispatch` posts. Starts no agent under any circumstances — there is no `--agent` flag here at all.",
|
|
5834
|
+
"Successor to `task dispatch` for the preparation step; the full unattended run (preparation, then the developer, then the review loop) is `task run`."
|
|
5835
|
+
],
|
|
5836
|
+
status: "shipped"
|
|
5837
|
+
},
|
|
5838
|
+
{
|
|
5839
|
+
name: "task run",
|
|
5840
|
+
description: "One command from a planned Issue to a reviewed pull request — exactly one developer started",
|
|
5841
|
+
flags: [
|
|
5842
|
+
{
|
|
5843
|
+
flag: "--agent <claude|codex|gemini>",
|
|
5844
|
+
description: "Vendor for the developer and both reviewers this run dispatches"
|
|
5845
|
+
}
|
|
5846
|
+
],
|
|
5847
|
+
details: [
|
|
5848
|
+
"Composes `task brief`'s own preparation (`prepareTask`) with `dev-review-loop` (`devReviewLoop`) — nothing else. Preparation starts no agent; the loop's own round 1 reads the frozen brief off the Issue and is the only place a developer is ever dispatched from a fresh task, so exactly one developer is started by construction.",
|
|
5849
|
+
"A brief already frozen on the Issue is reused, never re-posted — the second `task dispatch`/`task brief` call this composes around does not fail the whole run, it just skips straight to running the loop. A task whose Issue refuses preparation (a missing brief section, an unmet dispatch gate) is refused before any agent starts, with nothing posted.",
|
|
5850
|
+
"Refuses when the frozen brief's developer branch already has an open pull request — the old `task dispatch` followed by `task run` cannot start two developers this way.",
|
|
5851
|
+
"Exit and printed summary distinguish a published, reviewed pull request (exit `0`, the PR URL) from a pause (exit `1`, with the exact `vinaya dev-review-loop --resume <pr>` command to continue), a usage/argv error (exit `2`), and any other failure (exit `3`) — never sharing `1` with a pause, so an unattended host tells them apart from the exit code alone. A run that pauses is resumed with the loop's own existing `--resume <pr>` flag, never a flag on this command."
|
|
5852
|
+
],
|
|
5853
|
+
status: "shipped"
|
|
5854
|
+
},
|
|
5855
|
+
{
|
|
5856
|
+
name: "task status",
|
|
5857
|
+
description: "Every open task with a frozen brief, its pull request, and whether its loop is running, paused, or published",
|
|
5858
|
+
flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
|
|
5859
|
+
details: [
|
|
5860
|
+
"Read-only: one `gh issue list` for every open task Issue across every tranche (title/label resolved through the same `resolveTaskIssueRef` `list-tasks.ts` already uses), the open pull request per branch, and the driver pid record / pause record / publish effect markers under `<outboxRoot>/dev-review-loop/<task>/` — never a `ps` scan, never a re-parse of posted verdict comments to decide `published`.",
|
|
5861
|
+
"`running` names the driver's pid (`review-validity-v1` task 7's pid record); `paused` names the reason from the pause record; `published` means the newest round's reviewer and security verdict effect markers both read `posted`; `no driver` is the fallback when none of the above holds.",
|
|
5862
|
+
"`vinaya task status <tranche> <n>` narrows to one task and adds the last round's held or published verdict lines (`round-<n>-reviewer.md`/`round-<n>-security.md`, whichever their outbox carries) plus the exact `vinaya dev-review-loop --resume <pr>` command when paused."
|
|
5108
5863
|
],
|
|
5109
5864
|
status: "shipped"
|
|
5110
5865
|
},
|
|
@@ -5312,6 +6067,15 @@ var COMMANDS = [
|
|
|
5312
6067
|
],
|
|
5313
6068
|
status: "shipped"
|
|
5314
6069
|
},
|
|
6070
|
+
{
|
|
6071
|
+
name: "milestone status",
|
|
6072
|
+
description: "Print each of a Milestone's declared tranche intents with its derived lifecycle and issue counts",
|
|
6073
|
+
flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
|
|
6074
|
+
details: [
|
|
6075
|
+
"Read-only — writes nothing. For each `- <slug>: …` line in the Milestone's `### Tranche intents` section, prints the tranche's lifecycle (`planned`/`active`/`complete`) and its labeled Issues' counts (merged, open, not planned), all derived from the forge. Refuses if `<n>` isn't a real Milestone in this repo, or if the forge is unreachable."
|
|
6076
|
+
],
|
|
6077
|
+
status: "shipped"
|
|
6078
|
+
},
|
|
5315
6079
|
{
|
|
5316
6080
|
name: "review status",
|
|
5317
6081
|
description: "Print the review loop's own state for a PR, and its branch's distance from the base",
|
|
@@ -5608,10 +6372,10 @@ function createForgeSource(config) {
|
|
|
5608
6372
|
};
|
|
5609
6373
|
}
|
|
5610
6374
|
// ../../packages/sources/src/select-source.ts
|
|
5611
|
-
import { z as
|
|
5612
|
-
var StateSourceConfigSchema =
|
|
5613
|
-
|
|
5614
|
-
|
|
6375
|
+
import { z as z4 } from "zod";
|
|
6376
|
+
var StateSourceConfigSchema = z4.discriminatedUnion("kind", [
|
|
6377
|
+
z4.object({ kind: z4.literal("forge"), owner: z4.string(), repo: z4.string() }),
|
|
6378
|
+
z4.object({ kind: z4.literal("file"), root: z4.string().optional() })
|
|
5615
6379
|
]);
|
|
5616
6380
|
// src/checks/contract.ts
|
|
5617
6381
|
var CHECK_SCHEMA_VERSION = 1;
|
|
@@ -5625,27 +6389,29 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
5625
6389
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
5626
6390
|
import { homedir } from "node:os";
|
|
5627
6391
|
import { dirname, join, resolve } from "node:path";
|
|
5628
|
-
import { z as
|
|
6392
|
+
import { z as z5 } from "zod";
|
|
5629
6393
|
|
|
5630
6394
|
// src/lib/agent-vendors.ts
|
|
5631
6395
|
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
5632
6396
|
|
|
5633
6397
|
// src/lib/config.ts
|
|
5634
|
-
var EnvEntrySchema =
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
6398
|
+
var EnvEntrySchema = z5.union([
|
|
6399
|
+
z5.literal(true),
|
|
6400
|
+
z5.object({ optional: z5.literal(true) }),
|
|
6401
|
+
z5.object({ anyOf: z5.array(z5.string()).min(2) }),
|
|
6402
|
+
z5.string()
|
|
5639
6403
|
]);
|
|
5640
|
-
var CheckEntrySchema =
|
|
5641
|
-
run:
|
|
5642
|
-
scope:
|
|
5643
|
-
include:
|
|
5644
|
-
args:
|
|
5645
|
-
timeoutMs:
|
|
5646
|
-
env:
|
|
5647
|
-
requiresOpenPr:
|
|
5648
|
-
ownWorkflow:
|
|
6404
|
+
var CheckEntrySchema = z5.object({
|
|
6405
|
+
run: z5.string(),
|
|
6406
|
+
scope: z5.enum(["diff", "full"]),
|
|
6407
|
+
include: z5.array(z5.string()).optional(),
|
|
6408
|
+
args: z5.array(z5.string()).optional(),
|
|
6409
|
+
timeoutMs: z5.number().optional(),
|
|
6410
|
+
env: z5.record(z5.string(), EnvEntrySchema).optional(),
|
|
6411
|
+
requiresOpenPr: z5.boolean().optional(),
|
|
6412
|
+
ownWorkflow: z5.boolean().optional(),
|
|
6413
|
+
principalOwed: z5.literal(true).optional(),
|
|
6414
|
+
validates: z5.enum(["body", "issue"]).optional()
|
|
5649
6415
|
}).superRefine((entry2, ctx) => {
|
|
5650
6416
|
if (!entry2.env)
|
|
5651
6417
|
return;
|
|
@@ -5655,25 +6421,26 @@ var CheckEntrySchema = z3.object({
|
|
|
5655
6421
|
const members = value.anyOf;
|
|
5656
6422
|
if (new Set(members).size !== members.length) {
|
|
5657
6423
|
ctx.addIssue({
|
|
5658
|
-
code:
|
|
6424
|
+
code: z5.ZodIssueCode.custom,
|
|
5659
6425
|
message: `env.${key}.anyOf has duplicate members`,
|
|
5660
6426
|
path: ["env", key, "anyOf"]
|
|
5661
6427
|
});
|
|
5662
6428
|
}
|
|
5663
6429
|
if (!members.includes(key)) {
|
|
5664
6430
|
ctx.addIssue({
|
|
5665
|
-
code:
|
|
6431
|
+
code: z5.ZodIssueCode.custom,
|
|
5666
6432
|
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
5667
6433
|
path: ["env", key, "anyOf"]
|
|
5668
6434
|
});
|
|
5669
6435
|
}
|
|
5670
6436
|
}
|
|
5671
6437
|
});
|
|
5672
|
-
var RoleEntrySchema =
|
|
5673
|
-
contract:
|
|
6438
|
+
var RoleEntrySchema = z5.object({
|
|
6439
|
+
contract: z5.string().refine((p) => p.includes("/"), {
|
|
5674
6440
|
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
5675
6441
|
})
|
|
5676
6442
|
});
|
|
6443
|
+
var MAX_REPORT_COMMAND_TIMEOUT_MS = 3600000;
|
|
5677
6444
|
var BRIEF_BUILTINS = [
|
|
5678
6445
|
"tier",
|
|
5679
6446
|
"testPlan",
|
|
@@ -5693,17 +6460,17 @@ var BRIEF_BUILTINS = [
|
|
|
5693
6460
|
"briefSections",
|
|
5694
6461
|
"milestoneShape"
|
|
5695
6462
|
];
|
|
5696
|
-
var BriefSectionSchema =
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
6463
|
+
var BriefSectionSchema = z5.union([
|
|
6464
|
+
z5.object({ builtin: z5.enum(BRIEF_BUILTINS) }),
|
|
6465
|
+
z5.object({ heading: z5.string().min(1), name: z5.string().optional() }),
|
|
6466
|
+
z5.object({ field: z5.string().min(1), name: z5.string().optional() }),
|
|
6467
|
+
z5.object({ phrase: z5.string().min(1), name: z5.string().optional() })
|
|
5701
6468
|
]);
|
|
5702
|
-
var BriefSchemaSchema =
|
|
5703
|
-
pr:
|
|
5704
|
-
issue:
|
|
5705
|
-
milestone:
|
|
5706
|
-
ack:
|
|
6469
|
+
var BriefSchemaSchema = z5.object({
|
|
6470
|
+
pr: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
|
|
6471
|
+
issue: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
|
|
6472
|
+
milestone: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
|
|
6473
|
+
ack: z5.array(z5.enum(BRIEF_BUILTINS)).optional()
|
|
5707
6474
|
});
|
|
5708
6475
|
function isSafeRepoRelPath(p) {
|
|
5709
6476
|
if (p.length === 0)
|
|
@@ -5712,7 +6479,7 @@ function isSafeRepoRelPath(p) {
|
|
|
5712
6479
|
return false;
|
|
5713
6480
|
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
5714
6481
|
}
|
|
5715
|
-
var SafeRepoRelPath =
|
|
6482
|
+
var SafeRepoRelPath = z5.string().refine(isSafeRepoRelPath, {
|
|
5716
6483
|
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
5717
6484
|
});
|
|
5718
6485
|
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
@@ -5722,22 +6489,22 @@ function isCanonicalHookBlockPath(p) {
|
|
|
5722
6489
|
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
5723
6490
|
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
5724
6491
|
});
|
|
5725
|
-
var ManagedBlockRecordSchema =
|
|
6492
|
+
var ManagedBlockRecordSchema = z5.object({
|
|
5726
6493
|
path: ManagedHookBlockPath,
|
|
5727
|
-
marker:
|
|
5728
|
-
comment:
|
|
6494
|
+
marker: z5.string(),
|
|
6495
|
+
comment: z5.enum(["hash", "html"])
|
|
5729
6496
|
});
|
|
5730
|
-
var ManagedManifestSchema =
|
|
5731
|
-
version:
|
|
5732
|
-
files:
|
|
5733
|
-
blocks:
|
|
5734
|
-
labels:
|
|
5735
|
-
agents:
|
|
6497
|
+
var ManagedManifestSchema = z5.object({
|
|
6498
|
+
version: z5.number().int().positive(),
|
|
6499
|
+
files: z5.array(SafeRepoRelPath),
|
|
6500
|
+
blocks: z5.array(ManagedBlockRecordSchema),
|
|
6501
|
+
labels: z5.array(z5.string()),
|
|
6502
|
+
agents: z5.array(z5.enum(AGENT_VENDORS)).optional()
|
|
5736
6503
|
});
|
|
5737
|
-
var ProjectEntrySchema =
|
|
5738
|
-
name:
|
|
5739
|
-
description:
|
|
5740
|
-
path:
|
|
6504
|
+
var ProjectEntrySchema = z5.object({
|
|
6505
|
+
name: z5.string().min(1),
|
|
6506
|
+
description: z5.string().min(1).optional(),
|
|
6507
|
+
path: z5.string().min(1).optional()
|
|
5741
6508
|
});
|
|
5742
6509
|
function parseTokensCollectDeclaration(value) {
|
|
5743
6510
|
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
@@ -5748,34 +6515,55 @@ function parseTokensCollectDeclaration(value) {
|
|
|
5748
6515
|
return null;
|
|
5749
6516
|
return { interpreter, script };
|
|
5750
6517
|
}
|
|
5751
|
-
var VinayaConfigSchema =
|
|
5752
|
-
rings:
|
|
5753
|
-
ring1_forgeWriteInterception:
|
|
5754
|
-
ring2_asyncAudits:
|
|
6518
|
+
var VinayaConfigSchema = z5.object({
|
|
6519
|
+
rings: z5.object({
|
|
6520
|
+
ring1_forgeWriteInterception: z5.boolean(),
|
|
6521
|
+
ring2_asyncAudits: z5.boolean()
|
|
5755
6522
|
}).optional(),
|
|
5756
|
-
checks:
|
|
5757
|
-
roles:
|
|
6523
|
+
checks: z5.record(z5.string(), CheckEntrySchema).optional(),
|
|
6524
|
+
roles: z5.record(z5.string(), RoleEntrySchema).optional(),
|
|
5758
6525
|
briefSchema: BriefSchemaSchema.optional(),
|
|
5759
6526
|
managed: ManagedManifestSchema.optional(),
|
|
5760
|
-
principals:
|
|
5761
|
-
releaseActor:
|
|
5762
|
-
ci:
|
|
5763
|
-
tokens:
|
|
5764
|
-
collect:
|
|
6527
|
+
principals: z5.array(z5.string()).min(1).optional(),
|
|
6528
|
+
releaseActor: z5.string().min(1).optional(),
|
|
6529
|
+
ci: z5.object({ setup: z5.string().min(1) }).optional(),
|
|
6530
|
+
tokens: z5.object({
|
|
6531
|
+
collect: z5.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
5765
6532
|
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
5766
6533
|
})
|
|
5767
6534
|
}).optional(),
|
|
5768
|
-
blastRadius:
|
|
5769
|
-
proseGates:
|
|
5770
|
-
doctrineRoot:
|
|
5771
|
-
readerFacingPrefix:
|
|
5772
|
-
readerFacingSuffix:
|
|
5773
|
-
legacySlugDir:
|
|
6535
|
+
blastRadius: z5.object({ extraDomains: z5.array(z5.string()).optional() }).optional(),
|
|
6536
|
+
proseGates: z5.object({
|
|
6537
|
+
doctrineRoot: z5.string().min(1).optional(),
|
|
6538
|
+
readerFacingPrefix: z5.string().min(1).optional(),
|
|
6539
|
+
readerFacingSuffix: z5.string().min(1).optional(),
|
|
6540
|
+
legacySlugDir: z5.string().min(1).optional()
|
|
6541
|
+
}).optional(),
|
|
6542
|
+
projects: z5.array(ProjectEntrySchema).optional(),
|
|
6543
|
+
dispatch: z5.object({
|
|
6544
|
+
timeoutMs: z5.number().int().positive().optional(),
|
|
6545
|
+
killGraceMs: z5.number().int().positive().optional(),
|
|
6546
|
+
agent: z5.enum(["claude", "codex", "gemini"]).optional()
|
|
6547
|
+
}).optional(),
|
|
6548
|
+
reviewPolicy: z5.object({
|
|
6549
|
+
codeReviewThreshold: z5.string().min(1).optional(),
|
|
6550
|
+
securityThreshold: z5.string().min(1).optional(),
|
|
6551
|
+
maxRounds: z5.number().optional()
|
|
6552
|
+
}).optional(),
|
|
6553
|
+
prePush: z5.object({
|
|
6554
|
+
alwaysRun: z5.array(z5.string()).optional()
|
|
5774
6555
|
}).optional(),
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
6556
|
+
report: z5.object({
|
|
6557
|
+
commandTimeoutMs: z5.number().int().positive().optional()
|
|
6558
|
+
}).superRefine((report, ctx) => {
|
|
6559
|
+
const commandTimeoutMs = report.commandTimeoutMs;
|
|
6560
|
+
if (commandTimeoutMs !== undefined && commandTimeoutMs > MAX_REPORT_COMMAND_TIMEOUT_MS) {
|
|
6561
|
+
ctx.addIssue({
|
|
6562
|
+
code: z5.ZodIssueCode.custom,
|
|
6563
|
+
path: ["commandTimeoutMs"],
|
|
6564
|
+
message: "must be at most 3600000 (1 hour)"
|
|
6565
|
+
});
|
|
6566
|
+
}
|
|
5779
6567
|
}).optional()
|
|
5780
6568
|
});
|
|
5781
6569
|
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
@@ -5850,6 +6638,28 @@ function resolvePrincipalAllowlist(config) {
|
|
|
5850
6638
|
function resolveReleaseActor(config) {
|
|
5851
6639
|
return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
|
|
5852
6640
|
}
|
|
6641
|
+
function resolveReviewPolicy(config) {
|
|
6642
|
+
const raw = config?.reviewPolicy;
|
|
6643
|
+
if (!raw)
|
|
6644
|
+
return DEFAULT_REVIEW_POLICY;
|
|
6645
|
+
const codeReviewThreshold = raw.codeReviewThreshold ?? DEFAULT_REVIEW_POLICY.codeReviewThreshold;
|
|
6646
|
+
if (!isKnownSeverity(CODE_REVIEW_SEVERITY_ORDER, codeReviewThreshold)) {
|
|
6647
|
+
throw new Error(`vinaya.config.json: reviewPolicy.codeReviewThreshold "${codeReviewThreshold}" is not one of ${CODE_REVIEW_SEVERITY_ORDER.join(" > ")} — fix the config, this never falls back to a default.`);
|
|
6648
|
+
}
|
|
6649
|
+
const securityThreshold = raw.securityThreshold ?? DEFAULT_REVIEW_POLICY.securityThreshold;
|
|
6650
|
+
if (!isKnownSeverity(SECURITY_SEVERITY_ORDER, securityThreshold)) {
|
|
6651
|
+
throw new Error(`vinaya.config.json: reviewPolicy.securityThreshold "${securityThreshold}" is not one of ${SECURITY_SEVERITY_ORDER.join(" > ")} — fix the config, this never falls back to a default.`);
|
|
6652
|
+
}
|
|
6653
|
+
const maxRounds = raw.maxRounds ?? DEFAULT_REVIEW_POLICY.maxRounds;
|
|
6654
|
+
if (!Number.isInteger(maxRounds) || maxRounds < 1) {
|
|
6655
|
+
throw new Error(`vinaya.config.json: reviewPolicy.maxRounds "${maxRounds}" is not a positive integer — fix the config, this never falls back to a default.`);
|
|
6656
|
+
}
|
|
6657
|
+
return {
|
|
6658
|
+
codeReviewThreshold,
|
|
6659
|
+
securityThreshold,
|
|
6660
|
+
maxRounds
|
|
6661
|
+
};
|
|
6662
|
+
}
|
|
5853
6663
|
function trustAnchorRepo() {
|
|
5854
6664
|
const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
5855
6665
|
const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
|
|
@@ -6187,14 +6997,99 @@ function containedRealPath(root, p) {
|
|
|
6187
6997
|
return null;
|
|
6188
6998
|
}
|
|
6189
6999
|
}
|
|
7000
|
+
function fetchIssueJson(issueNumber) {
|
|
7001
|
+
try {
|
|
7002
|
+
const out = execFileSync4("gh", ["issue", "view", String(issueNumber), "--json", "number,state,body,labels"], {
|
|
7003
|
+
encoding: "utf8",
|
|
7004
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
7005
|
+
});
|
|
7006
|
+
return JSON.parse(out);
|
|
7007
|
+
} catch {
|
|
7008
|
+
return null;
|
|
7009
|
+
}
|
|
7010
|
+
}
|
|
7011
|
+
async function runIssueMode(issueNumber) {
|
|
7012
|
+
const issueJson = fetchIssueJson(issueNumber);
|
|
7013
|
+
if (!issueJson) {
|
|
7014
|
+
fail(`dispatch-gate issue-existence: Issue #${issueNumber} does not resolve on the forge.`, "Confirm the Issue number is correct and `gh auth status` passes, then re-run `vinaya check dispatch-readiness`.");
|
|
7015
|
+
}
|
|
7016
|
+
if (issueJson.labels.some((l) => l.name.startsWith("vinaya/tranche:"))) {
|
|
7017
|
+
fail(`dispatch-gate: Issue #${issueNumber} carries a vinaya/tranche:* label — it belongs to a tranche, not the tranche-less backlog path.`, "This Issue has a real tranche home — run this check on its tranche-shaped branch instead.");
|
|
7018
|
+
}
|
|
7019
|
+
const repo = resolveRepo2();
|
|
7020
|
+
if (!repo) {
|
|
7021
|
+
fail("dispatch-gate severity:infra — could not resolve owner/repo.", "Set AEG_REPO=owner/repo, or confirm `git remote get-url origin` resolves to a GitHub URL, then re-run `vinaya check dispatch-readiness`.");
|
|
7022
|
+
}
|
|
7023
|
+
const issueRationalePass = checkIssueRationale(issueJson.body).status !== "fail";
|
|
7024
|
+
const { dependsOn: dependsOnIds, conflictsWith: conflictsWithIds } = parseRationaleDeps(issueJson.body);
|
|
7025
|
+
const taskById = new Map;
|
|
7026
|
+
const factsByTaskId = new Map;
|
|
7027
|
+
const dependsOn = await Promise.all(dependsOnIds.map(async (dep) => {
|
|
7028
|
+
const r = await resolveEdge(dep, taskById, factsByTaskId, repo);
|
|
7029
|
+
return {
|
|
7030
|
+
id: dep,
|
|
7031
|
+
issue: r.issue,
|
|
7032
|
+
merged: r.merged,
|
|
7033
|
+
resolved: r.resolved,
|
|
7034
|
+
issueState: r.issueState,
|
|
7035
|
+
stateReason: r.stateReason,
|
|
7036
|
+
closedByActor: r.closedByActor
|
|
7037
|
+
};
|
|
7038
|
+
}));
|
|
7039
|
+
const conflictsWith = await Promise.all(conflictsWithIds.map(async (c) => {
|
|
7040
|
+
const r = await resolveEdge(c, taskById, factsByTaskId, repo);
|
|
7041
|
+
return { id: c, issue: r.issue, openOrInFlight: r.open };
|
|
7042
|
+
}));
|
|
7043
|
+
const task = {
|
|
7044
|
+
id: String(issueNumber),
|
|
7045
|
+
title: "",
|
|
7046
|
+
issue: issueNumber,
|
|
7047
|
+
projects: [],
|
|
7048
|
+
dependsOn: dependsOnIds,
|
|
7049
|
+
conflictsWith: conflictsWithIds,
|
|
7050
|
+
rationaleMarkdown: ""
|
|
7051
|
+
};
|
|
7052
|
+
const input = {
|
|
7053
|
+
trancheSlug: `issue-${issueNumber}`,
|
|
7054
|
+
task,
|
|
7055
|
+
issue: { number: issueNumber, state: issueJson.state === "OPEN" ? "open" : "closed" },
|
|
7056
|
+
issueRationalePass,
|
|
7057
|
+
dependsOn,
|
|
7058
|
+
conflictsWith,
|
|
7059
|
+
priorTask: null,
|
|
7060
|
+
priorTrancheArchival: [],
|
|
7061
|
+
principalAllowlist: resolvePrincipalAllowlist(loadTrustAnchorConfig())
|
|
7062
|
+
};
|
|
7063
|
+
const result = checkDispatchReadiness(input);
|
|
7064
|
+
let ready = true;
|
|
7065
|
+
if (!result.ready) {
|
|
7066
|
+
for (const detail of result.blockerDetails) {
|
|
7067
|
+
emitCheckError({
|
|
7068
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
7069
|
+
check: CHECK_NAME,
|
|
7070
|
+
severity: "error",
|
|
7071
|
+
message: detail.message,
|
|
7072
|
+
agent_recovery_prompt: recoveryPromptFor(detail.class)
|
|
7073
|
+
});
|
|
7074
|
+
}
|
|
7075
|
+
ready = false;
|
|
7076
|
+
}
|
|
7077
|
+
if (!checkPremiseReassertion())
|
|
7078
|
+
ready = false;
|
|
7079
|
+
process.exit(ready ? 0 : 1);
|
|
7080
|
+
}
|
|
6190
7081
|
async function main() {
|
|
6191
7082
|
const branch = currentBranch();
|
|
6192
|
-
const
|
|
6193
|
-
if (!
|
|
7083
|
+
const ref = parseTaskBranchIdentity(branch);
|
|
7084
|
+
if (!ref) {
|
|
6194
7085
|
process.exit(0);
|
|
6195
7086
|
}
|
|
6196
|
-
|
|
6197
|
-
|
|
7087
|
+
if (ref.kind === "issue") {
|
|
7088
|
+
await runIssueMode(ref.issueNumber);
|
|
7089
|
+
return;
|
|
7090
|
+
}
|
|
7091
|
+
const trancheSlug = ref.tranche;
|
|
7092
|
+
const taskId2 = ref.taskId;
|
|
6198
7093
|
const repo = resolveRepo2();
|
|
6199
7094
|
if (!repo) {
|
|
6200
7095
|
fail("dispatch-gate severity:infra — could not resolve owner/repo.", "Set AEG_REPO=owner/repo, or confirm `git remote get-url origin` resolves to a GitHub URL, then re-run `vinaya check dispatch-readiness`.");
|
|
@@ -6206,9 +7101,9 @@ async function main() {
|
|
|
6206
7101
|
} catch (err) {
|
|
6207
7102
|
fail(`dispatch-gate severity:infra — could not derive tranche "${trancheSlug}" from the forge: ${err.message}`, "Confirm `gh auth status` passes and the tranche has a Milestone + labeled Issues on the forge, then re-run this check.");
|
|
6208
7103
|
}
|
|
6209
|
-
const task = tranche.tasks.find((t) => t.id ===
|
|
7104
|
+
const task = tranche.tasks.find((t) => t.id === taskId2);
|
|
6210
7105
|
if (!task) {
|
|
6211
|
-
fail(`dispatch-gate row-existence: task "${
|
|
7106
|
+
fail(`dispatch-gate row-existence: task "${taskId2}" is not present in tranche "${trancheSlug}"'s forge-derived task list.`, "Confirm the branch name matches a real, forge-registered task id, or wait for the Planner to open the task Issue before re-running.");
|
|
6212
7107
|
}
|
|
6213
7108
|
const taskRefs = tranche.tasks.map((t) => ({ id: t.id, issue: t.issue }));
|
|
6214
7109
|
const snapshot = await fetchForgeFacts({
|
|
@@ -6257,13 +7152,13 @@ async function main() {
|
|
|
6257
7152
|
const result = checkDispatchReadiness(input);
|
|
6258
7153
|
let ready = true;
|
|
6259
7154
|
if (!result.ready) {
|
|
6260
|
-
for (const
|
|
7155
|
+
for (const detail of result.blockerDetails) {
|
|
6261
7156
|
emitCheckError({
|
|
6262
7157
|
schema: CHECK_SCHEMA_VERSION,
|
|
6263
7158
|
check: CHECK_NAME,
|
|
6264
7159
|
severity: "error",
|
|
6265
|
-
message:
|
|
6266
|
-
agent_recovery_prompt: recoveryPromptFor(
|
|
7160
|
+
message: detail.message,
|
|
7161
|
+
agent_recovery_prompt: recoveryPromptFor(detail.class)
|
|
6267
7162
|
});
|
|
6268
7163
|
}
|
|
6269
7164
|
ready = false;
|
|
@@ -6272,29 +7167,28 @@ async function main() {
|
|
|
6272
7167
|
ready = false;
|
|
6273
7168
|
process.exit(ready ? 0 : 1);
|
|
6274
7169
|
}
|
|
6275
|
-
function recoveryPromptFor(
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
}
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
}
|
|
6297
|
-
return "Resolve the named dispatch blocker before continuing work on this task, then re-run `vinaya check dispatch-readiness`.";
|
|
7170
|
+
function recoveryPromptFor(blockerClass) {
|
|
7171
|
+
switch (blockerClass) {
|
|
7172
|
+
case "internal-self-dependency":
|
|
7173
|
+
return "This is an INTERNAL parser-bug report, not a real dispatch gate — a task cannot depend on itself, so there is nothing here to wait for or resolve. Do NOT work around it and do NOT skip the hook. Report it upstream, and re-run once the rationale text is corrected or the parser fix ships.";
|
|
7174
|
+
case "issue-existence":
|
|
7175
|
+
return "This task has no resolvable Issue yet. Wait for the Planner to cut the Issue (or fix the phantom reference in the topology), then re-run `vinaya check dispatch-readiness`.";
|
|
7176
|
+
case "rationale":
|
|
7177
|
+
return "The task's Issue fails the rationale gate. Ask the Planner to complete the eight-field rationale on the Issue body, then re-run `vinaya check dispatch-readiness`.";
|
|
7178
|
+
case "depends-on-unresolvable":
|
|
7179
|
+
return "A declared dependency edge could not be resolved to any tranche/task/Issue — check the tranche slug and task id in the edge text, then re-run `vinaya check dispatch-readiness`.";
|
|
7180
|
+
case "depends-on-not-merged":
|
|
7181
|
+
return "A declared dependency is not merged yet. Do not start this task — wait for the named dependency PR to merge, then re-run `vinaya check dispatch-readiness`.";
|
|
7182
|
+
case "conflicts-with":
|
|
7183
|
+
return "A declared conflicting task has an open or in-flight PR. Wait for it to merge before continuing, then re-run `vinaya check dispatch-readiness`.";
|
|
7184
|
+
case "prior-tranche-archival":
|
|
7185
|
+
return "This project's previous tranche is not archived. Ask the Tranche Archivist to run first, then re-run `vinaya check dispatch-readiness`.";
|
|
7186
|
+
default:
|
|
7187
|
+
return assertNeverBlockerClass(blockerClass);
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
function assertNeverBlockerClass(x) {
|
|
7191
|
+
throw new Error(`Unhandled DispatchBlockerClass: ${JSON.stringify(x)}`);
|
|
6298
7192
|
}
|
|
6299
7193
|
if (__require.main == __require.module) {
|
|
6300
7194
|
main();
|