@attalabs/vinaya 0.23.0 → 0.24.1

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 (119) hide show
  1. package/README.md +1 -0
  2. package/aeg-root/enforcement.md +1 -1
  3. package/aeg-root/roles/developer.md +1 -1
  4. package/dist/checks/bin/check-body-bare-digits.js +7 -1
  5. package/dist/checks/bin/check-branch-topology.js +27 -1
  6. package/dist/checks/bin/check-brief-shape.js +7 -1
  7. package/dist/checks/bin/check-changeset-coverage.js +19 -1
  8. package/dist/checks/bin/check-closes-n.js +27 -1
  9. package/dist/checks/bin/check-coherence.js +27 -1
  10. package/dist/checks/bin/check-dead-branch-push.js +7 -1
  11. package/dist/checks/bin/check-dispatch-readiness.js +27 -1
  12. package/dist/checks/bin/check-doc-coverage-push.js +7 -1
  13. package/dist/checks/bin/check-doc-coverage.js +7 -1
  14. package/dist/checks/bin/check-doctrine-no-procedures.js +7 -1
  15. package/dist/checks/bin/check-doctrine-portability.js +7 -1
  16. package/dist/checks/bin/check-evidence-fresh.js +7 -1
  17. package/dist/checks/bin/check-exec-bits.js +7 -1
  18. package/dist/checks/bin/check-first-push-dispatch.js +27 -1
  19. package/dist/checks/bin/check-issue-assignment.js +27 -1
  20. package/dist/checks/bin/check-main-branch-refusal.js +13 -2
  21. package/dist/checks/bin/check-no-disk-state.js +7 -1
  22. package/dist/checks/bin/check-pr-report-density.js +7 -1
  23. package/dist/checks/bin/check-quoted-command.js +38 -12
  24. package/dist/checks/bin/check-reader-resolvable-prose.js +38 -12
  25. package/dist/checks/bin/check-registry-gates.js +8 -2
  26. package/dist/checks/bin/check-retired-vocabulary.js +38 -12
  27. package/dist/checks/bin/check-review-gate.js +7 -1
  28. package/dist/checks/bin/check-single-plan-pr.js +7 -1
  29. package/dist/checks/bin/check-test-plan.js +7 -1
  30. package/dist/checks/bin/check-token-collection-wired.js +7 -1
  31. package/dist/checks/bin/check-token-report.js +7 -1
  32. package/dist/checks/bin/check-workspace-escape.js +17 -3
  33. package/dist/index.js +405 -98
  34. package/package.json +2 -2
  35. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/anchored-region.ts +9 -4
  36. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/blast-radius-domains.ts +7 -1
  37. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +462 -0
  38. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-validation.ts +405 -4
  39. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/coherence-checks.ts +19 -0
  40. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/consumer-enumeration.ts +75 -0
  41. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dispatch-gate.ts +44 -0
  42. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-no-procedures.ts +94 -0
  43. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-portability.ts +128 -5
  44. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/gate-audience.ts +3 -0
  45. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/index.ts +25 -3
  46. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-token-report.ts +17 -5
  47. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/pr-report-density.ts +116 -0
  48. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/premise-check.ts +35 -0
  49. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/quoted-command.ts +3 -3
  50. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-gate.ts +116 -7
  51. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-status.ts +200 -0
  52. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-gate.ts +25 -12
  53. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/verdict-extraction.ts +70 -11
  54. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  55. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  56. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1mcitst._.js → [root-of-the-server]__1pndh3_._.js} +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +2 -2
  105. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_projects_[name]_tranches_[slug]_05ufo8i._.js +3 -3
  107. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  108. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  109. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  110. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  111. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  112. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0jer_drkl1e4r.js → 0zebsmmk1bvnb.js} +4 -4
  113. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{20x-t0a0v129z.js → 1uogpj5w2n5ju.js} +1 -1
  114. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2ou_81ahqw31e.js → 3m1kgax7j2vgs.js} +1 -1
  115. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  116. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  117. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5xy_GA7G5KRuE3HDumIiw → QY2GhiI47-765D9DHGy1I}/_buildManifest.js +0 -0
  118. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5xy_GA7G5KRuE3HDumIiw → QY2GhiI47-765D9DHGy1I}/_clientMiddlewareManifest.js +0 -0
  119. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5xy_GA7G5KRuE3HDumIiw → QY2GhiI47-765D9DHGy1I}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { dirname as dirname9, join as join29 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7
7
 
8
8
  // src/commands/archive.ts
9
- import { execFileSync as execFileSync5 } from "node:child_process";
9
+ import { execFileSync as execFileSync6 } from "node:child_process";
10
10
  // ../../packages/aeg-forge-state/src/strip-code.ts
11
11
  function maskCode(body) {
12
12
  const fill = (line) => " ".repeat(line.length);
@@ -3309,7 +3309,7 @@ function renderSection6(facts) {
3309
3309
  " Files:",
3310
3310
  ...files.map((f) => ` - ${f.path}`),
3311
3311
  "",
3312
- ` Run \`bunx turbo test --affected\` before committing this Part; touches ${pkg}.`
3312
+ ` Touches ${pkg}. The pre-push hook runs the affected suite on your one push and refuses it on failure — do not run it yourself per Part.`
3313
3313
  ].join(`
3314
3314
  `));
3315
3315
  n++;
@@ -3335,7 +3335,7 @@ function renderSection8() {
3335
3335
  return [
3336
3336
  "## 8. Verification before claiming done",
3337
3337
  "",
3338
- "- `bunx turbo test --affected --force` (the command and its output are attested in the Evidence block).",
3338
+ "- The pre-push hook already ran the affected suite on your one push and refused it on failure — do not additionally run it yourself; `vinaya pr report --write`/`--push` separately re-runs it with `--force` to attest the command and its output in the Evidence block.",
3339
3339
  "- The full `bun run test` suite is CI's to run, on the one push — never run it locally.",
3340
3340
  "- Every blast-radius consumer named in §4, re-verified by name.",
3341
3341
  '- `roles/developer.md`\'s tier checklist genuinely satisfied, and `PR_BODY="$(cat <body-file>)" bun packages/aeg-core/bin/verify-docs.ts --pr` green.'
@@ -3444,6 +3444,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
3444
3444
  import { join as join4 } from "node:path";
3445
3445
 
3446
3446
  // src/commands/doctrine.ts
3447
+ import { execFileSync as execFileSync2 } from "node:child_process";
3447
3448
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
3448
3449
  import { dirname as dirname2, join as join2, sep } from "node:path";
3449
3450
  import matter2 from "gray-matter";
@@ -3477,10 +3478,29 @@ function packageRoot(moduleUrl) {
3477
3478
 
3478
3479
  // src/commands/doctrine.ts
3479
3480
  var ENTRY_SEGMENTS = ["skills", "aeg", "SKILL.md"];
3480
- function resolveDoctrineRoot(pkg = packageRoot(import.meta.url)) {
3481
+ function resolveDoctrineRoot(pkg = packageRoot(import.meta.url), cwd = process.cwd()) {
3482
+ return resolveDoctrineRootInfo(pkg, cwd)?.root ?? null;
3483
+ }
3484
+ function gitToplevel(cwd) {
3485
+ try {
3486
+ return execFileSync2("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
3487
+ encoding: "utf8",
3488
+ stdio: ["ignore", "pipe", "pipe"]
3489
+ }).trim();
3490
+ } catch {
3491
+ return null;
3492
+ }
3493
+ }
3494
+ function resolveDoctrineRootInfo(pkg = packageRoot(import.meta.url), cwd = process.cwd()) {
3495
+ const toplevel = gitToplevel(cwd);
3496
+ if (toplevel !== null) {
3497
+ const treeRoot = join2(toplevel, "aeg-root");
3498
+ if (existsSync2(join2(treeRoot, "roles")))
3499
+ return { root: treeRoot, source: "tree" };
3500
+ }
3481
3501
  const fromSource = !pkg.split(sep).includes("node_modules");
3482
- const root = fromSource ? join2(dirname2(dirname2(pkg)), "aeg-root") : join2(pkg, "aeg-root");
3483
- return hasDoctrineEntry(root) ? root : null;
3502
+ const bundleRoot = fromSource ? join2(dirname2(dirname2(pkg)), "aeg-root") : join2(pkg, "aeg-root");
3503
+ return hasDoctrineEntry(bundleRoot) ? { root: bundleRoot, source: "bundle" } : null;
3484
3504
  }
3485
3505
  var ROLE_ALIASES = { "code-reviewer": "reviewer" };
3486
3506
  function hasDoctrineEntry(root) {
@@ -3550,21 +3570,24 @@ function discoverRoleNames(doctrineRoot) {
3550
3570
  function agentSkillPath(roleName) {
3551
3571
  return `.agents/skills/vinaya-${roleName}/SKILL.md`;
3552
3572
  }
3553
- function renderAgentSkill(roleName) {
3573
+ function doctrineInvocation(selfHost) {
3574
+ return selfHost ? `bun ${selfHost.dir}/src/index.ts doctrine` : "vinaya doctrine";
3575
+ }
3576
+ function renderAgentSkill(roleName, selfHost = null) {
3554
3577
  const roleTitle = formatRoleTitle(roleName);
3555
3578
  return `---
3556
3579
  name: vinaya-${roleName}
3557
3580
  description: Act as the AEG ${roleTitle} for this repo.
3558
3581
  ---
3559
- Run \`vinaya doctrine --role ${roleName}\` and follow its output as your operating instructions for this session.
3582
+ Run \`${doctrineInvocation(selfHost)} --role ${roleName}\` and follow its output as your operating instructions for this session.
3560
3583
  `;
3561
3584
  }
3562
- function buildAgentsSkillsOps(doctrineRoot) {
3585
+ function buildAgentsSkillsOps(doctrineRoot, selfHost = null) {
3563
3586
  const roles = discoverRoleNames(doctrineRoot);
3564
3587
  return roles.map((role) => ({
3565
3588
  kind: "create-file",
3566
3589
  path: agentSkillPath(role),
3567
- content: renderAgentSkill(role),
3590
+ content: renderAgentSkill(role, selfHost),
3568
3591
  group: AGENTS_SKILLS_GROUP
3569
3592
  }));
3570
3593
  }
@@ -3572,20 +3595,24 @@ function buildAgentsSkillsOps(doctrineRoot) {
3572
3595
  // src/lib/claude-command-emitter.ts
3573
3596
  var CLAUDE_COMMAND_GROUP = "Claude Code command (.claude/commands/)";
3574
3597
  var CLAUDE_COMMAND_PATH = ".claude/commands/vinaya.md";
3575
- function renderClaudeCommand() {
3598
+ function doctrineInvocation2(selfHost) {
3599
+ return selfHost ? `bun ${selfHost.dir}/src/index.ts doctrine` : "vinaya doctrine";
3600
+ }
3601
+ function renderClaudeCommand(selfHost = null) {
3602
+ const invocation = doctrineInvocation2(selfHost);
3576
3603
  return `---
3577
3604
  description: Act as an AEG role for this repo.
3578
- allowed-tools: Bash(vinaya doctrine *)
3605
+ allowed-tools: Bash(${invocation} *)
3579
3606
  ---
3580
- !\`vinaya doctrine --role "$ARGUMENTS"\`
3607
+ !\`${invocation} --role "$ARGUMENTS"\`
3581
3608
  `;
3582
3609
  }
3583
- function buildClaudeCommandOps() {
3610
+ function buildClaudeCommandOps(selfHost = null) {
3584
3611
  return [
3585
3612
  {
3586
3613
  kind: "create-file",
3587
3614
  path: CLAUDE_COMMAND_PATH,
3588
- content: renderClaudeCommand(),
3615
+ content: renderClaudeCommand(selfHost),
3589
3616
  group: CLAUDE_COMMAND_GROUP
3590
3617
  }
3591
3618
  ];
@@ -3681,16 +3708,19 @@ function buildClaudeStopHookOps() {
3681
3708
  // src/lib/gemini-command-emitter.ts
3682
3709
  var GEMINI_COMMAND_GROUP = "Gemini CLI command (.gemini/commands/)";
3683
3710
  var GEMINI_COMMAND_PATH = ".gemini/commands/vinaya.toml";
3684
- function renderGeminiCommand() {
3711
+ function doctrineInvocation3(selfHost) {
3712
+ return selfHost ? `bun ${selfHost.dir}/src/index.ts doctrine` : "vinaya doctrine";
3713
+ }
3714
+ function renderGeminiCommand(selfHost = null) {
3685
3715
  return `description = "Act as an AEG role for this repo."
3686
- prompt = "!{vinaya doctrine --role {{args}}}"
3716
+ prompt = "!{${doctrineInvocation3(selfHost)} --role {{args}}}"
3687
3717
  `;
3688
3718
  }
3689
- function buildGeminiCommandOp() {
3719
+ function buildGeminiCommandOp(selfHost = null) {
3690
3720
  return {
3691
3721
  kind: "create-file",
3692
3722
  path: GEMINI_COMMAND_PATH,
3693
- content: renderGeminiCommand(),
3723
+ content: renderGeminiCommand(selfHost),
3694
3724
  group: GEMINI_COMMAND_GROUP
3695
3725
  };
3696
3726
  }
@@ -3881,7 +3911,7 @@ ${vinayaSetupSteps(selfHost, "pull-request")}${adopterSetupStep(ciSetup)} #
3881
3911
  GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
3882
3912
  PR_NUMBER: \${{ github.event.pull_request.number }}
3883
3913
  run: |
3884
- DELIM="PR_BODY_$(date +%s%N)"
3914
+ DELIM="PR_BODY_$(openssl rand -hex 16)"
3885
3915
  echo "PR_BODY<<$DELIM" >> "$GITHUB_ENV"
3886
3916
  gh pr view "$PR_NUMBER" --json body --jq .body >> "$GITHUB_ENV"
3887
3917
  echo "$DELIM" >> "$GITHUB_ENV"
@@ -4051,13 +4081,26 @@ jobs:
4051
4081
  # lookup vinaya-review-verdict.yml's own retrigger step runs.
4052
4082
  set -o pipefail
4053
4083
  RUN_TITLE="Vinaya Review Gate PR #$PR_NUMBER @ $HEAD_SHA"
4084
+ # O7 (found live 2026-09-04): dropped \`select(.status == "completed")\`
4085
+ # — two retriggers for the same head race the SAME required run
4086
+ # (PR #401, PR #409: runs 33841069791/33841065139), and the loser's
4087
+ # query landed while the winner's \`gh run rerun\` had already
4088
+ # flipped the run to \`in_progress\`. Requiring \`completed\` made
4089
+ # that run invisible to the loser entirely — not "already handled,
4090
+ # skip", but "nothing matched, give up" — so when that in-flight
4091
+ # attempt itself later failed, nothing retried it again and the
4092
+ # gate stayed red until a hand rerun. Matching on title+event alone
4093
+ # (any status) lets the loser find the SAME run mid-run and try
4094
+ # \`gh run rerun\` on it too; the existing "declined (already
4095
+ # queued)" fallback below already handles that outcome harmlessly
4096
+ # — it is not a new failure mode, only a query that used to see
4097
+ # nothing at all now sees the run and takes the same safe no-op.
4054
4098
  RUN_ID=$(gh api --paginate \\
4055
4099
  "repos/\${{ github.repository }}/actions/workflows/vinaya-review.yml/runs?event=pull_request_target&per_page=100" \\
4056
4100
  | jq -sr --arg title "$RUN_TITLE" '
4057
4101
  [.[].workflow_runs[]
4058
4102
  | select(.display_title == $title)
4059
4103
  | select(.event == "pull_request_target")
4060
- | select(.status == "completed")
4061
4104
  | select(.conclusion != "cancelled")
4062
4105
  | .id][0] // empty')
4063
4106
  if [ -z "$RUN_ID" ]; then
@@ -4134,7 +4177,7 @@ ${vinayaSetupSteps(selfHost, "trusted")} # PR_BODY is what makes body-bare-
4134
4177
  GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
4135
4178
  PR_NUMBER: \${{ github.event.pull_request.number }}
4136
4179
  run: |
4137
- DELIM="PR_BODY_$(date +%s%N)"
4180
+ DELIM="PR_BODY_$(openssl rand -hex 16)"
4138
4181
  echo "PR_BODY<<$DELIM" >> "$GITHUB_ENV"
4139
4182
  gh pr view "$PR_NUMBER" --json body --jq .body >> "$GITHUB_ENV"
4140
4183
  echo "$DELIM" >> "$GITHUB_ENV"
@@ -4255,13 +4298,26 @@ ${vinayaSetupSteps(selfHost, "trusted")} - name: Review gate (verdict evalu
4255
4298
  # query matches that immutable display_title exactly.
4256
4299
  set -o pipefail
4257
4300
  RUN_TITLE="Vinaya Review Gate PR #$PR_NUMBER @ $HEAD_SHA"
4301
+ # O7 (found live 2026-09-04): dropped \`select(.status == "completed")\`
4302
+ # — two retriggers for the same head race the SAME required run
4303
+ # (PR #401, PR #409: runs 33841069791/33841065139), and the loser's
4304
+ # query landed while the winner's \`gh run rerun\` had already
4305
+ # flipped the run to \`in_progress\`. Requiring \`completed\` made
4306
+ # that run invisible to the loser entirely — not "already handled,
4307
+ # skip", but "nothing matched, give up" — so when that in-flight
4308
+ # attempt itself later failed, nothing retried it again and the
4309
+ # gate stayed red until a hand rerun. Matching on title+event alone
4310
+ # (any status) lets the loser find the SAME run mid-run and try
4311
+ # \`gh run rerun\` on it too; the existing "declined (already
4312
+ # queued)" fallback below already handles that outcome harmlessly
4313
+ # — it is not a new failure mode, only a query that used to see
4314
+ # nothing at all now sees the run and takes the same safe no-op.
4258
4315
  RUN_ID=$(gh api --paginate \\
4259
4316
  "repos/\${{ github.repository }}/actions/workflows/vinaya-review.yml/runs?event=pull_request_target&per_page=100" \\
4260
4317
  | jq -sr --arg title "$RUN_TITLE" '
4261
4318
  [.[].workflow_runs[]
4262
4319
  | select(.display_title == $title)
4263
4320
  | select(.event == "pull_request_target")
4264
- | select(.status == "completed")
4265
4321
  | select(.conclusion != "cancelled")
4266
4322
  | .id][0] // empty')
4267
4323
  if [ -z "$RUN_ID" ]; then
@@ -4388,8 +4444,24 @@ function preCommitBody(selfHost) {
4388
4444
  ${hookRun(selfHost, "check --all --diff-only --local --skip-full")}`;
4389
4445
  }
4390
4446
  function prePushBody(selfHost) {
4391
- return `# Vinaya pre-push gate. Runs branch/dispatch checks before the push leaves.
4447
+ const base = `# Vinaya pre-push gate. Runs branch/dispatch checks before the push leaves.
4448
+ # Forward git's own pre-push stdin (one "<local ref> <local sha> <remote
4449
+ # ref> <remote sha>" line per ref being pushed) so main-branch-refusal can
4450
+ # tell a tag-only push apart from one that also carries a branch ref.
4451
+ VINAYA_PUSH_REFS="$(cat)"
4452
+ export VINAYA_PUSH_REFS
4392
4453
  ${hookRun(selfHost, "check --all --local")}`;
4454
+ if (!selfHost)
4455
+ return base;
4456
+ return `${base}
4457
+ # Ring 0: the affected test suite. A failing test refuses the push with
4458
+ # its own output (#407, O4). --concurrency=1 (O9, found live 2026-09-04):
4459
+ # a local machine already running other work (another worktree's own
4460
+ # build/test, an IDE indexer) alongside this hook's parallel package
4461
+ # suites was measured pushing a git-clone-heavy fixture test past its
4462
+ # timeout under real contention — CI's own runner is dedicated and keeps
4463
+ # turbo.json's default concurrency; only this hook invocation is serialized.
4464
+ bunx turbo test --affected --concurrency=1 || exit 1`;
4393
4465
  }
4394
4466
  function commitMsgBody(selfHost) {
4395
4467
  return `# Vinaya commit-message gate. Validates the message's first line against
@@ -4751,14 +4823,14 @@ function buildInitOps(ctx) {
4751
4823
  if (!doctrineRoot) {
4752
4824
  throw new Error('vinaya init: --agents includes "skills" but no bundled doctrine was found next to this CLI install — ' + "cannot discover agent-skill roles. Reinstall @attalabs/vinaya, or run bundle-doctrine first in a repo " + "that vendors the CLI.");
4753
4825
  }
4754
- ops.push(...buildAgentsSkillsOps(doctrineRoot));
4826
+ ops.push(...buildAgentsSkillsOps(doctrineRoot, ctx.selfHost));
4755
4827
  }
4756
4828
  if (ctx.agents.has("claude")) {
4757
- ops.push(...buildClaudeCommandOps());
4829
+ ops.push(...buildClaudeCommandOps(ctx.selfHost));
4758
4830
  ops.push(...buildClaudeStopHookOps());
4759
4831
  }
4760
4832
  if (ctx.agents.has("gemini")) {
4761
- ops.push(buildGeminiCommandOp());
4833
+ ops.push(buildGeminiCommandOp(ctx.selfHost));
4762
4834
  }
4763
4835
  if (ctx.agents.size > 0) {
4764
4836
  const affected = [];
@@ -4784,12 +4856,12 @@ function buildInitOps(ctx) {
4784
4856
  }
4785
4857
 
4786
4858
  // src/lib/ops.ts
4787
- import { execFileSync as execFileSync3 } from "node:child_process";
4859
+ import { execFileSync as execFileSync4 } from "node:child_process";
4788
4860
  import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
4789
4861
  import { dirname as dirname4, isAbsolute, join as join6, resolve as resolve2, sep as sep2 } from "node:path";
4790
4862
 
4791
4863
  // src/lib/config.ts
4792
- import { execFileSync as execFileSync2 } from "node:child_process";
4864
+ import { execFileSync as execFileSync3 } from "node:child_process";
4793
4865
  import { createHash as createHash2 } from "node:crypto";
4794
4866
  import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, realpathSync, writeFileSync } from "node:fs";
4795
4867
  import { homedir } from "node:os";
@@ -5062,7 +5134,7 @@ function trustAnchorRepo() {
5062
5134
  return validated;
5063
5135
  }
5064
5136
  try {
5065
- const url = execFileSync2("git", ["remote", "get-url", "origin"], {
5137
+ const url = execFileSync3("git", ["remote", "get-url", "origin"], {
5066
5138
  encoding: "utf-8",
5067
5139
  stdio: ["ignore", "pipe", "pipe"]
5068
5140
  }).trim();
@@ -5076,7 +5148,7 @@ function ghFetchTrustAnchorConfig() {
5076
5148
  const repo = trustAnchorRepo();
5077
5149
  if (!repo)
5078
5150
  throw new Error("could not resolve repo identity for the trust-anchor read");
5079
- return execFileSync2("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
5151
+ return execFileSync3("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
5080
5152
  encoding: "utf-8",
5081
5153
  stdio: ["ignore", "pipe", "pipe"]
5082
5154
  });
@@ -5177,7 +5249,7 @@ function writeTokensCollectTrustStore(storePath, store) {
5177
5249
  var GIT_IDENTITY_TIMEOUT_MS = 5000;
5178
5250
  function gitCommonDir(cwd = process.cwd()) {
5179
5251
  try {
5180
- const raw = execFileSync2("git", ["rev-parse", "--git-common-dir"], {
5252
+ const raw = execFileSync3("git", ["rev-parse", "--git-common-dir"], {
5181
5253
  cwd,
5182
5254
  encoding: "utf-8",
5183
5255
  stdio: ["ignore", "pipe", "pipe"],
@@ -5196,7 +5268,7 @@ function repoLocalConfigDir() {
5196
5268
  }
5197
5269
  function gitBlobHash(absolutePath, cwd) {
5198
5270
  try {
5199
- const raw = execFileSync2("git", ["hash-object", absolutePath], {
5271
+ const raw = execFileSync3("git", ["hash-object", absolutePath], {
5200
5272
  cwd,
5201
5273
  encoding: "utf-8",
5202
5274
  stdio: ["ignore", "pipe", "pipe"],
@@ -5247,7 +5319,7 @@ function resolveManagedBlockPath(repoRoot, opPath) {
5247
5319
  }
5248
5320
  function gitCommonDir2(repoRoot) {
5249
5321
  try {
5250
- const commonDir = execFileSync3("git", ["-C", repoRoot, "rev-parse", "--git-common-dir"], {
5322
+ const commonDir = execFileSync4("git", ["-C", repoRoot, "rev-parse", "--git-common-dir"], {
5251
5323
  encoding: "utf8"
5252
5324
  }).trim();
5253
5325
  if (commonDir === "")
@@ -5826,7 +5898,7 @@ function closeStdin() {
5826
5898
  }
5827
5899
 
5828
5900
  // src/commands/tokens.ts
5829
- import { execFileSync as execFileSync4 } from "node:child_process";
5901
+ import { execFileSync as execFileSync5 } from "node:child_process";
5830
5902
  import { resolve as resolvePath } from "node:path";
5831
5903
  var USAGE = [
5832
5904
  'Usage: vinaya tokens --phase "<task-id>: develop" --role Developer',
@@ -5981,7 +6053,7 @@ function realDeps() {
5981
6053
  ...hardenedMeteringDeps(),
5982
6054
  loadConfig,
5983
6055
  repoConfigDir: repoLocalConfigDir,
5984
- runScript: (interpreter, scriptAbsolutePath, cwd) => execFileSync4(interpreter, [scriptAbsolutePath], { cwd, encoding: "utf-8", timeout: COLLECT_COMMAND_TIMEOUT_MS }),
6056
+ runScript: (interpreter, scriptAbsolutePath, cwd) => execFileSync5(interpreter, [scriptAbsolutePath], { cwd, encoding: "utf-8", timeout: COLLECT_COMMAND_TIMEOUT_MS }),
5985
6057
  warn: (message) => console.error(message),
5986
6058
  gitCommonDir,
5987
6059
  scriptContentHash: gitBlobHash,
@@ -6107,7 +6179,7 @@ function renderArchiveTokensLine(capability, phase, role) {
6107
6179
  return { line: formatTokensLine({ phase, role, summary: capability.summary }), dangling: null };
6108
6180
  }
6109
6181
  function sh(args, input) {
6110
- return execFileSync5(args[0], args.slice(1), { encoding: "utf8", input, env: process.env }).trim();
6182
+ return execFileSync6(args[0], args.slice(1), { encoding: "utf8", input, env: process.env }).trim();
6111
6183
  }
6112
6184
  function shJson(args) {
6113
6185
  return JSON.parse(sh(args));
@@ -6321,7 +6393,7 @@ async function archiveTrancheCommand(args) {
6321
6393
  }
6322
6394
 
6323
6395
  // src/commands/audit.ts
6324
- import { execFileSync as execFileSync6 } from "node:child_process";
6396
+ import { execFileSync as execFileSync7 } from "node:child_process";
6325
6397
  function ring2Accelerated2() {
6326
6398
  return loadConfig()?.rings?.ring2_asyncAudits === true;
6327
6399
  }
@@ -6340,7 +6412,7 @@ var DIRECT_PUSH_MARKER = `<!-- ${DIRECT_PUSH_LABEL} -->`;
6340
6412
  var DIRECT_PUSH_LABEL_DESCRIPTION = "A commit landed on main with no associated merged PR (ring-2 detection)";
6341
6413
  function shSoft(args, input) {
6342
6414
  try {
6343
- return execFileSync6(args[0], args.slice(1), {
6415
+ return execFileSync7(args[0], args.slice(1), {
6344
6416
  encoding: "utf8",
6345
6417
  input,
6346
6418
  timeout: COMMAND_TIMEOUT_MS
@@ -6364,7 +6436,7 @@ function shSoftJson(args) {
6364
6436
  }
6365
6437
  }
6366
6438
  function sh2(args, input) {
6367
- return execFileSync6(args[0], args.slice(1), { encoding: "utf8", input }).trim();
6439
+ return execFileSync7(args[0], args.slice(1), { encoding: "utf8", input }).trim();
6368
6440
  }
6369
6441
  function listTaskBranches() {
6370
6442
  const out = shSoft(["git", "ls-remote", "--heads", "origin", "task/*"]);
@@ -6625,7 +6697,7 @@ async function auditCommand(args) {
6625
6697
 
6626
6698
  // src/commands/brief.ts
6627
6699
  import { createHash as createHash3 } from "node:crypto";
6628
- import { execFileSync as execFileSync8 } from "node:child_process";
6700
+ import { execFileSync as execFileSync9 } from "node:child_process";
6629
6701
  import { existsSync as existsSync7, readFileSync as readFileSync9, readdirSync as readdirSync5, writeFileSync as writeFileSync3 } from "node:fs";
6630
6702
  import { join as join9 } from "node:path";
6631
6703
 
@@ -7112,6 +7184,26 @@ var COMMANDS = [
7112
7184
  "Never reimplements or edits `init`/`init product`/`demo break`/`doctor` — it only calls their existing, unmodified entry points in sequence."
7113
7185
  ],
7114
7186
  status: "shipped"
7187
+ },
7188
+ {
7189
+ name: "release",
7190
+ description: "Run this repo's own publish sequence in one command, refusing to start unless every precondition holds",
7191
+ flags: [
7192
+ {
7193
+ flag: "--dry-run",
7194
+ description: "Run the preconditions only and print the plan; publishes nothing"
7195
+ },
7196
+ {
7197
+ flag: "--allow-any-commit",
7198
+ description: "Skip the check that HEAD's commit is a Version Packages commit"
7199
+ }
7200
+ ],
7201
+ details: [
7202
+ "Refuses unless HEAD is the default branch, the working tree is clean, HEAD equals `origin/<default>` (after `git fetch origin`), HEAD's commit subject starts with `Chore(release): Version packages` (unless `--allow-any-commit`), and `npm whoami` exits `0` — each its own refusal naming the fix.",
7203
+ "Then streams `bun install --frozen-lockfile`, `bun run build`, `bun run changeset:publish`, and `git push origin --tags` — a real push, so the repo's own generated pre-push hook sees it exactly as any other push would. After it, prints `npm view <pkg> version` for every tag now on HEAD, noting registry lag on `@attalabs/vinaya` (observed ~20 minutes) when it still shows the previous version.",
7204
+ 'This is the one procedure named in `apps/cli/specs/self-hosting.md`, "How the published version is produced" — publishing itself stays manual and human-triggered; this command only removes the hand-typed four-step recipe.'
7205
+ ],
7206
+ status: "shipped"
7115
7207
  }
7116
7208
  ];
7117
7209
  // ../../packages/sources/src/doctrine-file-adapter.ts
@@ -7155,7 +7247,7 @@ var StateSourceConfigSchema = z2.discriminatedUnion("kind", [
7155
7247
  z2.object({ kind: z2.literal("file"), root: z2.string().optional() })
7156
7248
  ]);
7157
7249
  // src/checks/edge-resolve.ts
7158
- import { execFileSync as execFileSync7 } from "node:child_process";
7250
+ import { execFileSync as execFileSync8 } from "node:child_process";
7159
7251
  var DIRECT_ISSUE_REF = /#(\d+)/;
7160
7252
  var issueCache = new Map;
7161
7253
  function fetchIssueState(num, repo) {
@@ -7164,7 +7256,7 @@ function fetchIssueState(num, repo) {
7164
7256
  return issueCache.get(key) ?? null;
7165
7257
  let out = null;
7166
7258
  try {
7167
- const raw = execFileSync7("gh", [
7259
+ const raw = execFileSync8("gh", [
7168
7260
  "issue",
7169
7261
  "view",
7170
7262
  String(num),
@@ -7277,7 +7369,7 @@ var DOC_OWNERS_PATH2 = ".vinaya/doc-owners";
7277
7369
  var TEMPLATE_PATH = "aeg-root/templates/brief-template.md";
7278
7370
  function git(args) {
7279
7371
  try {
7280
- return execFileSync8("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
7372
+ return execFileSync9("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
7281
7373
  } catch {
7282
7374
  return "";
7283
7375
  }
@@ -7304,7 +7396,7 @@ async function resolveToken() {
7304
7396
  if (process.env.GH_TOKEN)
7305
7397
  return process.env.GH_TOKEN;
7306
7398
  try {
7307
- return execFileSync8("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
7399
+ return execFileSync9("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
7308
7400
  } catch {
7309
7401
  return null;
7310
7402
  }
@@ -7465,7 +7557,7 @@ ${result.missing.map((m) => ` - ${m}`).join(`
7465
7557
  }
7466
7558
 
7467
7559
  // src/commands/check.ts
7468
- import { execFileSync as execFileSync9 } from "node:child_process";
7560
+ import { execFileSync as execFileSync10 } from "node:child_process";
7469
7561
  import { dirname as dirname5 } from "node:path";
7470
7562
 
7471
7563
  // src/checks/contract.ts
@@ -7870,7 +7962,7 @@ var REGISTRY = [
7870
7962
  run: bin("check-main-branch-refusal"),
7871
7963
  scope: "full",
7872
7964
  timeoutMs: 15000,
7873
- env: {}
7965
+ env: { VINAYA_PUSH_REFS: { optional: true } }
7874
7966
  },
7875
7967
  0
7876
7968
  ],
@@ -8418,7 +8510,7 @@ async function buildRolePlan(configDir, configRoles) {
8418
8510
  // src/commands/check.ts
8419
8511
  function git2(args) {
8420
8512
  try {
8421
- return execFileSync9("git", args, { encoding: "utf8" }).trim();
8513
+ return execFileSync10("git", args, { encoding: "utf8" }).trim();
8422
8514
  } catch {
8423
8515
  return "";
8424
8516
  }
@@ -8682,14 +8774,14 @@ function commitMsgCommand(args) {
8682
8774
  }
8683
8775
 
8684
8776
  // src/commands/demo.ts
8685
- import { execFileSync as execFileSync10, spawnSync } from "node:child_process";
8777
+ import { execFileSync as execFileSync11, spawnSync } from "node:child_process";
8686
8778
  import { existsSync as existsSync10, readFileSync as readFileSync12, rmSync as rmSync2, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
8687
8779
  import { isAbsolute as isAbsolute2, join as join12 } from "node:path";
8688
8780
  var DEMO_BRANCH_PREFIX = "vinaya/demo-break-";
8689
8781
  var FIXTURE_PATH = ".vinaya-demo-brief.md";
8690
8782
  var STATE_FILENAME = "vinaya-demo-state.json";
8691
8783
  function gitCapture(repoRoot, args) {
8692
- return execFileSync10("git", args, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
8784
+ return execFileSync11("git", args, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
8693
8785
  }
8694
8786
  function currentBranchName(repoRoot) {
8695
8787
  try {
@@ -8700,7 +8792,7 @@ function currentBranchName(repoRoot) {
8700
8792
  }
8701
8793
  function branchExists(repoRoot, name) {
8702
8794
  try {
8703
- execFileSync10("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${name}`], {
8795
+ execFileSync11("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${name}`], {
8704
8796
  cwd: repoRoot,
8705
8797
  stdio: ["ignore", "pipe", "pipe"]
8706
8798
  });
@@ -8902,7 +8994,7 @@ async function demoBreakCommand(args) {
8902
8994
  }
8903
8995
 
8904
8996
  // src/commands/doctor.ts
8905
- import { execFileSync as execFileSync11 } from "node:child_process";
8997
+ import { execFileSync as execFileSync12 } from "node:child_process";
8906
8998
  import { existsSync as existsSync13, readdirSync as readdirSync7, readFileSync as readFileSync16, statSync as statSync2 } from "node:fs";
8907
8999
  import { delimiter, join as join15 } from "node:path";
8908
9000
 
@@ -9328,7 +9420,7 @@ function diagnoseCustomChecks(repoRoot, config) {
9328
9420
  }
9329
9421
  function listTrackedFiles(repoRoot) {
9330
9422
  try {
9331
- return execFileSync11("git", ["ls-files"], { cwd: repoRoot, encoding: "utf-8" }).split(`
9423
+ return execFileSync12("git", ["ls-files"], { cwd: repoRoot, encoding: "utf-8" }).split(`
9332
9424
  `).map((s) => s.trim()).filter(Boolean);
9333
9425
  } catch {
9334
9426
  return [];
@@ -9593,10 +9685,15 @@ function symbolFor(severity) {
9593
9685
  return "✗";
9594
9686
  }
9595
9687
  }
9596
- function printReport(findings, healthy) {
9688
+ function printReport(findings, healthy, doctrineInfo) {
9597
9689
  process.stdout.write(`vinaya doctor
9598
9690
 
9599
9691
  `);
9692
+ if (doctrineInfo) {
9693
+ process.stdout.write(`doctrine: ${doctrineInfo.root} (${doctrineInfo.source})
9694
+
9695
+ `);
9696
+ }
9600
9697
  for (const f of findings) {
9601
9698
  process.stdout.write(`${symbolFor(f.severity)} [${f.check}] ${f.message}
9602
9699
  `);
@@ -9612,6 +9709,7 @@ async function runDoctor(args, deps) {
9612
9709
  console.error("Error: not a git repository. Run `vinaya doctor` from inside your repo.");
9613
9710
  return 1;
9614
9711
  }
9712
+ const doctrineInfo = resolveDoctrineRootInfo(undefined, repo.repoRoot);
9615
9713
  const configRead = readConfig(repo.repoRoot);
9616
9714
  const findings = [];
9617
9715
  let hasDrift = false;
@@ -9652,9 +9750,9 @@ async function runDoctor(args, deps) {
9652
9750
  findings.push(...diagnoseTestCi(repo.repoRoot));
9653
9751
  const healthy = findings.every((f) => f.severity === "ok" || f.severity === "info");
9654
9752
  if (jsonOutput) {
9655
- printJson({ healthy, findings });
9753
+ printJson({ healthy, findings, doctrineInfo });
9656
9754
  } else {
9657
- printReport(findings, healthy);
9755
+ printReport(findings, healthy, doctrineInfo);
9658
9756
  }
9659
9757
  return healthy ? 0 : 1;
9660
9758
  }
@@ -9978,10 +10076,10 @@ async function initProductCommand(args) {
9978
10076
  }
9979
10077
 
9980
10078
  // src/commands/issue.ts
9981
- import { execFileSync as execFileSync13 } from "node:child_process";
10079
+ import { execFileSync as execFileSync14 } from "node:child_process";
9982
10080
 
9983
10081
  // src/lib/forge-write.ts
9984
- import { execFileSync as execFileSync12 } from "node:child_process";
10082
+ import { execFileSync as execFileSync13 } from "node:child_process";
9985
10083
  import { existsSync as existsSync16, mkdtempSync, readdirSync as readdirSync8, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
9986
10084
  import { readFileSync as readFileSync19 } from "node:fs";
9987
10085
  import { tmpdir } from "node:os";
@@ -10075,7 +10173,7 @@ function ensureTrancheLabelExists(slug) {
10075
10173
  const name = trancheLabel(slug);
10076
10174
  let existing;
10077
10175
  try {
10078
- const out = execFileSync12("gh", ["label", "list", "--json", "name", "--limit", "200"], {
10176
+ const out = execFileSync13("gh", ["label", "list", "--json", "name", "--limit", "200"], {
10079
10177
  encoding: "utf8",
10080
10178
  stdio: ["ignore", "pipe", "pipe"]
10081
10179
  });
@@ -10088,7 +10186,7 @@ function ensureTrancheLabelExists(slug) {
10088
10186
  if (existing.some((l) => l.name === name))
10089
10187
  return;
10090
10188
  try {
10091
- execFileSync12("gh", ["label", "create", name, "--color", TRANCHE_LABEL_COLOR, "--description", `Tranche: ${slug}`], {
10189
+ execFileSync13("gh", ["label", "create", name, "--color", TRANCHE_LABEL_COLOR, "--description", `Tranche: ${slug}`], {
10092
10190
  encoding: "utf8",
10093
10191
  stdio: ["ignore", "pipe", "pipe"]
10094
10192
  });
@@ -10240,7 +10338,7 @@ function validateForgeWrite(input) {
10240
10338
  }
10241
10339
  function git3(args) {
10242
10340
  try {
10243
- return execFileSync12("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
10341
+ return execFileSync13("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
10244
10342
  } catch {
10245
10343
  return "";
10246
10344
  }
@@ -10335,7 +10433,7 @@ function locateBodyOrRefuse(ghArgs, retryCommand) {
10335
10433
  function fetchForgeLabels(issueRef) {
10336
10434
  let out;
10337
10435
  try {
10338
- out = execFileSync13("gh", ["issue", "view", issueRef, "--json", "labels"], {
10436
+ out = execFileSync14("gh", ["issue", "view", issueRef, "--json", "labels"], {
10339
10437
  encoding: "utf8",
10340
10438
  stdio: ["ignore", "pipe", "pipe"]
10341
10439
  });
@@ -10363,7 +10461,7 @@ function reportPass(json, command) {
10363
10461
  function runGhWrite(ghCmd, ghArgs, bodyResult, json) {
10364
10462
  const { finalArgs, cleanup: cleanup2 } = resolveShippableArgs(ghArgs, bodyResult);
10365
10463
  try {
10366
- const out = execFileSync13("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
10464
+ const out = execFileSync14("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
10367
10465
  const url = out.trim();
10368
10466
  if (json)
10369
10467
  printJson({ validated: true, written: true, url });
@@ -10449,13 +10547,13 @@ function issueEditCommand(args) {
10449
10547
  }
10450
10548
 
10451
10549
  // src/commands/milestone.ts
10452
- import { execFileSync as execFileSync14 } from "node:child_process";
10550
+ import { execFileSync as execFileSync15 } from "node:child_process";
10453
10551
  var RETRY_CREATE2 = "vinaya milestone create --title <title> --body-file <path>";
10454
10552
  var RETRY_ADOPT = "vinaya milestone adopt --target <title> --slug <slug> [--slug <slug> ...]";
10455
10553
  var RETRY_EDIT2 = "vinaya milestone edit <n> --body-file <path>";
10456
10554
  var RETRY_CLOSE = "vinaya milestone close --slug <slug>";
10457
10555
  function sh3(args, input) {
10458
- return execFileSync14(args[0], args.slice(1), {
10556
+ return execFileSync15(args[0], args.slice(1), {
10459
10557
  encoding: "utf8",
10460
10558
  input,
10461
10559
  env: process.env,
@@ -10955,7 +11053,7 @@ ${registration}
10955
11053
  }
10956
11054
 
10957
11055
  // src/commands/pr.ts
10958
- import { execFileSync as execFileSync15 } from "node:child_process";
11056
+ import { execFileSync as execFileSync16 } from "node:child_process";
10959
11057
  import { rmSync as rmSync4, writeFileSync as writeFileSync11 } from "node:fs";
10960
11058
  import { tmpdir as tmpdir2 } from "node:os";
10961
11059
  import { join as join22 } from "node:path";
@@ -11250,7 +11348,7 @@ var RETRY_CREATE3 = "vinaya pr create --validate-only …";
11250
11348
  var RETRY_EDIT3 = "vinaya pr edit <n> --validate-only …";
11251
11349
  function git4(args) {
11252
11350
  try {
11253
- return execFileSync15("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
11351
+ return execFileSync16("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
11254
11352
  } catch {
11255
11353
  return "";
11256
11354
  }
@@ -11284,7 +11382,7 @@ function reportPass2(json, command) {
11284
11382
  function runGhWrite2(ghCmd, ghArgs, bodyResult, json) {
11285
11383
  const { finalArgs, cleanup: cleanup2 } = resolveShippableArgs(ghArgs, bodyResult);
11286
11384
  try {
11287
- const out = execFileSync15("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
11385
+ const out = execFileSync16("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
11288
11386
  const url = out.trim();
11289
11387
  if (json)
11290
11388
  printJson({ validated: true, written: true, url });
@@ -11299,7 +11397,7 @@ function runGhWrite2(ghCmd, ghArgs, bodyResult, json) {
11299
11397
  function fetchPrForgeContext(prRef) {
11300
11398
  let viewOut;
11301
11399
  try {
11302
- viewOut = execFileSync15("gh", ["pr", "view", prRef, "--json", "headRefName,files"], {
11400
+ viewOut = execFileSync16("gh", ["pr", "view", prRef, "--json", "headRefName,files"], {
11303
11401
  encoding: "utf8",
11304
11402
  stdio: ["ignore", "pipe", "pipe"]
11305
11403
  });
@@ -11353,7 +11451,7 @@ ${brief}
11353
11451
  const tmp = join22(tmpdir2(), `vinaya-pr-create-brief-${process.pid}-${Date.now()}.md`);
11354
11452
  writeFileSync11(tmp, commentBody);
11355
11453
  try {
11356
- execFileSync15("gh", ["pr", "comment", prNumber, "--body-file", tmp], {
11454
+ execFileSync16("gh", ["pr", "comment", prNumber, "--body-file", tmp], {
11357
11455
  encoding: "utf8",
11358
11456
  stdio: ["ignore", "pipe", "pipe"]
11359
11457
  });
@@ -11476,7 +11574,7 @@ function prEditCommand(args) {
11476
11574
  }
11477
11575
 
11478
11576
  // src/commands/pr-report.ts
11479
- import { execFileSync as execFileSync16, spawnSync as spawnSync2 } from "node:child_process";
11577
+ import { execFileSync as execFileSync17, spawnSync as spawnSync2 } from "node:child_process";
11480
11578
  import { existsSync as existsSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "node:fs";
11481
11579
  import { tmpdir as tmpdir3 } from "node:os";
11482
11580
  import { join as join23 } from "node:path";
@@ -11484,14 +11582,14 @@ var EVIDENCE_START = "<!-- AEG:EVIDENCE:START -->";
11484
11582
  var EVIDENCE_END = "<!-- AEG:EVIDENCE:END -->";
11485
11583
  function git5(args) {
11486
11584
  try {
11487
- return execFileSync16("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
11585
+ return execFileSync17("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
11488
11586
  } catch {
11489
11587
  return "";
11490
11588
  }
11491
11589
  }
11492
11590
  function gh(args) {
11493
11591
  try {
11494
- return execFileSync16("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
11592
+ return execFileSync17("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
11495
11593
  } catch (err) {
11496
11594
  const stderr = err.stderr;
11497
11595
  throw new Error(String(stderr ?? err.message).trim() || "gh command failed");
@@ -11510,7 +11608,7 @@ class GitCommandError extends Error {
11510
11608
  }
11511
11609
  function gitStrict(args) {
11512
11610
  try {
11513
- return execFileSync16("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
11611
+ return execFileSync17("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
11514
11612
  } catch (err) {
11515
11613
  const stderr = err.stderr;
11516
11614
  throw new GitCommandError(args, String(stderr ?? err.message).trim() || "non-zero exit");
@@ -11862,16 +11960,17 @@ async function buildReport(opts = {}) {
11862
11960
  const groupA = opts.groupA ?? computeGroupA();
11863
11961
  const gateRunner = opts.gateRunner ?? runRealGates;
11864
11962
  const gateResult = await gateRunner();
11963
+ const outcomes = gateResult.outcomes.filter((o) => o.name !== "evidence-fresh");
11865
11964
  const groupC = opts.groupC ?? computeGroupC(opts.body ?? process.env.PR_BODY ?? "");
11866
- const blockInner = buildBlockInner(groupA, gateResult.outcomes, groupC);
11965
+ const blockInner = buildBlockInner(groupA, outcomes, groupC);
11867
11966
  const block = `${EVIDENCE_START}
11868
11967
  ${blockInner}
11869
11968
  ${EVIDENCE_END}`;
11870
11969
  return {
11871
11970
  block,
11872
11971
  blockInner,
11873
- gatesFailed: gateResult.failed || groupCFailed(groupC),
11874
- gateOutcomes: gateResult.outcomes,
11972
+ gatesFailed: anyGateFailed(outcomes) || groupCFailed(groupC),
11973
+ gateOutcomes: outcomes,
11875
11974
  groupC
11876
11975
  };
11877
11976
  }
@@ -12028,7 +12127,7 @@ The AEG:EVIDENCE block was still written to ${writePath}.`);
12028
12127
  }
12029
12128
 
12030
12129
  // src/commands/pr-verify-evidence.ts
12031
- import { execFileSync as execFileSync17 } from "node:child_process";
12130
+ import { execFileSync as execFileSync18 } from "node:child_process";
12032
12131
 
12033
12132
  // src/commands/pr-verify-evidence-logic.ts
12034
12133
  function publishedMergeBase(region) {
@@ -12115,10 +12214,10 @@ function renderVerdict(verdict) {
12115
12214
 
12116
12215
  // src/commands/pr-verify-evidence.ts
12117
12216
  function gh2(args) {
12118
- return execFileSync17("gh", args, { encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
12217
+ return execFileSync18("gh", args, { encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
12119
12218
  }
12120
12219
  function assertCleanWorktree() {
12121
- const dirty = execFileSync17("git", ["status", "--porcelain"], {
12220
+ const dirty = execFileSync18("git", ["status", "--porcelain"], {
12122
12221
  encoding: "utf8",
12123
12222
  cwd: repoRoot2()
12124
12223
  }).trim();
@@ -12155,7 +12254,7 @@ function assertRepoRootCwd() {
12155
12254
  process.exit(2);
12156
12255
  }
12157
12256
  function repoRoot2() {
12158
- return execFileSync17("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
12257
+ return execFileSync18("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
12159
12258
  }
12160
12259
  async function prVerifyEvidenceCommand(args) {
12161
12260
  const prRef = args[0] && !args[0].startsWith("-") ? args[0] : undefined;
@@ -12179,7 +12278,7 @@ async function prVerifyEvidenceCommand(args) {
12179
12278
  process.exit(1);
12180
12279
  return;
12181
12280
  }
12182
- const localHead = execFileSync17("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
12281
+ const localHead = execFileSync18("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
12183
12282
  if (!forgeHead) {
12184
12283
  process.stderr.write(`pr verify-evidence: REFUSED — could not read #${prRef}'s head from the forge, so the verdict cannot be bound to a commit.
12185
12284
  `);
@@ -12202,7 +12301,7 @@ async function prVerifyEvidenceCommand(args) {
12202
12301
  }
12203
12302
 
12204
12303
  // src/commands/quickstart.ts
12205
- import { execFileSync as execFileSync18 } from "node:child_process";
12304
+ import { execFileSync as execFileSync19 } from "node:child_process";
12206
12305
  import { existsSync as existsSync22, readFileSync as readFileSync25 } from "node:fs";
12207
12306
  import { join as join25 } from "node:path";
12208
12307
 
@@ -12402,7 +12501,7 @@ function realDeps7() {
12402
12501
  };
12403
12502
  }
12404
12503
  function execGit(repoRoot3, args) {
12405
- return execFileSync18("git", args, { cwd: repoRoot3, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
12504
+ return execFileSync19("git", args, { cwd: repoRoot3, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
12406
12505
  }
12407
12506
  function errorDetail(err) {
12408
12507
  const stderr = err.stderr;
@@ -12535,8 +12634,212 @@ async function quickstartCommand(args) {
12535
12634
  process.exit(await runQuickstart(args, realDeps7()));
12536
12635
  }
12537
12636
 
12637
+ // src/commands/release.ts
12638
+ import { execFileSync as execFileSync20 } from "node:child_process";
12639
+ function realDeps8() {
12640
+ const git6 = (args) => {
12641
+ try {
12642
+ return execFileSync20("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
12643
+ } catch {
12644
+ return null;
12645
+ }
12646
+ };
12647
+ return {
12648
+ currentBranch() {
12649
+ const branch = git6(["symbolic-ref", "--quiet", "--short", "HEAD"]);
12650
+ return branch === null || branch === "" ? null : branch;
12651
+ },
12652
+ defaultBranch() {
12653
+ const ref = git6(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
12654
+ if (ref === null || !ref.startsWith("origin/"))
12655
+ return null;
12656
+ const branch = ref.slice("origin/".length);
12657
+ return branch === "" ? null : branch;
12658
+ },
12659
+ fetchOrigin() {
12660
+ try {
12661
+ execFileSync20("git", ["fetch", "origin"], { stdio: "inherit" });
12662
+ return true;
12663
+ } catch {
12664
+ return false;
12665
+ }
12666
+ },
12667
+ treeIsClean() {
12668
+ const status = git6(["status", "--porcelain"]);
12669
+ return status === "";
12670
+ },
12671
+ headSha() {
12672
+ return git6(["rev-parse", "HEAD"]);
12673
+ },
12674
+ defaultBranchSha(defaultBranch) {
12675
+ return git6(["rev-parse", `origin/${defaultBranch}`]);
12676
+ },
12677
+ headSubject() {
12678
+ return git6(["log", "-1", "--format=%s"]) ?? "";
12679
+ },
12680
+ npmWhoami() {
12681
+ try {
12682
+ execFileSync20("npm", ["whoami"], { stdio: "ignore" });
12683
+ return true;
12684
+ } catch {
12685
+ return false;
12686
+ }
12687
+ },
12688
+ runStreamed(cmd, args) {
12689
+ execFileSync20(cmd, args, { stdio: "inherit" });
12690
+ },
12691
+ tagsAtHead() {
12692
+ const tags = git6(["tag", "--points-at", "HEAD"]);
12693
+ if (tags === null || tags === "")
12694
+ return [];
12695
+ return tags.split(`
12696
+ `).filter((t) => t.length > 0);
12697
+ },
12698
+ npmViewVersion(pkg) {
12699
+ try {
12700
+ const out = execFileSync20("npm", ["view", pkg, "version"], {
12701
+ encoding: "utf8",
12702
+ stdio: ["ignore", "pipe", "pipe"]
12703
+ }).trim();
12704
+ return out === "" ? null : out;
12705
+ } catch {
12706
+ return null;
12707
+ }
12708
+ },
12709
+ log(message) {
12710
+ process.stdout.write(`${message}
12711
+ `);
12712
+ }
12713
+ };
12714
+ }
12715
+ var RELEASE_PLAN = [
12716
+ ["bun", "install", "--frozen-lockfile"],
12717
+ ["bun", "run", "build"],
12718
+ ["bun", "run", "changeset:publish"],
12719
+ ["git", "push", "origin", "--tags"]
12720
+ ];
12721
+ function parseTag(tag) {
12722
+ const at = tag.lastIndexOf("@");
12723
+ if (at <= 0)
12724
+ return null;
12725
+ return { pkg: tag.slice(0, at), version: tag.slice(at + 1) };
12726
+ }
12727
+ function runRelease(opts, deps) {
12728
+ const current = deps.currentBranch();
12729
+ const defaultBranch = deps.defaultBranch();
12730
+ if (defaultBranch === null) {
12731
+ return {
12732
+ ok: false,
12733
+ message: "vinaya release: could not determine this repo's default branch (no resolvable `origin/HEAD`) — run `git remote set-head origin -a` first."
12734
+ };
12735
+ }
12736
+ if (current !== defaultBranch) {
12737
+ return {
12738
+ ok: false,
12739
+ message: `vinaya release: HEAD is on \`${current ?? "(detached)"}\`, not the default branch \`${defaultBranch}\` — check out \`${defaultBranch}\` first.`
12740
+ };
12741
+ }
12742
+ if (!deps.treeIsClean()) {
12743
+ return {
12744
+ ok: false,
12745
+ message: "vinaya release: working tree is dirty — commit or stash your changes first."
12746
+ };
12747
+ }
12748
+ if (!deps.fetchOrigin()) {
12749
+ return {
12750
+ ok: false,
12751
+ message: "vinaya release: `git fetch origin` failed — check your network/auth and try again."
12752
+ };
12753
+ }
12754
+ const head = deps.headSha();
12755
+ const originSha = deps.defaultBranchSha(defaultBranch);
12756
+ if (head === null || originSha === null || head !== originSha) {
12757
+ return {
12758
+ ok: false,
12759
+ message: `vinaya release: HEAD does not equal \`origin/${defaultBranch}\` — run \`git pull --ff-only\` first.`
12760
+ };
12761
+ }
12762
+ const subject = deps.headSubject();
12763
+ if (!opts.allowAnyCommit && !subject.startsWith("Chore(release): Version packages")) {
12764
+ return {
12765
+ ok: false,
12766
+ message: `vinaya release: HEAD's commit "${subject}" is not a Version Packages commit — merge the Version Packages PR first, or pass \`--allow-any-commit\`.`
12767
+ };
12768
+ }
12769
+ if (!deps.npmWhoami()) {
12770
+ return {
12771
+ ok: false,
12772
+ message: "vinaya release: `npm whoami` failed — run `npm login` first."
12773
+ };
12774
+ }
12775
+ if (opts.dryRun) {
12776
+ return { ok: true, dryRun: true, plan: RELEASE_PLAN };
12777
+ }
12778
+ const ranSteps = [];
12779
+ let publishedAlready = false;
12780
+ for (const step of RELEASE_PLAN) {
12781
+ const [cmd, ...args] = step;
12782
+ deps.log(`vinaya release: running \`${step.join(" ")}\``);
12783
+ try {
12784
+ deps.runStreamed(cmd, args);
12785
+ } catch (error2) {
12786
+ const reason = error2 instanceof Error ? error2.message : String(error2);
12787
+ if (!publishedAlready) {
12788
+ return { ok: false, message: `vinaya release: \`${step.join(" ")}\` failed — ${reason}` };
12789
+ }
12790
+ const recoveryCommand = RELEASE_PLAN.slice(ranSteps.length).map((s) => s.join(" ")).join(" && ");
12791
+ return {
12792
+ ok: false,
12793
+ message: `vinaya release: \`${step.join(" ")}\` failed after publish already succeeded — packages are on the registry, the release is not finished. Ran: ${ranSteps.map((s) => s.join(" ")).join(", ") || "(nothing)"}. Failed: \`${step.join(" ")}\` — ${reason}. Finish by hand: \`${recoveryCommand}\``,
12794
+ ranSteps: [...ranSteps],
12795
+ failedStep: step,
12796
+ recoveryCommand
12797
+ };
12798
+ }
12799
+ deps.log(`vinaya release: \`${step.join(" ")}\` done`);
12800
+ ranSteps.push(step);
12801
+ if (cmd === "bun" && args.join(" ") === "run changeset:publish")
12802
+ publishedAlready = true;
12803
+ }
12804
+ const published = [];
12805
+ for (const tag of deps.tagsAtHead()) {
12806
+ const parsed = parseTag(tag);
12807
+ if (parsed === null)
12808
+ continue;
12809
+ const registryVersion = deps.npmViewVersion(parsed.pkg);
12810
+ const lagExpected = parsed.pkg === "@attalabs/vinaya" && registryVersion !== parsed.version;
12811
+ published.push({ tag, pkg: parsed.pkg, version: parsed.version, registryVersion, lagExpected });
12812
+ }
12813
+ return { ok: true, dryRun: false, published };
12814
+ }
12815
+ async function releaseCommand(args) {
12816
+ const dryRun = args.includes("--dry-run");
12817
+ const allowAnyCommit = args.includes("--allow-any-commit");
12818
+ const deps = realDeps8();
12819
+ const outcome = runRelease({ dryRun, allowAnyCommit }, deps);
12820
+ if (!outcome.ok) {
12821
+ console.error(outcome.message);
12822
+ process.exit(1);
12823
+ }
12824
+ if (outcome.dryRun) {
12825
+ deps.log("vinaya release --dry-run: preconditions pass. Plan:");
12826
+ for (const step of outcome.plan)
12827
+ deps.log(` ${step.join(" ")}`);
12828
+ process.exit(0);
12829
+ }
12830
+ for (const p of outcome.published) {
12831
+ const registry = p.registryVersion ?? "(not found)";
12832
+ if (p.lagExpected) {
12833
+ deps.log(`${p.tag}: npm view reports \`${registry}\` — registry lag expected (~20 min)`);
12834
+ } else {
12835
+ deps.log(`${p.tag}: npm view reports \`${registry}\``);
12836
+ }
12837
+ }
12838
+ process.exit(0);
12839
+ }
12840
+
12538
12841
  // src/commands/review-post.ts
12539
- import { execFileSync as execFileSync19 } from "node:child_process";
12842
+ import { execFileSync as execFileSync21 } from "node:child_process";
12540
12843
  import { readFileSync as readFileSync26, rmSync as rmSync6, writeFileSync as writeFileSync14 } from "node:fs";
12541
12844
  import { tmpdir as tmpdir4 } from "node:os";
12542
12845
  import { join as join26 } from "node:path";
@@ -12925,7 +13228,7 @@ function normalizeSecurityVerdict(raw) {
12925
13228
  }
12926
13229
  function gh3(args) {
12927
13230
  try {
12928
- return execFileSync19("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
13231
+ return execFileSync21("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
12929
13232
  } catch (err) {
12930
13233
  const stderr = err.stderr;
12931
13234
  throw new Error(String(stderr ?? err.message).trim() || "gh command failed");
@@ -12945,7 +13248,7 @@ function resolveHeadRefName(pr) {
12945
13248
  }
12946
13249
  function shaFromLsRemote(branch) {
12947
13250
  try {
12948
- const out = execFileSync19("git", ["ls-remote", "origin", `refs/heads/${branch}`], {
13251
+ const out = execFileSync21("git", ["ls-remote", "origin", `refs/heads/${branch}`], {
12949
13252
  encoding: "utf8",
12950
13253
  stdio: ["ignore", "pipe", "pipe"]
12951
13254
  }).trim();
@@ -13007,13 +13310,13 @@ function fetchComments(pr) {
13007
13310
  }
13008
13311
  function computeChangedRanges(pr, judgedHead, resolvedHead) {
13009
13312
  try {
13010
- execFileSync19("git", ["fetch", "origin", resolvedHead], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
13313
+ execFileSync21("git", ["fetch", "origin", resolvedHead], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
13011
13314
  } catch (err) {
13012
13315
  refuseCmd(`Could not compute the round-two diff for PR ${pr}: \`git fetch origin ${resolvedHead}\` failed (${err instanceof Error ? err.message : String(err)}) — the PR's current head is not reachable from this worktree's origin remote.`, "Confirm the PR head is pushed to origin and reachable, then re-run.");
13013
13316
  }
13014
13317
  let out;
13015
13318
  try {
13016
- out = execFileSync19("git", ["diff", `${judgedHead}...${resolvedHead}`, "-U0"], {
13319
+ out = execFileSync21("git", ["diff", `${judgedHead}...${resolvedHead}`, "-U0"], {
13017
13320
  encoding: "utf8",
13018
13321
  stdio: ["ignore", "pipe", "pipe"]
13019
13322
  });
@@ -13312,11 +13615,11 @@ Self-verification: clean — re-parsed VERDICT is bound to head ${headSha}.
13312
13615
  }
13313
13616
 
13314
13617
  // src/commands/review-status.ts
13315
- import { execFileSync as execFileSync20 } from "node:child_process";
13618
+ import { execFileSync as execFileSync22 } from "node:child_process";
13316
13619
  var MAX_ROUNDS = 3;
13317
13620
  function fetchPr(prNumber) {
13318
13621
  try {
13319
- const out = execFileSync20("gh", ["pr", "view", prNumber, "--json", "comments,headRefOid,baseRefName"], {
13622
+ const out = execFileSync22("gh", ["pr", "view", prNumber, "--json", "comments,headRefOid,baseRefName"], {
13320
13623
  encoding: "utf8",
13321
13624
  stdio: ["ignore", "pipe", "pipe"]
13322
13625
  });
@@ -13327,7 +13630,7 @@ function fetchPr(prNumber) {
13327
13630
  }
13328
13631
  function behindBy(base) {
13329
13632
  try {
13330
- const out = execFileSync20("git", ["rev-list", "--count", `HEAD..origin/${base}`], {
13633
+ const out = execFileSync22("git", ["rev-list", "--count", `HEAD..origin/${base}`], {
13331
13634
  encoding: "utf8",
13332
13635
  stdio: ["ignore", "pipe", "pipe"]
13333
13636
  }).trim();
@@ -13526,7 +13829,7 @@ async function runStudio(cwd, args, moduleUrl = import.meta.url) {
13526
13829
  // src/commands/upgrade.ts
13527
13830
  import { existsSync as existsSync24, readFileSync as readFileSync28, rmSync as rmSync7, writeFileSync as writeFileSync15 } from "node:fs";
13528
13831
  import { join as join28 } from "node:path";
13529
- function realDeps8() {
13832
+ function realDeps9() {
13530
13833
  return {
13531
13834
  detectRepo: detectGitRepo,
13532
13835
  hookDirFor: resolveHookDir,
@@ -13921,16 +14224,16 @@ Vinaya upgraded.
13921
14224
  return 0;
13922
14225
  }
13923
14226
  async function upgradeCommand(args) {
13924
- process.exit(await runUpgrade(args, realDeps8()));
14227
+ process.exit(await runUpgrade(args, realDeps9()));
13925
14228
  }
13926
14229
 
13927
14230
  // src/commands/waiver.ts
13928
- import { execFileSync as execFileSync21 } from "node:child_process";
14231
+ import { execFileSync as execFileSync23 } from "node:child_process";
13929
14232
  function labelFor(kind) {
13930
14233
  return kind === "docs" ? WAIVER_LABEL : WAIVER_LABEL_REVIEW;
13931
14234
  }
13932
14235
  function gh4(args) {
13933
- return execFileSync21("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: process.env }).trim();
14236
+ return execFileSync23("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: process.env }).trim();
13934
14237
  }
13935
14238
  function detectPrNumber() {
13936
14239
  try {
@@ -14030,8 +14333,8 @@ Reason: ${reason}`];
14030
14333
  }
14031
14334
  process.stdout.write(`Applying \`${label2}\` to PR #${prNumber} under your authenticated \`gh\` identity — vinaya never fabricates an actor.
14032
14335
  `);
14033
- execFileSync21("gh", editArgs, { stdio: "inherit", env: process.env });
14034
- execFileSync21("gh", commentArgs, { stdio: "inherit", env: process.env });
14336
+ execFileSync23("gh", editArgs, { stdio: "inherit", env: process.env });
14337
+ execFileSync23("gh", commentArgs, { stdio: "inherit", env: process.env });
14035
14338
  process.stdout.write(`
14036
14339
  ✓ Label applied and reason posted as a PR comment.
14037
14340
  `);
@@ -14227,6 +14530,10 @@ try {
14227
14530
  await waiverCommand(args);
14228
14531
  break;
14229
14532
  }
14533
+ case "release": {
14534
+ await releaseCommand(args);
14535
+ break;
14536
+ }
14230
14537
  case "quickstart": {
14231
14538
  await quickstartCommand(args);
14232
14539
  break;