@attalabs/vinaya 0.25.0 → 0.26.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 -4
- package/aeg-root/contracts/architect-planner.md +5 -5
- package/aeg-root/contracts/developer-reviewer.md +7 -7
- package/aeg-root/contracts/planner-developer.md +142 -0
- package/aeg-root/contracts/reviewer-archivist.md +2 -2
- package/aeg-root/contracts/tranche-archivist-planner.md +12 -11
- package/aeg-root/enforcement.md +15 -5
- package/aeg-root/process.md +53 -75
- package/aeg-root/roles/archivist.md +3 -3
- package/aeg-root/roles/developer.md +10 -10
- package/aeg-root/roles/planner.md +88 -23
- package/aeg-root/roles/principal.md +11 -11
- package/aeg-root/roles/reviewer.md +10 -10
- package/aeg-root/roles/security.md +7 -7
- package/aeg-root/roles/tranche-archivist.md +2 -2
- package/aeg-root/skills/aeg/SKILL.md +5 -5
- package/aeg-root/skills/aeg-roles/SKILL.md +7 -7
- package/aeg-root/state-machine.md +35 -34
- package/aeg-root/task-model.md +3 -3
- package/aeg-root/templates/brief-template.md +2 -2
- package/aeg-root/templates/issue-rationale-template.md +3 -3
- package/aeg-root/tranche-model.md +21 -21
- package/dist/checks/bin/check-body-bare-digits.js +116 -19
- package/dist/checks/bin/check-branch-topology.js +116 -19
- package/dist/checks/bin/check-brief-shape.js +121 -1101
- package/dist/checks/bin/check-changeset-coverage.js +877 -26
- package/dist/checks/bin/check-closes-n.js +116 -19
- package/dist/checks/bin/check-coherence.js +116 -19
- package/dist/checks/bin/check-dead-branch-push.js +116 -19
- package/dist/checks/bin/check-dispatch-readiness.js +119 -22
- package/dist/checks/bin/check-doc-coverage-push.js +877 -26
- package/dist/checks/bin/check-doc-coverage.js +879 -28
- package/dist/checks/bin/check-doctrine-no-procedures.js +116 -19
- package/dist/checks/bin/check-doctrine-portability.js +877 -26
- package/dist/checks/bin/check-evidence-fresh.js +116 -19
- package/dist/checks/bin/check-exec-bits.js +877 -26
- package/dist/checks/bin/check-first-push-dispatch.js +116 -19
- package/dist/checks/bin/check-issue-assignment.js +116 -19
- package/dist/checks/bin/check-main-branch-refusal.js +117 -20
- package/dist/checks/bin/check-no-disk-state.js +116 -19
- package/dist/checks/bin/check-pr-premise-reassert.js +5401 -0
- package/dist/checks/bin/check-pr-report-density.js +116 -19
- package/dist/checks/bin/check-quoted-command.js +875 -24
- package/dist/checks/bin/check-reader-resolvable-prose.js +875 -24
- package/dist/checks/bin/check-registry-gates.js +147 -21
- package/dist/checks/bin/check-retired-vocabulary.js +875 -24
- package/dist/checks/bin/check-review-gate.js +116 -19
- package/dist/checks/bin/check-single-plan-pr.js +116 -19
- package/dist/checks/bin/check-surface-scope.js +5722 -0
- package/dist/checks/bin/check-test-plan.js +116 -19
- package/dist/checks/bin/check-token-collection-wired.js +116 -19
- package/dist/checks/bin/check-token-report.js +128 -26
- package/dist/checks/bin/check-workspace-escape.js +875 -24
- package/dist/index.js +1002 -214
- package/package.json +1 -1
- package/aeg-root/contracts/brief-developer.md +0 -141
- package/aeg-root/contracts/planner-brief.md +0 -143
- package/aeg-root/roles/brief-author.md +0 -116
- package/aeg-root/skills/brief-authoring/SKILL.md +0 -509
|
@@ -27,6 +27,15 @@ var GATE_AUDIENCE = {
|
|
|
27
27
|
internal: "A composite that shells this repo's own `typecheck`/`lint`/`test`/`build` scripts by name. Those names are this monorepo's toolchain (bun + turbo + biome), not an adopter's; the shipped equivalent of its intent is `vinaya check --all`, which is portable by construction."
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
+
var NON_GATE_BINS = [
|
|
31
|
+
"archive-task",
|
|
32
|
+
"assign-task-issue",
|
|
33
|
+
"dead-branch-audit",
|
|
34
|
+
"eval-agent-compliance",
|
|
35
|
+
"open-issue",
|
|
36
|
+
"open-pr",
|
|
37
|
+
"report-tokens"
|
|
38
|
+
];
|
|
30
39
|
function isShipped(a) {
|
|
31
40
|
return "shippedAs" in a;
|
|
32
41
|
}
|
|
@@ -387,7 +396,7 @@ var LABELS = [
|
|
|
387
396
|
id: "vinaya/needs:brief-correction",
|
|
388
397
|
category: "needs",
|
|
389
398
|
form: "literal",
|
|
390
|
-
carries: "Waiting on the
|
|
399
|
+
carries: "Waiting on the Planner — the brief contradicts the surface it describes."
|
|
391
400
|
},
|
|
392
401
|
{
|
|
393
402
|
key: "waiver-docs",
|
|
@@ -2394,7 +2403,7 @@ function objectivesOf(body) {
|
|
|
2394
2403
|
continue;
|
|
2395
2404
|
}
|
|
2396
2405
|
if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
2397
|
-
errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the
|
|
2406
|
+
errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Planner maps it to files).`);
|
|
2398
2407
|
continue;
|
|
2399
2408
|
}
|
|
2400
2409
|
objectives.push({ id: `O${n}`, text });
|
|
@@ -2606,18 +2615,14 @@ function checkTestPlanExclusivity(prBody) {
|
|
|
2606
2615
|
function checkPrincipalPlaceholder(prBody) {
|
|
2607
2616
|
const region = testPlanRegion(prBody);
|
|
2608
2617
|
const lineRe = /^-\s*\[[ xX]\]\s*\*{2}\[principal\]\*{2}(.*)$/gim;
|
|
2618
|
+
const errors = [];
|
|
2609
2619
|
for (const m of region.matchAll(lineRe)) {
|
|
2610
2620
|
const content = m[1] ?? "";
|
|
2611
2621
|
if (/^\s*None\b/i.test(content)) {
|
|
2612
|
-
|
|
2613
|
-
status: "fail",
|
|
2614
|
-
errors: [
|
|
2615
|
-
'brief-validation Test Plan shape: a `**[principal]**` checkbox item is a "None" placeholder — if there is no principal-runnable surface, omit the item entirely; an untickable placeholder box blocks the merge gate forever.'
|
|
2616
|
-
]
|
|
2617
|
-
};
|
|
2622
|
+
errors.push(`brief-validation Test Plan shape: a \`**[principal]**\` checkbox item is a "None" placeholder ("${content.trim()}") — if there is no principal-runnable surface, omit the item entirely; an untickable placeholder box blocks the merge gate forever.`);
|
|
2618
2623
|
}
|
|
2619
2624
|
}
|
|
2620
|
-
return { status: "pass", errors: [] };
|
|
2625
|
+
return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
|
|
2621
2626
|
}
|
|
2622
2627
|
function checkSurfaceMap(prBody) {
|
|
2623
2628
|
return headingCheck(prBody, "(?:technical\\s+)?surface map", "Technical surface map");
|
|
@@ -2933,6 +2938,18 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2933
2938
|
];
|
|
2934
2939
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2935
2940
|
}
|
|
2941
|
+
var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
|
|
2942
|
+
function contentAfterTwoLines(body) {
|
|
2943
|
+
const first = body.indexOf(`
|
|
2944
|
+
`);
|
|
2945
|
+
if (first === -1)
|
|
2946
|
+
return "";
|
|
2947
|
+
const second = body.indexOf(`
|
|
2948
|
+
`, first + 1);
|
|
2949
|
+
if (second === -1)
|
|
2950
|
+
return "";
|
|
2951
|
+
return body.slice(second + 1);
|
|
2952
|
+
}
|
|
2936
2953
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2937
2954
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
2938
2955
|
var STATIC_PORTABLE_PREFIXES = [
|
|
@@ -3175,6 +3192,66 @@ function checkIssueObjectives(body, issueNumber) {
|
|
|
3175
3192
|
return { status: "pass", errors: [] };
|
|
3176
3193
|
return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
|
|
3177
3194
|
}
|
|
3195
|
+
function topLevelSectionText(body, headingName) {
|
|
3196
|
+
const headingRe = new RegExp(`^##[ \\t]*${headingName}[ \\t]*$`, "im");
|
|
3197
|
+
const heading = headingRe.exec(body);
|
|
3198
|
+
if (!heading)
|
|
3199
|
+
return null;
|
|
3200
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
3201
|
+
const next = /^##[ \t]/m.exec(afterHeading);
|
|
3202
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
3203
|
+
}
|
|
3204
|
+
function looksLikeFilePath(entry2) {
|
|
3205
|
+
const stripped = entry2.replace(/\/\*\*?$/, "");
|
|
3206
|
+
const lastSegment = stripped.split("/").pop() ?? stripped;
|
|
3207
|
+
const extMatch = /\.[A-Za-z0-9]{1,6}$/.exec(lastSegment);
|
|
3208
|
+
return extMatch !== null && extMatch.index > 0;
|
|
3209
|
+
}
|
|
3210
|
+
function splitGlobList(raw) {
|
|
3211
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3212
|
+
}
|
|
3213
|
+
function parseIssueSurface(body) {
|
|
3214
|
+
const section = topLevelSectionText(body, "Surface");
|
|
3215
|
+
if (section === null)
|
|
3216
|
+
return { ok: false, errors: ["no `## Surface` heading found in the body."] };
|
|
3217
|
+
const inLine = /^in:\s*(.+)$/im.exec(section);
|
|
3218
|
+
const outLine = /^out:\s*(.+)$/im.exec(section);
|
|
3219
|
+
const errors = [];
|
|
3220
|
+
if (!inLine)
|
|
3221
|
+
errors.push("`## Surface` has no `in:` line.");
|
|
3222
|
+
if (!outLine)
|
|
3223
|
+
errors.push("`## Surface` has no `out:` line.");
|
|
3224
|
+
if (errors.length > 0)
|
|
3225
|
+
return { ok: false, errors };
|
|
3226
|
+
const inGlobs = splitGlobList(inLine[1]);
|
|
3227
|
+
const outGlobs = splitGlobList(outLine[1]);
|
|
3228
|
+
for (const g of [...inGlobs, ...outGlobs]) {
|
|
3229
|
+
if (looksLikeFilePath(g)) {
|
|
3230
|
+
errors.push(`\`${g}\` in \`## Surface\` looks like a file path — Surface entries are directory-level globs, never file paths.`);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
if (inGlobs.length === 0)
|
|
3234
|
+
errors.push("`## Surface`'s `in:` line resolved to zero globs.");
|
|
3235
|
+
if (errors.length > 0)
|
|
3236
|
+
return { ok: false, errors };
|
|
3237
|
+
return { ok: true, value: { in: inGlobs, out: outGlobs } };
|
|
3238
|
+
}
|
|
3239
|
+
function globCoversPath(glob, path) {
|
|
3240
|
+
const g = glob.replace(/\/\*\*?$/, "").replace(/\/+$/, "");
|
|
3241
|
+
const p = path.replace(/\/+$/, "");
|
|
3242
|
+
return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
|
|
3243
|
+
}
|
|
3244
|
+
function checkSurfaceScope(changedFiles, outGlobs) {
|
|
3245
|
+
if (outGlobs.length === 0)
|
|
3246
|
+
return { ok: true };
|
|
3247
|
+
const violations = [];
|
|
3248
|
+
for (const file of changedFiles) {
|
|
3249
|
+
const glob = outGlobs.find((g) => globCoversPath(g, file));
|
|
3250
|
+
if (glob)
|
|
3251
|
+
violations.push({ file, glob });
|
|
3252
|
+
}
|
|
3253
|
+
return violations.length > 0 ? { ok: false, violations } : { ok: true };
|
|
3254
|
+
}
|
|
3178
3255
|
function isTaskIssueLabelSet(labels) {
|
|
3179
3256
|
return hasLabel("tranche", labels);
|
|
3180
3257
|
}
|
|
@@ -3239,6 +3316,8 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
|
|
|
3239
3316
|
}
|
|
3240
3317
|
return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
|
|
3241
3318
|
}
|
|
3319
|
+
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;
|
|
3320
|
+
var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
|
|
3242
3321
|
|
|
3243
3322
|
// ../../packages/aeg-core/src/coherence-checks.ts
|
|
3244
3323
|
var COHERENCE_ENFORCED_FROM = "2026-07-01";
|
|
@@ -3768,16 +3847,33 @@ function checkG1(rows, existsFn) {
|
|
|
3768
3847
|
return { check: "G1", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3769
3848
|
}
|
|
3770
3849
|
var SCAFFOLD_PLACEHOLDER = "[undocumented — fill in why]";
|
|
3850
|
+
var NON_GATE_BIN_NAMES = NON_GATE_BINS;
|
|
3851
|
+
var TWIN_CANDIDATE_EXCEPTIONS = {
|
|
3852
|
+
"apps/cli/src/checks/bin/check-dead-branch-push.ts": 'Ring 0 "Pushing to a branch whose pull request already resolved" (implementation: check-push-target.ts)',
|
|
3853
|
+
"apps/cli/src/checks/bin/check-issue-assignment.ts": `Ring 0 "A task branch's first push (Issue self-assignment)" (implementation: assign-task-issue.ts)`,
|
|
3854
|
+
"apps/cli/src/checks/bin/check-closes-n.ts": 'Ring 1 "Closes linkage" (implementation: verify-coherence.ts)'
|
|
3855
|
+
};
|
|
3856
|
+
var NON_GATE_HOOK_SCRIPTS = [".claude/hooks/track-transcript.sh"];
|
|
3771
3857
|
function checkG2(rows, candidateFiles) {
|
|
3772
3858
|
const implementations = new Set(rows.map((r) => r.implementation).filter((p) => p !== ""));
|
|
3859
|
+
const claimedNames = new Set(rows.flatMap((r) => claimedCheckNames(r.implementation)));
|
|
3773
3860
|
const findings = [];
|
|
3774
3861
|
for (const path of candidateFiles) {
|
|
3775
|
-
if (
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3862
|
+
if (implementations.has(path))
|
|
3863
|
+
continue;
|
|
3864
|
+
if (path.startsWith(AEG_CORE_BIN_PATH_PREFIX) && NON_GATE_BIN_NAMES.includes(basenameNoExt(path)))
|
|
3865
|
+
continue;
|
|
3866
|
+
if (NON_GATE_HOOK_SCRIPTS.includes(path))
|
|
3867
|
+
continue;
|
|
3868
|
+
if (path in TWIN_CANDIDATE_EXCEPTIONS)
|
|
3869
|
+
continue;
|
|
3870
|
+
const candidateNames = claimedCheckNames(path);
|
|
3871
|
+
if (candidateNames.length > 0 && candidateNames.some((n) => claimedNames.has(n)))
|
|
3872
|
+
continue;
|
|
3873
|
+
findings.push({
|
|
3874
|
+
path,
|
|
3875
|
+
reason: `"${path}" is not named as the implementation of any row in enforcement.md's ring tables`
|
|
3876
|
+
});
|
|
3781
3877
|
}
|
|
3782
3878
|
for (const row of rows) {
|
|
3783
3879
|
const carriesPlaceholder = [row.summary, row.description, row.spec].some((cell) => cell === SCAFFOLD_PLACEHOLDER);
|
|
@@ -3789,7 +3885,7 @@ function checkG2(rows, candidateFiles) {
|
|
|
3789
3885
|
});
|
|
3790
3886
|
}
|
|
3791
3887
|
}
|
|
3792
|
-
return { check: "G2", status: findings.length > 0 ? "
|
|
3888
|
+
return { check: "G2", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3793
3889
|
}
|
|
3794
3890
|
function checkG3(ring0Rows, crossingFiles) {
|
|
3795
3891
|
const ring0Implementations = new Set(ring0Rows.map((r) => r.implementation));
|
|
@@ -4665,7 +4761,6 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4665
4761
|
import { z } from "zod";
|
|
4666
4762
|
var ROLE_VALUES = [
|
|
4667
4763
|
"planner",
|
|
4668
|
-
"brief-author",
|
|
4669
4764
|
"developer",
|
|
4670
4765
|
"code-reviewer",
|
|
4671
4766
|
"security",
|
|
@@ -4739,18 +4834,20 @@ var dispatchShared = {
|
|
|
4739
4834
|
round: z.number().int().optional(),
|
|
4740
4835
|
effect_id: z.string()
|
|
4741
4836
|
};
|
|
4837
|
+
var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
|
|
4742
4838
|
var DispatchEventSchema = z.discriminatedUnion("event", [
|
|
4743
4839
|
z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
|
|
4744
4840
|
z.object({
|
|
4745
4841
|
...dispatchShared,
|
|
4746
4842
|
event: z.literal("outcome_received"),
|
|
4747
4843
|
outcome: DispatchOutcomeSchema,
|
|
4748
|
-
usage:
|
|
4844
|
+
usage: dispatchUsageField
|
|
4749
4845
|
}).strict(),
|
|
4750
4846
|
z.object({
|
|
4751
4847
|
...dispatchShared,
|
|
4752
4848
|
event: z.literal("dispatch_failed"),
|
|
4753
|
-
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
|
|
4849
|
+
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
|
|
4850
|
+
usage: dispatchUsageField
|
|
4754
4851
|
}).strict()
|
|
4755
4852
|
]);
|
|
4756
4853
|
var loopShared = {
|
|
@@ -30,6 +30,15 @@ var GATE_AUDIENCE = {
|
|
|
30
30
|
internal: "A composite that shells this repo's own `typecheck`/`lint`/`test`/`build` scripts by name. Those names are this monorepo's toolchain (bun + turbo + biome), not an adopter's; the shipped equivalent of its intent is `vinaya check --all`, which is portable by construction."
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
+
var NON_GATE_BINS = [
|
|
34
|
+
"archive-task",
|
|
35
|
+
"assign-task-issue",
|
|
36
|
+
"dead-branch-audit",
|
|
37
|
+
"eval-agent-compliance",
|
|
38
|
+
"open-issue",
|
|
39
|
+
"open-pr",
|
|
40
|
+
"report-tokens"
|
|
41
|
+
];
|
|
33
42
|
function isShipped(a) {
|
|
34
43
|
return "shippedAs" in a;
|
|
35
44
|
}
|
|
@@ -390,7 +399,7 @@ var LABELS = [
|
|
|
390
399
|
id: "vinaya/needs:brief-correction",
|
|
391
400
|
category: "needs",
|
|
392
401
|
form: "literal",
|
|
393
|
-
carries: "Waiting on the
|
|
402
|
+
carries: "Waiting on the Planner — the brief contradicts the surface it describes."
|
|
394
403
|
},
|
|
395
404
|
{
|
|
396
405
|
key: "waiver-docs",
|
|
@@ -2397,7 +2406,7 @@ function objectivesOf(body) {
|
|
|
2397
2406
|
continue;
|
|
2398
2407
|
}
|
|
2399
2408
|
if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
2400
|
-
errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the
|
|
2409
|
+
errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Planner maps it to files).`);
|
|
2401
2410
|
continue;
|
|
2402
2411
|
}
|
|
2403
2412
|
objectives.push({ id: `O${n}`, text });
|
|
@@ -2609,18 +2618,14 @@ function checkTestPlanExclusivity(prBody) {
|
|
|
2609
2618
|
function checkPrincipalPlaceholder(prBody) {
|
|
2610
2619
|
const region = testPlanRegion(prBody);
|
|
2611
2620
|
const lineRe = /^-\s*\[[ xX]\]\s*\*{2}\[principal\]\*{2}(.*)$/gim;
|
|
2621
|
+
const errors = [];
|
|
2612
2622
|
for (const m of region.matchAll(lineRe)) {
|
|
2613
2623
|
const content = m[1] ?? "";
|
|
2614
2624
|
if (/^\s*None\b/i.test(content)) {
|
|
2615
|
-
|
|
2616
|
-
status: "fail",
|
|
2617
|
-
errors: [
|
|
2618
|
-
'brief-validation Test Plan shape: a `**[principal]**` checkbox item is a "None" placeholder — if there is no principal-runnable surface, omit the item entirely; an untickable placeholder box blocks the merge gate forever.'
|
|
2619
|
-
]
|
|
2620
|
-
};
|
|
2625
|
+
errors.push(`brief-validation Test Plan shape: a \`**[principal]**\` checkbox item is a "None" placeholder ("${content.trim()}") — if there is no principal-runnable surface, omit the item entirely; an untickable placeholder box blocks the merge gate forever.`);
|
|
2621
2626
|
}
|
|
2622
2627
|
}
|
|
2623
|
-
return { status: "pass", errors: [] };
|
|
2628
|
+
return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
|
|
2624
2629
|
}
|
|
2625
2630
|
function checkSurfaceMap(prBody) {
|
|
2626
2631
|
return headingCheck(prBody, "(?:technical\\s+)?surface map", "Technical surface map");
|
|
@@ -2936,6 +2941,18 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2936
2941
|
];
|
|
2937
2942
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2938
2943
|
}
|
|
2944
|
+
var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
|
|
2945
|
+
function contentAfterTwoLines(body) {
|
|
2946
|
+
const first = body.indexOf(`
|
|
2947
|
+
`);
|
|
2948
|
+
if (first === -1)
|
|
2949
|
+
return "";
|
|
2950
|
+
const second = body.indexOf(`
|
|
2951
|
+
`, first + 1);
|
|
2952
|
+
if (second === -1)
|
|
2953
|
+
return "";
|
|
2954
|
+
return body.slice(second + 1);
|
|
2955
|
+
}
|
|
2939
2956
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2940
2957
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
2941
2958
|
var STATIC_PORTABLE_PREFIXES = [
|
|
@@ -3178,6 +3195,66 @@ function checkIssueObjectives(body, issueNumber) {
|
|
|
3178
3195
|
return { status: "pass", errors: [] };
|
|
3179
3196
|
return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
|
|
3180
3197
|
}
|
|
3198
|
+
function topLevelSectionText(body, headingName) {
|
|
3199
|
+
const headingRe = new RegExp(`^##[ \\t]*${headingName}[ \\t]*$`, "im");
|
|
3200
|
+
const heading = headingRe.exec(body);
|
|
3201
|
+
if (!heading)
|
|
3202
|
+
return null;
|
|
3203
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
3204
|
+
const next = /^##[ \t]/m.exec(afterHeading);
|
|
3205
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
3206
|
+
}
|
|
3207
|
+
function looksLikeFilePath(entry2) {
|
|
3208
|
+
const stripped = entry2.replace(/\/\*\*?$/, "");
|
|
3209
|
+
const lastSegment = stripped.split("/").pop() ?? stripped;
|
|
3210
|
+
const extMatch = /\.[A-Za-z0-9]{1,6}$/.exec(lastSegment);
|
|
3211
|
+
return extMatch !== null && extMatch.index > 0;
|
|
3212
|
+
}
|
|
3213
|
+
function splitGlobList(raw) {
|
|
3214
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3215
|
+
}
|
|
3216
|
+
function parseIssueSurface(body) {
|
|
3217
|
+
const section = topLevelSectionText(body, "Surface");
|
|
3218
|
+
if (section === null)
|
|
3219
|
+
return { ok: false, errors: ["no `## Surface` heading found in the body."] };
|
|
3220
|
+
const inLine = /^in:\s*(.+)$/im.exec(section);
|
|
3221
|
+
const outLine = /^out:\s*(.+)$/im.exec(section);
|
|
3222
|
+
const errors = [];
|
|
3223
|
+
if (!inLine)
|
|
3224
|
+
errors.push("`## Surface` has no `in:` line.");
|
|
3225
|
+
if (!outLine)
|
|
3226
|
+
errors.push("`## Surface` has no `out:` line.");
|
|
3227
|
+
if (errors.length > 0)
|
|
3228
|
+
return { ok: false, errors };
|
|
3229
|
+
const inGlobs = splitGlobList(inLine[1]);
|
|
3230
|
+
const outGlobs = splitGlobList(outLine[1]);
|
|
3231
|
+
for (const g of [...inGlobs, ...outGlobs]) {
|
|
3232
|
+
if (looksLikeFilePath(g)) {
|
|
3233
|
+
errors.push(`\`${g}\` in \`## Surface\` looks like a file path — Surface entries are directory-level globs, never file paths.`);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
if (inGlobs.length === 0)
|
|
3237
|
+
errors.push("`## Surface`'s `in:` line resolved to zero globs.");
|
|
3238
|
+
if (errors.length > 0)
|
|
3239
|
+
return { ok: false, errors };
|
|
3240
|
+
return { ok: true, value: { in: inGlobs, out: outGlobs } };
|
|
3241
|
+
}
|
|
3242
|
+
function globCoversPath(glob, path) {
|
|
3243
|
+
const g = glob.replace(/\/\*\*?$/, "").replace(/\/+$/, "");
|
|
3244
|
+
const p = path.replace(/\/+$/, "");
|
|
3245
|
+
return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
|
|
3246
|
+
}
|
|
3247
|
+
function checkSurfaceScope(changedFiles, outGlobs) {
|
|
3248
|
+
if (outGlobs.length === 0)
|
|
3249
|
+
return { ok: true };
|
|
3250
|
+
const violations = [];
|
|
3251
|
+
for (const file of changedFiles) {
|
|
3252
|
+
const glob = outGlobs.find((g) => globCoversPath(g, file));
|
|
3253
|
+
if (glob)
|
|
3254
|
+
violations.push({ file, glob });
|
|
3255
|
+
}
|
|
3256
|
+
return violations.length > 0 ? { ok: false, violations } : { ok: true };
|
|
3257
|
+
}
|
|
3181
3258
|
function isTaskIssueLabelSet(labels) {
|
|
3182
3259
|
return hasLabel("tranche", labels);
|
|
3183
3260
|
}
|
|
@@ -3242,6 +3319,8 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
|
|
|
3242
3319
|
}
|
|
3243
3320
|
return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
|
|
3244
3321
|
}
|
|
3322
|
+
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
|
+
var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
|
|
3245
3324
|
|
|
3246
3325
|
// ../../packages/aeg-core/src/coherence-checks.ts
|
|
3247
3326
|
var COHERENCE_ENFORCED_FROM = "2026-07-01";
|
|
@@ -3771,16 +3850,33 @@ function checkG1(rows, existsFn) {
|
|
|
3771
3850
|
return { check: "G1", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3772
3851
|
}
|
|
3773
3852
|
var SCAFFOLD_PLACEHOLDER = "[undocumented — fill in why]";
|
|
3853
|
+
var NON_GATE_BIN_NAMES = NON_GATE_BINS;
|
|
3854
|
+
var TWIN_CANDIDATE_EXCEPTIONS = {
|
|
3855
|
+
"apps/cli/src/checks/bin/check-dead-branch-push.ts": 'Ring 0 "Pushing to a branch whose pull request already resolved" (implementation: check-push-target.ts)',
|
|
3856
|
+
"apps/cli/src/checks/bin/check-issue-assignment.ts": `Ring 0 "A task branch's first push (Issue self-assignment)" (implementation: assign-task-issue.ts)`,
|
|
3857
|
+
"apps/cli/src/checks/bin/check-closes-n.ts": 'Ring 1 "Closes linkage" (implementation: verify-coherence.ts)'
|
|
3858
|
+
};
|
|
3859
|
+
var NON_GATE_HOOK_SCRIPTS = [".claude/hooks/track-transcript.sh"];
|
|
3774
3860
|
function checkG2(rows, candidateFiles) {
|
|
3775
3861
|
const implementations = new Set(rows.map((r) => r.implementation).filter((p) => p !== ""));
|
|
3862
|
+
const claimedNames = new Set(rows.flatMap((r) => claimedCheckNames(r.implementation)));
|
|
3776
3863
|
const findings = [];
|
|
3777
3864
|
for (const path of candidateFiles) {
|
|
3778
|
-
if (
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3865
|
+
if (implementations.has(path))
|
|
3866
|
+
continue;
|
|
3867
|
+
if (path.startsWith(AEG_CORE_BIN_PATH_PREFIX) && NON_GATE_BIN_NAMES.includes(basenameNoExt(path)))
|
|
3868
|
+
continue;
|
|
3869
|
+
if (NON_GATE_HOOK_SCRIPTS.includes(path))
|
|
3870
|
+
continue;
|
|
3871
|
+
if (path in TWIN_CANDIDATE_EXCEPTIONS)
|
|
3872
|
+
continue;
|
|
3873
|
+
const candidateNames = claimedCheckNames(path);
|
|
3874
|
+
if (candidateNames.length > 0 && candidateNames.some((n) => claimedNames.has(n)))
|
|
3875
|
+
continue;
|
|
3876
|
+
findings.push({
|
|
3877
|
+
path,
|
|
3878
|
+
reason: `"${path}" is not named as the implementation of any row in enforcement.md's ring tables`
|
|
3879
|
+
});
|
|
3784
3880
|
}
|
|
3785
3881
|
for (const row of rows) {
|
|
3786
3882
|
const carriesPlaceholder = [row.summary, row.description, row.spec].some((cell) => cell === SCAFFOLD_PLACEHOLDER);
|
|
@@ -3792,7 +3888,7 @@ function checkG2(rows, candidateFiles) {
|
|
|
3792
3888
|
});
|
|
3793
3889
|
}
|
|
3794
3890
|
}
|
|
3795
|
-
return { check: "G2", status: findings.length > 0 ? "
|
|
3891
|
+
return { check: "G2", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3796
3892
|
}
|
|
3797
3893
|
function checkG3(ring0Rows, crossingFiles) {
|
|
3798
3894
|
const ring0Implementations = new Set(ring0Rows.map((r) => r.implementation));
|
|
@@ -4668,7 +4764,6 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4668
4764
|
import { z } from "zod";
|
|
4669
4765
|
var ROLE_VALUES = [
|
|
4670
4766
|
"planner",
|
|
4671
|
-
"brief-author",
|
|
4672
4767
|
"developer",
|
|
4673
4768
|
"code-reviewer",
|
|
4674
4769
|
"security",
|
|
@@ -4742,18 +4837,20 @@ var dispatchShared = {
|
|
|
4742
4837
|
round: z.number().int().optional(),
|
|
4743
4838
|
effect_id: z.string()
|
|
4744
4839
|
};
|
|
4840
|
+
var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
|
|
4745
4841
|
var DispatchEventSchema = z.discriminatedUnion("event", [
|
|
4746
4842
|
z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
|
|
4747
4843
|
z.object({
|
|
4748
4844
|
...dispatchShared,
|
|
4749
4845
|
event: z.literal("outcome_received"),
|
|
4750
4846
|
outcome: DispatchOutcomeSchema,
|
|
4751
|
-
usage:
|
|
4847
|
+
usage: dispatchUsageField
|
|
4752
4848
|
}).strict(),
|
|
4753
4849
|
z.object({
|
|
4754
4850
|
...dispatchShared,
|
|
4755
4851
|
event: z.literal("dispatch_failed"),
|
|
4756
|
-
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
|
|
4852
|
+
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
|
|
4853
|
+
usage: dispatchUsageField
|
|
4757
4854
|
}).strict()
|
|
4758
4855
|
]);
|
|
4759
4856
|
var loopShared = {
|
|
@@ -5971,7 +6068,7 @@ function reassertPremiseFile(checkName, premiseFilePath, body, fileReader) {
|
|
|
5971
6068
|
check: checkName,
|
|
5972
6069
|
severity: "error",
|
|
5973
6070
|
message: `dispatch-gate premise: PREMISE_FILE "${premiseFilePath}" does not exist or is unreadable.`,
|
|
5974
|
-
agent_recovery_prompt:
|
|
6071
|
+
agent_recovery_prompt: `Confirm the path passed via PREMISE_FILE is correct and readable, then re-run \`vinaya check ${checkName}\`.`
|
|
5975
6072
|
}
|
|
5976
6073
|
]
|
|
5977
6074
|
};
|
|
@@ -5986,7 +6083,7 @@ function reassertPremiseFile(checkName, premiseFilePath, body, fileReader) {
|
|
|
5986
6083
|
check: checkName,
|
|
5987
6084
|
severity: "error",
|
|
5988
6085
|
message: `dispatch-gate premise: PREMISE_FILE "${premiseFilePath}" carries no \`Premise:\` assertions — a premise file with no pins is a mistake, not a pass.`,
|
|
5989
|
-
agent_recovery_prompt:
|
|
6086
|
+
agent_recovery_prompt: `Add a \`Premise:\` block with at least one \`contains\`/\`absent\`/\`sha256\` pin to the brief file, then re-run \`vinaya check ${checkName}\`.`
|
|
5990
6087
|
}
|
|
5991
6088
|
]
|
|
5992
6089
|
};
|
|
@@ -6000,7 +6097,7 @@ function reassertPremiseFile(checkName, premiseFilePath, body, fileReader) {
|
|
|
6000
6097
|
check: checkName,
|
|
6001
6098
|
severity: "error",
|
|
6002
6099
|
message: `dispatch-gate premise: ${failure}`,
|
|
6003
|
-
agent_recovery_prompt:
|
|
6100
|
+
agent_recovery_prompt: `The surface moved since this brief was authored — re-dig the affected pin, correct the brief, and re-run \`vinaya check ${checkName}\` before continuing.`
|
|
6004
6101
|
}))
|
|
6005
6102
|
};
|
|
6006
6103
|
}
|