@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.
Files changed (59) hide show
  1. package/README.md +4 -4
  2. package/aeg-root/contracts/architect-planner.md +5 -5
  3. package/aeg-root/contracts/developer-reviewer.md +7 -7
  4. package/aeg-root/contracts/planner-developer.md +142 -0
  5. package/aeg-root/contracts/reviewer-archivist.md +2 -2
  6. package/aeg-root/contracts/tranche-archivist-planner.md +12 -11
  7. package/aeg-root/enforcement.md +15 -5
  8. package/aeg-root/process.md +53 -75
  9. package/aeg-root/roles/archivist.md +3 -3
  10. package/aeg-root/roles/developer.md +10 -10
  11. package/aeg-root/roles/planner.md +88 -23
  12. package/aeg-root/roles/principal.md +11 -11
  13. package/aeg-root/roles/reviewer.md +10 -10
  14. package/aeg-root/roles/security.md +7 -7
  15. package/aeg-root/roles/tranche-archivist.md +2 -2
  16. package/aeg-root/skills/aeg/SKILL.md +5 -5
  17. package/aeg-root/skills/aeg-roles/SKILL.md +7 -7
  18. package/aeg-root/state-machine.md +35 -34
  19. package/aeg-root/task-model.md +3 -3
  20. package/aeg-root/templates/brief-template.md +2 -2
  21. package/aeg-root/templates/issue-rationale-template.md +3 -3
  22. package/aeg-root/tranche-model.md +21 -21
  23. package/dist/checks/bin/check-body-bare-digits.js +116 -19
  24. package/dist/checks/bin/check-branch-topology.js +116 -19
  25. package/dist/checks/bin/check-brief-shape.js +121 -1101
  26. package/dist/checks/bin/check-changeset-coverage.js +877 -26
  27. package/dist/checks/bin/check-closes-n.js +116 -19
  28. package/dist/checks/bin/check-coherence.js +116 -19
  29. package/dist/checks/bin/check-dead-branch-push.js +116 -19
  30. package/dist/checks/bin/check-dispatch-readiness.js +119 -22
  31. package/dist/checks/bin/check-doc-coverage-push.js +877 -26
  32. package/dist/checks/bin/check-doc-coverage.js +879 -28
  33. package/dist/checks/bin/check-doctrine-no-procedures.js +116 -19
  34. package/dist/checks/bin/check-doctrine-portability.js +877 -26
  35. package/dist/checks/bin/check-evidence-fresh.js +116 -19
  36. package/dist/checks/bin/check-exec-bits.js +877 -26
  37. package/dist/checks/bin/check-first-push-dispatch.js +116 -19
  38. package/dist/checks/bin/check-issue-assignment.js +116 -19
  39. package/dist/checks/bin/check-main-branch-refusal.js +117 -20
  40. package/dist/checks/bin/check-no-disk-state.js +116 -19
  41. package/dist/checks/bin/check-pr-premise-reassert.js +5401 -0
  42. package/dist/checks/bin/check-pr-report-density.js +116 -19
  43. package/dist/checks/bin/check-quoted-command.js +875 -24
  44. package/dist/checks/bin/check-reader-resolvable-prose.js +875 -24
  45. package/dist/checks/bin/check-registry-gates.js +147 -21
  46. package/dist/checks/bin/check-retired-vocabulary.js +875 -24
  47. package/dist/checks/bin/check-review-gate.js +116 -19
  48. package/dist/checks/bin/check-single-plan-pr.js +116 -19
  49. package/dist/checks/bin/check-surface-scope.js +5722 -0
  50. package/dist/checks/bin/check-test-plan.js +116 -19
  51. package/dist/checks/bin/check-token-collection-wired.js +116 -19
  52. package/dist/checks/bin/check-token-report.js +128 -26
  53. package/dist/checks/bin/check-workspace-escape.js +875 -24
  54. package/dist/index.js +1002 -214
  55. package/package.json +1 -1
  56. package/aeg-root/contracts/brief-developer.md +0 -141
  57. package/aeg-root/contracts/planner-brief.md +0 -143
  58. package/aeg-root/roles/brief-author.md +0 -116
  59. package/aeg-root/skills/brief-authoring/SKILL.md +0 -509
@@ -29,6 +29,15 @@ var GATE_AUDIENCE = {
29
29
  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."
30
30
  }
31
31
  };
32
+ var NON_GATE_BINS = [
33
+ "archive-task",
34
+ "assign-task-issue",
35
+ "dead-branch-audit",
36
+ "eval-agent-compliance",
37
+ "open-issue",
38
+ "open-pr",
39
+ "report-tokens"
40
+ ];
32
41
  function isShipped(a) {
33
42
  return "shippedAs" in a;
34
43
  }
