@attalabs/vinaya 0.26.0 → 0.27.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 +4 -2
- package/aeg-root/enforcement.md +2 -2
- package/aeg-root/milestone-model.md +2 -0
- package/aeg-root/process.md +1 -1
- package/aeg-root/roles/planner.md +2 -0
- package/aeg-root/roles/principal.md +4 -2
- package/aeg-root/roles/reviewer.md +6 -2
- package/aeg-root/roles/security.md +6 -2
- package/dist/checks/bin/check-body-bare-digits.js +128 -22
- package/dist/checks/bin/check-branch-topology.js +148 -24
- package/dist/checks/bin/check-brief-shape.js +430 -32
- package/dist/checks/bin/check-changeset-coverage.js +148 -24
- package/dist/checks/bin/check-closes-n.js +148 -24
- package/dist/checks/bin/check-coherence.js +148 -24
- package/dist/checks/bin/check-dead-branch-push.js +110 -22
- package/dist/checks/bin/check-dispatch-readiness.js +148 -24
- package/dist/checks/bin/check-doc-coverage-push.js +148 -24
- package/dist/checks/bin/check-doc-coverage.js +148 -24
- package/dist/checks/bin/check-doctrine-no-procedures.js +110 -22
- package/dist/checks/bin/check-doctrine-portability.js +148 -24
- package/dist/checks/bin/check-evidence-fresh.js +650 -63
- package/dist/checks/bin/check-exec-bits.js +148 -24
- package/dist/checks/bin/check-first-push-dispatch.js +148 -24
- package/dist/checks/bin/check-issue-assignment.js +148 -24
- package/dist/checks/bin/check-main-branch-refusal.js +110 -22
- package/dist/checks/bin/check-no-disk-state.js +110 -22
- package/dist/checks/bin/check-pr-premise-reassert.js +110 -22
- package/dist/checks/bin/check-pr-report-density.js +110 -22
- package/dist/checks/bin/check-quoted-command.js +148 -24
- package/dist/checks/bin/check-reader-resolvable-prose.js +148 -24
- package/dist/checks/bin/check-registry-gates.js +110 -22
- package/dist/checks/bin/check-retired-vocabulary.js +148 -24
- package/dist/checks/bin/check-review-gate.js +169 -77
- package/dist/checks/bin/check-single-plan-pr.js +110 -22
- package/dist/checks/bin/check-surface-scope.js +148 -24
- package/dist/checks/bin/check-test-plan.js +110 -22
- package/dist/checks/bin/check-token-collection-wired.js +110 -22
- package/dist/checks/bin/check-token-report.js +110 -22
- package/dist/checks/bin/check-workspace-escape.js +148 -24
- package/dist/index.js +1584 -572
- package/package.json +1 -1
|
@@ -305,7 +305,7 @@ function issueListByLabelArgs(owner, repo, label) {
|
|
|
305
305
|
"--state",
|
|
306
306
|
"all",
|
|
307
307
|
"--json",
|
|
308
|
-
"number,title,body,state,labels,milestone",
|
|
308
|
+
"number,title,body,state,labels,milestone,stateReason",
|
|
309
309
|
"--limit",
|
|
310
310
|
"200"
|
|
311
311
|
];
|
|
@@ -651,14 +651,19 @@ function resolveTaskIssueRef(title, labels) {
|
|
|
651
651
|
var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
|
|
652
652
|
var NEXT_HEADING = /^#{1,6}\s+\S/m;
|
|
653
653
|
var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
654
|
-
function
|
|
655
|
-
const text = stripCode(description, { inlineSpans: "keep" });
|
|
654
|
+
function intentsSection(text) {
|
|
656
655
|
const start = text.match(INTENTS_HEADING);
|
|
657
656
|
if (!start || start.index === undefined)
|
|
658
|
-
return
|
|
657
|
+
return null;
|
|
659
658
|
const rest = text.slice(start.index + start[0].length);
|
|
660
659
|
const next = rest.match(NEXT_HEADING);
|
|
661
|
-
|
|
660
|
+
return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
|
|
661
|
+
}
|
|
662
|
+
function intentGoalForSlug(description, slug) {
|
|
663
|
+
const text = stripCode(description, { inlineSpans: "keep" });
|
|
664
|
+
const section = intentsSection(text);
|
|
665
|
+
if (section === null)
|
|
666
|
+
return "";
|
|
662
667
|
for (const line of section.split(`
|
|
663
668
|
`)) {
|
|
664
669
|
const trimmed = line.trim();
|
|
@@ -1950,6 +1955,7 @@ function parseInlineFieldList(section) {
|
|
|
1950
1955
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1951
1956
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1952
1957
|
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1958
|
+
var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
|
|
1953
1959
|
function firstFiveLines(comment) {
|
|
1954
1960
|
return comment.split(`
|
|
1955
1961
|
`).slice(0, 5).join(`
|
|
@@ -1963,6 +1969,15 @@ function extractObjectivesVersion(comment) {
|
|
|
1963
1969
|
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1964
1970
|
return m ? m[1].toLowerCase() : null;
|
|
1965
1971
|
}
|
|
1972
|
+
function firstSevenLines(comment) {
|
|
1973
|
+
return comment.split(`
|
|
1974
|
+
`).slice(0, 7).join(`
|
|
1975
|
+
`);
|
|
1976
|
+
}
|
|
1977
|
+
function extractRulingOrdinal(comment) {
|
|
1978
|
+
const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
|
|
1979
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
1980
|
+
}
|
|
1966
1981
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1967
1982
|
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1968
1983
|
if (candidates.length === 0) {
|
|
@@ -1970,6 +1985,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1970
1985
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1971
1986
|
headSha: null,
|
|
1972
1987
|
objectivesVersion: null,
|
|
1988
|
+
rulingOrdinal: null,
|
|
1973
1989
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1974
1990
|
};
|
|
1975
1991
|
}
|
|
@@ -1980,6 +1996,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1980
1996
|
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1981
1997
|
headSha: null,
|
|
1982
1998
|
objectivesVersion: null,
|
|
1999
|
+
rulingOrdinal: null,
|
|
1983
2000
|
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1984
2001
|
};
|
|
1985
2002
|
}
|
|
@@ -1987,6 +2004,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1987
2004
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1988
2005
|
headSha: extractHeadSha(latest),
|
|
1989
2006
|
objectivesVersion: extractObjectivesVersion(latest),
|
|
2007
|
+
rulingOrdinal: extractRulingOrdinal(latest),
|
|
1990
2008
|
danglingNote: null
|
|
1991
2009
|
};
|
|
1992
2010
|
}
|
|
@@ -2348,6 +2366,9 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2348
2366
|
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2349
2367
|
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2350
2368
|
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2369
|
+
function maskedForHeadingSearch(body) {
|
|
2370
|
+
return maskDetailsBlocks(maskCode(body));
|
|
2371
|
+
}
|
|
2351
2372
|
function hasBacktickedPath(text) {
|
|
2352
2373
|
let i = 0;
|
|
2353
2374
|
while (i < text.length) {
|
|
@@ -2370,16 +2391,22 @@ function stripObjectiveBackticks(text) {
|
|
|
2370
2391
|
function wordCount(text) {
|
|
2371
2392
|
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2372
2393
|
}
|
|
2373
|
-
function
|
|
2374
|
-
const
|
|
2394
|
+
function objectivesSectionBounds(body) {
|
|
2395
|
+
const masked = maskedForHeadingSearch(body);
|
|
2396
|
+
const heading = HEADING_RE.exec(masked);
|
|
2375
2397
|
if (!heading)
|
|
2376
2398
|
return null;
|
|
2377
|
-
const
|
|
2399
|
+
const start = heading.index + heading[0].length;
|
|
2400
|
+
const afterHeading = masked.slice(start);
|
|
2378
2401
|
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2379
|
-
return next ?
|
|
2402
|
+
return { start, end: next ? start + next.index : body.length };
|
|
2403
|
+
}
|
|
2404
|
+
function objectivesSectionText(body) {
|
|
2405
|
+
const bounds = objectivesSectionBounds(body);
|
|
2406
|
+
return bounds === null ? null : body.slice(bounds.start, bounds.end);
|
|
2380
2407
|
}
|
|
2381
2408
|
function hasObjectivesHeading(body) {
|
|
2382
|
-
return HEADING_RE.test(body);
|
|
2409
|
+
return HEADING_RE.test(maskedForHeadingSearch(body));
|
|
2383
2410
|
}
|
|
2384
2411
|
function objectivesOf(body) {
|
|
2385
2412
|
const section = objectivesSectionText(body);
|
|
@@ -2439,6 +2466,15 @@ function isIssueNotFoundError(err) {
|
|
|
2439
2466
|
`);
|
|
2440
2467
|
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2441
2468
|
}
|
|
2469
|
+
function resolveObjectivesSource(prBody, issue, cutoverIssue) {
|
|
2470
|
+
if (issue !== null && issue < cutoverIssue)
|
|
2471
|
+
return { kind: "none" };
|
|
2472
|
+
if (issue !== null)
|
|
2473
|
+
return { kind: "issue", issue };
|
|
2474
|
+
if (hasObjectivesHeading(prBody))
|
|
2475
|
+
return { kind: "body" };
|
|
2476
|
+
return { kind: "none" };
|
|
2477
|
+
}
|
|
2442
2478
|
|
|
2443
2479
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2444
2480
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -2940,17 +2976,40 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2940
2976
|
];
|
|
2941
2977
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2942
2978
|
}
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
`
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2979
|
+
function contentAfterNLines(body, n) {
|
|
2980
|
+
let idx = -1;
|
|
2981
|
+
for (let i = 0;i < n; i++) {
|
|
2982
|
+
idx = body.indexOf(`
|
|
2983
|
+
`, idx + 1);
|
|
2984
|
+
if (idx === -1)
|
|
2985
|
+
return "";
|
|
2986
|
+
}
|
|
2987
|
+
return body.slice(idx + 1);
|
|
2988
|
+
}
|
|
2989
|
+
var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
|
|
2990
|
+
function parseBriefMarkerVersion(firstLine) {
|
|
2991
|
+
const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
|
|
2992
|
+
if (!m)
|
|
2993
|
+
return null;
|
|
2994
|
+
const version = Number.parseInt(m[1], 10);
|
|
2995
|
+
return Number.isInteger(version) && version >= 1 ? version : null;
|
|
2996
|
+
}
|
|
2997
|
+
function frozenBriefContent(body, version) {
|
|
2998
|
+
return contentAfterNLines(body, version === 1 ? 2 : 3);
|
|
2999
|
+
}
|
|
3000
|
+
function resolveNewestFrozenBrief(comments, allowlist) {
|
|
3001
|
+
let best = null;
|
|
3002
|
+
for (const c of comments) {
|
|
3003
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3004
|
+
continue;
|
|
3005
|
+
const version = parseBriefMarkerVersion(c.body.split(`
|
|
3006
|
+
`)[0] ?? "");
|
|
3007
|
+
if (version === null)
|
|
3008
|
+
continue;
|
|
3009
|
+
if (best === null || version > best.version)
|
|
3010
|
+
best = { ...c, version };
|
|
3011
|
+
}
|
|
3012
|
+
return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
|
|
2954
3013
|
}
|
|
2955
3014
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2956
3015
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
@@ -3677,6 +3736,11 @@ function isBoundToObjectives(extraction, currentVersion) {
|
|
|
3677
3736
|
return true;
|
|
3678
3737
|
return extraction.objectivesVersion === currentVersion;
|
|
3679
3738
|
}
|
|
3739
|
+
function isBoundToRulings(extraction, currentOrdinal) {
|
|
3740
|
+
if (extraction.rulingOrdinal === null)
|
|
3741
|
+
return currentOrdinal === 0;
|
|
3742
|
+
return extraction.rulingOrdinal === currentOrdinal;
|
|
3743
|
+
}
|
|
3680
3744
|
function checkReviewGate(input) {
|
|
3681
3745
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3682
3746
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3705,7 +3769,9 @@ function checkReviewGate(input) {
|
|
|
3705
3769
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3706
3770
|
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3707
3771
|
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3708
|
-
|
|
3772
|
+
const codeReviewRulingsBound = isBoundToRulings(codeReview, input.rulingOrdinal);
|
|
3773
|
+
const securityRulingsBound = isBoundToRulings(security, input.rulingOrdinal);
|
|
3774
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && codeReviewRulingsBound && securityClean && securityBound && securityObjectivesBound && securityRulingsBound && mechanicalChecksClean) {
|
|
3709
3775
|
return {
|
|
3710
3776
|
verdict: "pass",
|
|
3711
3777
|
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.`,
|
|
@@ -3719,6 +3785,8 @@ function checkReviewGate(input) {
|
|
|
3719
3785
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3720
3786
|
} else if (!codeReviewObjectivesBound) {
|
|
3721
3787
|
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3788
|
+
} else if (!codeReviewRulingsBound) {
|
|
3789
|
+
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`);
|
|
3722
3790
|
}
|
|
3723
3791
|
if (!securityClean) {
|
|
3724
3792
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
@@ -3726,6 +3794,8 @@ function checkReviewGate(input) {
|
|
|
3726
3794
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3727
3795
|
} else if (!securityObjectivesBound) {
|
|
3728
3796
|
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3797
|
+
} else if (!securityRulingsBound) {
|
|
3798
|
+
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`);
|
|
3729
3799
|
}
|
|
3730
3800
|
if (!mechanicalChecksClean) {
|
|
3731
3801
|
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(", ")}`);
|
|
@@ -3742,6 +3812,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
|
|
|
3742
3812
|
function isChangesetsReleasePr(branch, author, expectedAuthor) {
|
|
3743
3813
|
return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
|
|
3744
3814
|
}
|
|
3815
|
+
// ../../packages/aeg-core/src/ruling-ordinal.ts
|
|
3816
|
+
var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
|
|
3817
|
+
function newestPrincipalRulingOrdinal(comments, allowlist) {
|
|
3818
|
+
let best = 0;
|
|
3819
|
+
for (const c of comments) {
|
|
3820
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3821
|
+
continue;
|
|
3822
|
+
const firstLine = (c.body.split(`
|
|
3823
|
+
`)[0] ?? "").trim();
|
|
3824
|
+
const m = RULING_MARKER_ORDINAL.exec(firstLine);
|
|
3825
|
+
if (!m)
|
|
3826
|
+
continue;
|
|
3827
|
+
const k = Number.parseInt(m[1], 10);
|
|
3828
|
+
if (k > best)
|
|
3829
|
+
best = k;
|
|
3830
|
+
}
|
|
3831
|
+
return best;
|
|
3832
|
+
}
|
|
3745
3833
|
// ../../packages/aeg-core/src/markdown-table.ts
|
|
3746
3834
|
function splitRow(line) {
|
|
3747
3835
|
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
@@ -5409,14 +5497,41 @@ var COMMANDS = [
|
|
|
5409
5497
|
},
|
|
5410
5498
|
{
|
|
5411
5499
|
name: "task dispatch",
|
|
5412
|
-
description: "
|
|
5500
|
+
description: "Deprecated — render, pin, and post the brief on the Issue as the frozen original; start the developer",
|
|
5413
5501
|
flags: [
|
|
5414
5502
|
{ flag: "--agent <claude|codex|gemini>", description: "Start the developer through dispatchRole once posted" }
|
|
5415
5503
|
],
|
|
5416
5504
|
details: [
|
|
5417
5505
|
"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.",
|
|
5418
5506
|
"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.",
|
|
5419
|
-
"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."
|
|
5507
|
+
"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.",
|
|
5508
|
+
"Deprecated in favor of `task brief` (preparation only) and `task run` (the full unattended loop) — kept for a documented compatibility window while callers migrate."
|
|
5509
|
+
],
|
|
5510
|
+
status: "shipped"
|
|
5511
|
+
},
|
|
5512
|
+
{
|
|
5513
|
+
name: "task brief",
|
|
5514
|
+
description: "Render and freeze the brief as the Issue's original comment — preparation only, starts nobody",
|
|
5515
|
+
details: [
|
|
5516
|
+
"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.",
|
|
5517
|
+
"Successor to `task dispatch` for the preparation step; the full unattended run (preparation, then the developer, then the review loop) is `task run`."
|
|
5518
|
+
],
|
|
5519
|
+
status: "shipped"
|
|
5520
|
+
},
|
|
5521
|
+
{
|
|
5522
|
+
name: "task run",
|
|
5523
|
+
description: "One command from a planned Issue to a reviewed pull request — exactly one developer started",
|
|
5524
|
+
flags: [
|
|
5525
|
+
{
|
|
5526
|
+
flag: "--agent <claude|codex|gemini>",
|
|
5527
|
+
description: "Vendor for the developer and both reviewers this run dispatches"
|
|
5528
|
+
}
|
|
5529
|
+
],
|
|
5530
|
+
details: [
|
|
5531
|
+
"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.",
|
|
5532
|
+
"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.",
|
|
5533
|
+
"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.",
|
|
5534
|
+
"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."
|
|
5420
5535
|
],
|
|
5421
5536
|
status: "shipped"
|
|
5422
5537
|
},
|
|
@@ -5624,6 +5739,15 @@ var COMMANDS = [
|
|
|
5624
5739
|
],
|
|
5625
5740
|
status: "shipped"
|
|
5626
5741
|
},
|
|
5742
|
+
{
|
|
5743
|
+
name: "milestone status",
|
|
5744
|
+
description: "Print each of a Milestone's declared tranche intents with its derived lifecycle and issue counts",
|
|
5745
|
+
flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
|
|
5746
|
+
details: [
|
|
5747
|
+
"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."
|
|
5748
|
+
],
|
|
5749
|
+
status: "shipped"
|
|
5750
|
+
},
|
|
5627
5751
|
{
|
|
5628
5752
|
name: "review status",
|
|
5629
5753
|
description: "Print the review loop's own state for a PR, and its branch's distance from the base",
|
|
@@ -303,7 +303,7 @@ function issueListByLabelArgs(owner, repo, label) {
|
|
|
303
303
|
"--state",
|
|
304
304
|
"all",
|
|
305
305
|
"--json",
|
|
306
|
-
"number,title,body,state,labels,milestone",
|
|
306
|
+
"number,title,body,state,labels,milestone,stateReason",
|
|
307
307
|
"--limit",
|
|
308
308
|
"200"
|
|
309
309
|
];
|
|
@@ -649,14 +649,19 @@ function resolveTaskIssueRef(title, labels) {
|
|
|
649
649
|
var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
|
|
650
650
|
var NEXT_HEADING = /^#{1,6}\s+\S/m;
|
|
651
651
|
var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
652
|
-
function
|
|
653
|
-
const text = stripCode(description, { inlineSpans: "keep" });
|
|
652
|
+
function intentsSection(text) {
|
|
654
653
|
const start = text.match(INTENTS_HEADING);
|
|
655
654
|
if (!start || start.index === undefined)
|
|
656
|
-
return
|
|
655
|
+
return null;
|
|
657
656
|
const rest = text.slice(start.index + start[0].length);
|
|
658
657
|
const next = rest.match(NEXT_HEADING);
|
|
659
|
-
|
|
658
|
+
return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
|
|
659
|
+
}
|
|
660
|
+
function intentGoalForSlug(description, slug) {
|
|
661
|
+
const text = stripCode(description, { inlineSpans: "keep" });
|
|
662
|
+
const section = intentsSection(text);
|
|
663
|
+
if (section === null)
|
|
664
|
+
return "";
|
|
660
665
|
for (const line of section.split(`
|
|
661
666
|
`)) {
|
|
662
667
|
const trimmed = line.trim();
|
|
@@ -1948,6 +1953,7 @@ function parseInlineFieldList(section) {
|
|
|
1948
1953
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1949
1954
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1950
1955
|
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1956
|
+
var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
|
|
1951
1957
|
function firstFiveLines(comment) {
|
|
1952
1958
|
return comment.split(`
|
|
1953
1959
|
`).slice(0, 5).join(`
|
|
@@ -1961,6 +1967,15 @@ function extractObjectivesVersion(comment) {
|
|
|
1961
1967
|
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1962
1968
|
return m ? m[1].toLowerCase() : null;
|
|
1963
1969
|
}
|
|
1970
|
+
function firstSevenLines(comment) {
|
|
1971
|
+
return comment.split(`
|
|
1972
|
+
`).slice(0, 7).join(`
|
|
1973
|
+
`);
|
|
1974
|
+
}
|
|
1975
|
+
function extractRulingOrdinal(comment) {
|
|
1976
|
+
const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
|
|
1977
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
1978
|
+
}
|
|
1964
1979
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1965
1980
|
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1966
1981
|
if (candidates.length === 0) {
|
|
@@ -1968,6 +1983,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1968
1983
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1969
1984
|
headSha: null,
|
|
1970
1985
|
objectivesVersion: null,
|
|
1986
|
+
rulingOrdinal: null,
|
|
1971
1987
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1972
1988
|
};
|
|
1973
1989
|
}
|
|
@@ -1978,6 +1994,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1978
1994
|
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1979
1995
|
headSha: null,
|
|
1980
1996
|
objectivesVersion: null,
|
|
1997
|
+
rulingOrdinal: null,
|
|
1981
1998
|
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1982
1999
|
};
|
|
1983
2000
|
}
|
|
@@ -1985,6 +2002,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1985
2002
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1986
2003
|
headSha: extractHeadSha(latest),
|
|
1987
2004
|
objectivesVersion: extractObjectivesVersion(latest),
|
|
2005
|
+
rulingOrdinal: extractRulingOrdinal(latest),
|
|
1988
2006
|
danglingNote: null
|
|
1989
2007
|
};
|
|
1990
2008
|
}
|
|
@@ -2346,6 +2364,9 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2346
2364
|
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2347
2365
|
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2348
2366
|
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2367
|
+
function maskedForHeadingSearch(body) {
|
|
2368
|
+
return maskDetailsBlocks(maskCode(body));
|
|
2369
|
+
}
|
|
2349
2370
|
function hasBacktickedPath(text) {
|
|
2350
2371
|
let i = 0;
|
|
2351
2372
|
while (i < text.length) {
|
|
@@ -2368,16 +2389,22 @@ function stripObjectiveBackticks(text) {
|
|
|
2368
2389
|
function wordCount(text) {
|
|
2369
2390
|
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2370
2391
|
}
|
|
2371
|
-
function
|
|
2372
|
-
const
|
|
2392
|
+
function objectivesSectionBounds(body) {
|
|
2393
|
+
const masked = maskedForHeadingSearch(body);
|
|
2394
|
+
const heading = HEADING_RE.exec(masked);
|
|
2373
2395
|
if (!heading)
|
|
2374
2396
|
return null;
|
|
2375
|
-
const
|
|
2397
|
+
const start = heading.index + heading[0].length;
|
|
2398
|
+
const afterHeading = masked.slice(start);
|
|
2376
2399
|
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2377
|
-
return next ?
|
|
2400
|
+
return { start, end: next ? start + next.index : body.length };
|
|
2401
|
+
}
|
|
2402
|
+
function objectivesSectionText(body) {
|
|
2403
|
+
const bounds = objectivesSectionBounds(body);
|
|
2404
|
+
return bounds === null ? null : body.slice(bounds.start, bounds.end);
|
|
2378
2405
|
}
|
|
2379
2406
|
function hasObjectivesHeading(body) {
|
|
2380
|
-
return HEADING_RE.test(body);
|
|
2407
|
+
return HEADING_RE.test(maskedForHeadingSearch(body));
|
|
2381
2408
|
}
|
|
2382
2409
|
function objectivesOf(body) {
|
|
2383
2410
|
const section = objectivesSectionText(body);
|
|
@@ -2437,6 +2464,15 @@ function isIssueNotFoundError(err) {
|
|
|
2437
2464
|
`);
|
|
2438
2465
|
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2439
2466
|
}
|
|
2467
|
+
function resolveObjectivesSource(prBody, issue, cutoverIssue) {
|
|
2468
|
+
if (issue !== null && issue < cutoverIssue)
|
|
2469
|
+
return { kind: "none" };
|
|
2470
|
+
if (issue !== null)
|
|
2471
|
+
return { kind: "issue", issue };
|
|
2472
|
+
if (hasObjectivesHeading(prBody))
|
|
2473
|
+
return { kind: "body" };
|
|
2474
|
+
return { kind: "none" };
|
|
2475
|
+
}
|
|
2440
2476
|
|
|
2441
2477
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2442
2478
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -2938,17 +2974,40 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2938
2974
|
];
|
|
2939
2975
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2940
2976
|
}
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
`
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2977
|
+
function contentAfterNLines(body, n) {
|
|
2978
|
+
let idx = -1;
|
|
2979
|
+
for (let i = 0;i < n; i++) {
|
|
2980
|
+
idx = body.indexOf(`
|
|
2981
|
+
`, idx + 1);
|
|
2982
|
+
if (idx === -1)
|
|
2983
|
+
return "";
|
|
2984
|
+
}
|
|
2985
|
+
return body.slice(idx + 1);
|
|
2986
|
+
}
|
|
2987
|
+
var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
|
|
2988
|
+
function parseBriefMarkerVersion(firstLine) {
|
|
2989
|
+
const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
|
|
2990
|
+
if (!m)
|
|
2991
|
+
return null;
|
|
2992
|
+
const version = Number.parseInt(m[1], 10);
|
|
2993
|
+
return Number.isInteger(version) && version >= 1 ? version : null;
|
|
2994
|
+
}
|
|
2995
|
+
function frozenBriefContent(body, version) {
|
|
2996
|
+
return contentAfterNLines(body, version === 1 ? 2 : 3);
|
|
2997
|
+
}
|
|
2998
|
+
function resolveNewestFrozenBrief(comments, allowlist) {
|
|
2999
|
+
let best = null;
|
|
3000
|
+
for (const c of comments) {
|
|
3001
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3002
|
+
continue;
|
|
3003
|
+
const version = parseBriefMarkerVersion(c.body.split(`
|
|
3004
|
+
`)[0] ?? "");
|
|
3005
|
+
if (version === null)
|
|
3006
|
+
continue;
|
|
3007
|
+
if (best === null || version > best.version)
|
|
3008
|
+
best = { ...c, version };
|
|
3009
|
+
}
|
|
3010
|
+
return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
|
|
2952
3011
|
}
|
|
2953
3012
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2954
3013
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
@@ -3675,6 +3734,11 @@ function isBoundToObjectives(extraction, currentVersion) {
|
|
|
3675
3734
|
return true;
|
|
3676
3735
|
return extraction.objectivesVersion === currentVersion;
|
|
3677
3736
|
}
|
|
3737
|
+
function isBoundToRulings(extraction, currentOrdinal) {
|
|
3738
|
+
if (extraction.rulingOrdinal === null)
|
|
3739
|
+
return currentOrdinal === 0;
|
|
3740
|
+
return extraction.rulingOrdinal === currentOrdinal;
|
|
3741
|
+
}
|
|
3678
3742
|
function checkReviewGate(input) {
|
|
3679
3743
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3680
3744
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3703,7 +3767,9 @@ function checkReviewGate(input) {
|
|
|
3703
3767
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3704
3768
|
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3705
3769
|
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3706
|
-
|
|
3770
|
+
const codeReviewRulingsBound = isBoundToRulings(codeReview, input.rulingOrdinal);
|
|
3771
|
+
const securityRulingsBound = isBoundToRulings(security, input.rulingOrdinal);
|
|
3772
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && codeReviewRulingsBound && securityClean && securityBound && securityObjectivesBound && securityRulingsBound && mechanicalChecksClean) {
|
|
3707
3773
|
return {
|
|
3708
3774
|
verdict: "pass",
|
|
3709
3775
|
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.`,
|
|
@@ -3717,6 +3783,8 @@ function checkReviewGate(input) {
|
|
|
3717
3783
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3718
3784
|
} else if (!codeReviewObjectivesBound) {
|
|
3719
3785
|
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3786
|
+
} else if (!codeReviewRulingsBound) {
|
|
3787
|
+
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`);
|
|
3720
3788
|
}
|
|
3721
3789
|
if (!securityClean) {
|
|
3722
3790
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
@@ -3724,6 +3792,8 @@ function checkReviewGate(input) {
|
|
|
3724
3792
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3725
3793
|
} else if (!securityObjectivesBound) {
|
|
3726
3794
|
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3795
|
+
} else if (!securityRulingsBound) {
|
|
3796
|
+
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`);
|
|
3727
3797
|
}
|
|
3728
3798
|
if (!mechanicalChecksClean) {
|
|
3729
3799
|
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(", ")}`);
|
|
@@ -3740,6 +3810,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
|
|
|
3740
3810
|
function isChangesetsReleasePr(branch, author, expectedAuthor) {
|
|
3741
3811
|
return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
|
|
3742
3812
|
}
|
|
3813
|
+
// ../../packages/aeg-core/src/ruling-ordinal.ts
|
|
3814
|
+
var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
|
|
3815
|
+
function newestPrincipalRulingOrdinal(comments, allowlist) {
|
|
3816
|
+
let best = 0;
|
|
3817
|
+
for (const c of comments) {
|
|
3818
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3819
|
+
continue;
|
|
3820
|
+
const firstLine = (c.body.split(`
|
|
3821
|
+
`)[0] ?? "").trim();
|
|
3822
|
+
const m = RULING_MARKER_ORDINAL.exec(firstLine);
|
|
3823
|
+
if (!m)
|
|
3824
|
+
continue;
|
|
3825
|
+
const k = Number.parseInt(m[1], 10);
|
|
3826
|
+
if (k > best)
|
|
3827
|
+
best = k;
|
|
3828
|
+
}
|
|
3829
|
+
return best;
|
|
3830
|
+
}
|
|
3743
3831
|
// ../../packages/aeg-core/src/markdown-table.ts
|
|
3744
3832
|
function splitRow(line) {
|
|
3745
3833
|
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
@@ -5094,14 +5182,41 @@ var COMMANDS = [
|
|
|
5094
5182
|
},
|
|
5095
5183
|
{
|
|
5096
5184
|
name: "task dispatch",
|
|
5097
|
-
description: "
|
|
5185
|
+
description: "Deprecated — render, pin, and post the brief on the Issue as the frozen original; start the developer",
|
|
5098
5186
|
flags: [
|
|
5099
5187
|
{ flag: "--agent <claude|codex|gemini>", description: "Start the developer through dispatchRole once posted" }
|
|
5100
5188
|
],
|
|
5101
5189
|
details: [
|
|
5102
5190
|
"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.",
|
|
5103
5191
|
"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.",
|
|
5104
|
-
"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."
|
|
5192
|
+
"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.",
|
|
5193
|
+
"Deprecated in favor of `task brief` (preparation only) and `task run` (the full unattended loop) — kept for a documented compatibility window while callers migrate."
|
|
5194
|
+
],
|
|
5195
|
+
status: "shipped"
|
|
5196
|
+
},
|
|
5197
|
+
{
|
|
5198
|
+
name: "task brief",
|
|
5199
|
+
description: "Render and freeze the brief as the Issue's original comment — preparation only, starts nobody",
|
|
5200
|
+
details: [
|
|
5201
|
+
"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.",
|
|
5202
|
+
"Successor to `task dispatch` for the preparation step; the full unattended run (preparation, then the developer, then the review loop) is `task run`."
|
|
5203
|
+
],
|
|
5204
|
+
status: "shipped"
|
|
5205
|
+
},
|
|
5206
|
+
{
|
|
5207
|
+
name: "task run",
|
|
5208
|
+
description: "One command from a planned Issue to a reviewed pull request — exactly one developer started",
|
|
5209
|
+
flags: [
|
|
5210
|
+
{
|
|
5211
|
+
flag: "--agent <claude|codex|gemini>",
|
|
5212
|
+
description: "Vendor for the developer and both reviewers this run dispatches"
|
|
5213
|
+
}
|
|
5214
|
+
],
|
|
5215
|
+
details: [
|
|
5216
|
+
"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.",
|
|
5217
|
+
"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.",
|
|
5218
|
+
"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.",
|
|
5219
|
+
"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."
|
|
5105
5220
|
],
|
|
5106
5221
|
status: "shipped"
|
|
5107
5222
|
},
|
|
@@ -5309,6 +5424,15 @@ var COMMANDS = [
|
|
|
5309
5424
|
],
|
|
5310
5425
|
status: "shipped"
|
|
5311
5426
|
},
|
|
5427
|
+
{
|
|
5428
|
+
name: "milestone status",
|
|
5429
|
+
description: "Print each of a Milestone's declared tranche intents with its derived lifecycle and issue counts",
|
|
5430
|
+
flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
|
|
5431
|
+
details: [
|
|
5432
|
+
"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."
|
|
5433
|
+
],
|
|
5434
|
+
status: "shipped"
|
|
5435
|
+
},
|
|
5312
5436
|
{
|
|
5313
5437
|
name: "review status",
|
|
5314
5438
|
description: "Print the review loop's own state for a PR, and its branch's distance from the base",
|