@@ -389,7 +398,7 @@ var LABELS = [
389
398
  id: "vinaya/needs:brief-correction",
390
399
  category: "needs",
391
400
  form: "literal",
392
- carries: "Waiting on the Brief Author — the brief contradicts the surface it describes."
401
+ carries: "Waiting on the Planner — the brief contradicts the surface it describes."
393
402
  },
394
403
  {
395
404
  key: "waiver-docs",
@@ -2396,7 +2405,7 @@ function objectivesOf(body) {
2396
2405
  continue;
2397
2406
  }
2398
2407
  if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
2399
- errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Brief Author maps it to files).`);
2408
+ 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).`);
2400
2409
  continue;
2401
2410
  }
2402
2411
  objectives.push({ id: `O${n}`, text });
@@ -2608,18 +2617,14 @@ function checkTestPlanExclusivity(prBody) {
2608
2617
  function checkPrincipalPlaceholder(prBody) {
2609
2618
  const region = testPlanRegion(prBody);
2610
2619
  const lineRe = /^-\s*\[[ xX]\]\s*\*{2}\[principal\]\*{2}(.*)$/gim;
2620
+ const errors = [];
2611
2621
  for (const m of region.matchAll(lineRe)) {
2612
2622
  const content = m[1] ?? "";
2613
2623
  if (/^\s*None\b/i.test(content)) {
2614
- return {
2615
- status: "fail",
2616
- errors: [
2617
- '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.'
2618
- ]
2619
- };
2624
+ 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.`);
2620
2625
  }
2621
2626
  }
2622
- return { status: "pass", errors: [] };
2627
+ return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
2623
2628
  }
2624
2629
  function checkSurfaceMap(prBody) {
2625
2630
  return headingCheck(prBody, "(?:technical\\s+)?surface map", "Technical surface map");
@@ -2935,6 +2940,18 @@ function checkBriefSections(prBody, readTier, options = {}) {
2935
2940
  ];
2936
2941
  return { errors: results.flatMap((r) => r.errors) };
2937
2942
  }
2943
+ var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
2944
+ function contentAfterTwoLines(body) {
2945
+ const first = body.indexOf(`
2946
+ `);
2947
+ if (first === -1)
2948
+ return "";
2949
+ const second = body.indexOf(`
2950
+ `, first + 1);
2951
+ if (second === -1)
2952
+ return "";
2953
+ return body.slice(second + 1);
2954
+ }
2938
2955
  // ../../packages/aeg-core/src/doctrine-portability.ts
2939
2956
  var DEFAULT_SHIPS_PREFIX = "aeg-root/";
2940
2957
  var STATIC_PORTABLE_PREFIXES = [
@@ -3177,6 +3194,66 @@ function checkIssueObjectives(body, issueNumber) {
3177
3194
  return { status: "pass", errors: [] };
3178
3195
  return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
3179
3196
  }
3197
+ function topLevelSectionText(body, headingName) {
3198
+ const headingRe = new RegExp(`^##[ \\t]*${headingName}[ \\t]*$`, "im");
3199
+ const heading = headingRe.exec(body);
3200
+ if (!heading)
3201
+ return null;
3202
+ const afterHeading = body.slice(heading.index + heading[0].length);
3203
+ const next = /^##[ \t]/m.exec(afterHeading);
3204
+ return next ? afterHeading.slice(0, next.index) : afterHeading;
3205
+ }
3206
+ function looksLikeFilePath(entry2) {
3207
+ const stripped = entry2.replace(/\/\*\*?$/, "");
3208
+ const lastSegment = stripped.split("/").pop() ?? stripped;
3209
+ const extMatch = /\.[A-Za-z0-9]{1,6}$/.exec(lastSegment);
3210
+ return extMatch !== null && extMatch.index > 0;
3211
+ }
3212
+ function splitGlobList(raw) {
3213
+ return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
3214
+ }
3215
+ function parseIssueSurface(body) {
3216
+ const section = topLevelSectionText(body, "Surface");
3217
+ if (section === null)
3218
+ return { ok: false, errors: ["no `## Surface` heading found in the body."] };
3219
+ const inLine = /^in:\s*(.+)$/im.exec(section);
3220
+ const outLine = /^out:\s*(.+)$/im.exec(section);
3221
+ const errors = [];
3222
+ if (!inLine)
3223
+ errors.push("`## Surface` has no `in:` line.");
3224
+ if (!outLine)
3225
+ errors.push("`## Surface` has no `out:` line.");
3226
+ if (errors.length > 0)
3227
+ return { ok: false, errors };
3228
+ const inGlobs = splitGlobList(inLine[1]);
3229
+ const outGlobs = splitGlobList(outLine[1]);
3230
+ for (const g of [...inGlobs, ...outGlobs]) {
3231
+ if (looksLikeFilePath(g)) {
3232
+ errors.push(`\`${g}\` in \`## Surface\` looks like a file path — Surface entries are directory-level globs, never file paths.`);
3233
+ }
3234
+ }
3235
+ if (inGlobs.length === 0)
3236
+ errors.push("`## Surface`'s `in:` line resolved to zero globs.");
3237
+ if (errors.length > 0)
3238
+ return { ok: false, errors };
3239
+ return { ok: true, value: { in: inGlobs, out: outGlobs } };
3240
+ }
3241
+ function globCoversPath(glob, path) {
3242
+ const g = glob.replace(/\/\*\*?$/, "").replace(/\/+$/, "");
3243
+ const p = path.replace(/\/+$/, "");
3244
+ return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
3245
+ }
3246
+ function checkSurfaceScope(changedFiles, outGlobs) {
3247
+ if (outGlobs.length === 0)
3248
+ return { ok: true };
3249
+ const violations = [];
3250
+ for (const file of changedFiles) {
3251
+ const glob = outGlobs.find((g) => globCoversPath(g, file));
3252
+ if (glob)
3253
+ violations.push({ file, glob });
3254
+ }
3255
+ return violations.length > 0 ? { ok: false, violations } : { ok: true };
3256
+ }
3180
3257
  function isTaskIssueLabelSet(labels) {
3181
3258
  return hasLabel("tranche", labels);
3182
3259
  }
@@ -3241,6 +3318,8 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
3241
3318
  }
3242
3319
  return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
3243
3320
  }
3321
+ 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;
3322
+ var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
3244
3323
 
3245
3324
  // ../../packages/aeg-core/src/coherence-checks.ts
3246
3325
  var COHERENCE_ENFORCED_FROM = "2026-07-01";
@@ -3770,16 +3849,33 @@ function checkG1(rows, existsFn) {
3770
3849
  return { check: "G1", status: findings.length > 0 ? "fail" : "pass", findings };
3771
3850
  }
3772
3851
  var SCAFFOLD_PLACEHOLDER = "[undocumented — fill in why]";
3852
+ var NON_GATE_BIN_NAMES = NON_GATE_BINS;
3853
+ var TWIN_CANDIDATE_EXCEPTIONS = {
3854
+ "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)',
3855
+ "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)`,
3856
+ "apps/cli/src/checks/bin/check-closes-n.ts": 'Ring 1 "Closes linkage" (implementation: verify-coherence.ts)'
3857
+ };
3858
+ var NON_GATE_HOOK_SCRIPTS = [".claude/hooks/track-transcript.sh"];
3773
3859
  function checkG2(rows, candidateFiles) {
3774
3860
  const implementations = new Set(rows.map((r) => r.implementation).filter((p) => p !== ""));
3861
+ const claimedNames = new Set(rows.flatMap((r) => claimedCheckNames(r.implementation)));
3775
3862
  const findings = [];
3776
3863
  for (const path of candidateFiles) {
3777
- if (!implementations.has(path)) {
3778
- findings.push({
3779
- path,
3780
- reason: `"${path}" is not named as the implementation of any row in enforcement.md's ring tables`
3781
- });
3782
- }
3864
+ if (implementations.has(path))
3865
+ continue;
3866
+ if (path.startsWith(AEG_CORE_BIN_PATH_PREFIX) && NON_GATE_BIN_NAMES.includes(basenameNoExt(path)))
3867
+ continue;
3868
+ if (NON_GATE_HOOK_SCRIPTS.includes(path))
3869
+ continue;
3870
+ if (path in TWIN_CANDIDATE_EXCEPTIONS)
3871
+ continue;
3872
+ const candidateNames = claimedCheckNames(path);
3873
+ if (candidateNames.length > 0 && candidateNames.some((n) => claimedNames.has(n)))
3874
+ continue;
3875
+ findings.push({
3876
+ path,
3877
+ reason: `"${path}" is not named as the implementation of any row in enforcement.md's ring tables`
3878
+ });
3783
3879
  }
3784
3880
  for (const row of rows) {
3785
3881
  const carriesPlaceholder = [row.summary, row.description, row.spec].some((cell) => cell === SCAFFOLD_PLACEHOLDER);
@@ -3791,7 +3887,7 @@ function checkG2(rows, candidateFiles) {
3791
3887
  });
3792
3888
  }
3793
3889
  }
3794
- return { check: "G2", status: findings.length > 0 ? "info" : "pass", findings };
3890
+ return { check: "G2", status: findings.length > 0 ? "fail" : "pass", findings };
3795
3891
  }
3796
3892
  function checkG3(ring0Rows, crossingFiles) {
3797
3893
  const ring0Implementations = new Set(ring0Rows.map((r) => r.implementation));
@@ -4667,7 +4763,6 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4667
4763
  import { z } from "zod";
4668
4764
  var ROLE_VALUES = [
4669
4765
  "planner",
4670
- "brief-author",
4671
4766
  "developer",
4672
4767
  "code-reviewer",
4673
4768
  "security",
@@ -4741,18 +4836,20 @@ var dispatchShared = {
4741
4836
  round: z.number().int().optional(),
4742
4837
  effect_id: z.string()
4743
4838
  };
4839
+ var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
4744
4840
  var DispatchEventSchema = z.discriminatedUnion("event", [
4745
4841
  z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4746
4842
  z.object({
4747
4843
  ...dispatchShared,
4748
4844
  event: z.literal("outcome_received"),
4749
4845
  outcome: DispatchOutcomeSchema,
4750
- usage: z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable()
4846
+ usage: dispatchUsageField
4751
4847
  }).strict(),
4752
4848
  z.object({
4753
4849
  ...dispatchShared,
4754
4850
  event: z.literal("dispatch_failed"),
4755
- reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
4851
+ reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
4852
+ usage: dispatchUsageField
4756
4853
  }).strict()
4757
4854
  ]);
4758
4855
  var loopShared = {
@@ -4977,6 +5074,18 @@ var REGISTRY = [
4977
5074
  },
4978
5075
  0
4979
5076
  ],
5077
+ [
5078
+ {
5079
+ name: "pr-premise-reassert",
5080
+ run: bin("check-pr-premise-reassert"),
5081
+ scope: "diff",
5082
+ timeoutMs: 15000,
5083
+ env: {
5084
+ PR_BODY: { optional: true }
5085
+ }
5086
+ },
5087
+ 0
5088
+ ],
4980
5089
  [
4981
5090
  {
4982
5091
  name: "dispatch-readiness",
@@ -5025,6 +5134,22 @@ var REGISTRY = [
5025
5134
  },
5026
5135
  0
5027
5136
  ],
5137
+ [
5138
+ {
5139
+ name: "surface-scope",
5140
+ run: bin("check-surface-scope"),
5141
+ scope: "diff",
5142
+ timeoutMs: 15000,
5143
+ env: {
5144
+ AEG_REPO: { optional: true },
5145
+ BASE_SHA: { optional: true },
5146
+ BRANCH: { optional: true },
5147
+ GITHUB_TOKEN: { optional: true },
5148
+ GH_TOKEN: { optional: true }
5149
+ }
5150
+ },
5151
+ 0
5152
+ ],
5028
5153
  [
5029
5154
  {
5030
5155
  name: "test-plan",
@@ -5066,6 +5191,7 @@ var REGISTRY = [
5066
5191
  requiresOpenPr: true,
5067
5192
  env: {
5068
5193
  PR_BODY: { optional: true },
5194
+ BRANCH: { optional: true },
5069
5195
  CLAUDE_PROJECT_DIR: { optional: true },
5070
5196
  CLAUDE_CODE_SESSION_ID: { optional: true }
5071
5197
  }
@@ -5488,13 +5614,13 @@ async function main() {
5488
5614
  const g3 = checkG3(ring0Rows, crossingFiles);
5489
5615
  const g5 = checkG5(roles, contracts);
5490
5616
  const g6 = checkG6(rows, new Set(coreCheckRegistry().map((s) => s.name)));
5491
- emitResult(g2, false);
5492
5617
  emitResult(g1, true);
5618
+ emitResult(g2, true);
5493
5619
  emitResult(g3, true);
5494
5620
  emitResult(g4Result, true);
5495
5621
  emitResult(g5, true);
5496
5622
  emitResult(g6, true);
5497
- const blockingFailed = [g1, g3, g4Result, g5, g6].some((r) => r.status === "fail");
5623
+ const blockingFailed = [g1, g2, g3, g4Result, g5, g6].some((r) => r.status === "fail");
5498
5624
  process.exit(blockingFailed ? 1 : 0);
5499
5625
  }
5500
5626
  main();