@attalabs/vinaya 0.19.3 → 0.20.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 (112) hide show
  1. package/README.md +7 -0
  2. package/aeg-root/contracts/archivist-tranche-archivist.md +2 -2
  3. package/aeg-root/contracts/tranche-archivist-planner.md +8 -8
  4. package/aeg-root/enforcement.md +6 -4
  5. package/aeg-root/process.md +5 -5
  6. package/aeg-root/roles/archivist.md +9 -10
  7. package/aeg-root/roles/brief-author.md +1 -1
  8. package/aeg-root/roles/developer.md +1 -1
  9. package/aeg-root/roles/planner.md +1 -1
  10. package/aeg-root/roles/principal.md +1 -1
  11. package/aeg-root/roles/tranche-archivist.md +3 -16
  12. package/aeg-root/skills/aeg/SKILL.md +30 -1
  13. package/aeg-root/skills/aeg-roles/SKILL.md +1 -1
  14. package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
  15. package/aeg-root/state-machine.md +8 -6
  16. package/aeg-root/task-model.md +1 -1
  17. package/aeg-root/templates/pr-report-template.md +4 -0
  18. package/dist/checks/bin/check-body-bare-digits.js +192 -27
  19. package/dist/checks/bin/check-branch-topology.js +518 -11
  20. package/dist/checks/bin/check-brief-shape.js +104 -11
  21. package/dist/checks/bin/check-closes-n.js +518 -11
  22. package/dist/checks/bin/check-coherence.js +526 -12
  23. package/dist/checks/bin/check-dead-branch-push.js +104 -11
  24. package/dist/checks/bin/check-dispatch-readiness.js +621 -14
  25. package/dist/checks/bin/check-doc-coverage-push.js +170 -20
  26. package/dist/checks/bin/check-doc-coverage.js +170 -20
  27. package/dist/checks/bin/check-doctrine-portability.js +112 -12
  28. package/dist/checks/bin/check-evidence-fresh.js +213 -20
  29. package/dist/checks/bin/check-first-push-dispatch.js +526 -12
  30. package/dist/checks/bin/check-issue-assignment.js +518 -11
  31. package/dist/checks/bin/check-main-branch-refusal.js +3724 -0
  32. package/dist/checks/bin/check-no-disk-state.js +104 -11
  33. package/dist/checks/bin/check-reader-resolvable-prose.js +186 -17
  34. package/dist/checks/bin/check-registry-gates.js +185 -16
  35. package/dist/checks/bin/check-retired-vocabulary.js +185 -16
  36. package/dist/checks/bin/check-review-gate.js +112 -12
  37. package/dist/checks/bin/check-single-plan-pr.js +104 -11
  38. package/dist/checks/bin/check-test-plan.js +104 -11
  39. package/dist/checks/bin/check-workspace-escape.js +104 -11
  40. package/dist/index.js +664 -56
  41. package/package.json +1 -1
  42. package/studio-standalone/_node_modules/@attalabs/aeg-core/bin/verify-coherence.ts +1089 -0
  43. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  44. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  45. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  46. package/studio-standalone/apps/vinaya-studio/web/.next/required-server-files.json +5 -0
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/{[externals]__0pvgf28._.js → [externals]__1agj1ww._.js} +2 -2
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
  85. 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
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__03jepya._.js → [root-of-the-server]__16cuydh._.js} +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  102. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3noqvds-2bvrd.js → 0ea8-8nopz29h.js} +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0la0hhmjprdxm.css +1 -0
  104. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1lk7h-0buf4ab.js +1 -0
  105. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3gehliafvmjo9.js → 2twnwe77d4n-m.js} +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
  107. package/aeg-root/coordination.md +0 -292
  108. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/2iysedx-n06xc.js +0 -1
  109. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/42wxpzjhw7oy4.css +0 -1
  110. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_buildManifest.js +0 -0
  111. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_clientMiddlewareManifest.js +0 -0
  112. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync27 } from "node:fs";
4
+ import { readFileSync as readFileSync28 } from "node:fs";
5
5
  import { dirname as dirname9, join as join27 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7
7
 
@@ -182,6 +182,13 @@ var LABELS = [
182
182
  form: "literal",
183
183
  carries: "Execution is halted pending an external unblock; wins over every other derived status."
184
184
  },
185
+ {
186
+ key: "backlog",
187
+ id: "vinaya/backlog",
188
+ category: "state",
189
+ form: "literal",
190
+ carries: "The Issue is deliberately unplanned — belongs to no tranche and is excluded from dispatch, until a Planner promotes it."
191
+ },
185
192
  {
186
193
  key: "tier-0",
187
194
  id: "vinaya/tier:0",
@@ -724,6 +731,141 @@ var DERIVED_STATUSES = [
724
731
  "incoherent"
725
732
  ];
726
733
  var DERIVABLE_STATUSES = DERIVED_STATUSES.filter((s) => s !== "backlog");
734
+ // ../../packages/aeg-core/src/report-tokens.ts
735
+ function formatTokensLine(input) {
736
+ const model = input.modelOverride ?? input.summary?.model ?? "—";
737
+ if (!input.summary) {
738
+ return `Tokens: ${input.phase} — ${input.role} — ${model} — —`;
739
+ }
740
+ const { inputTokens, cacheCreationInputTokens, cacheReadInputTokens, outputTokens } = input.summary.components;
741
+ const tokensIn = inputTokens + cacheCreationInputTokens + cacheReadInputTokens;
742
+ return `Tokens: ${input.phase} — ${input.role} — ${model} — ${tokensIn}/${outputTokens}/—`;
743
+ }
744
+ function formatBreakdown(summary) {
745
+ const { inputTokens, cacheCreationInputTokens, cacheReadInputTokens, outputTokens } = summary.components;
746
+ return [
747
+ ` fresh input tokens: ${inputTokens}`,
748
+ ` cache creation tokens: ${cacheCreationInputTokens}`,
749
+ ` cache read tokens: ${cacheReadInputTokens}`,
750
+ ` output tokens: ${outputTokens}`,
751
+ ` model: ${summary.model ?? "—"}`,
752
+ ` messages summed: ${summary.messageCount}`
753
+ ].join(`
754
+ `);
755
+ }
756
+ // ../../packages/aeg-core/src/claude-code-transcript.ts
757
+ function summarizeTranscript(jsonl) {
758
+ const seen = new Set;
759
+ const components = {
760
+ inputTokens: 0,
761
+ outputTokens: 0,
762
+ cacheCreationInputTokens: 0,
763
+ cacheReadInputTokens: 0
764
+ };
765
+ let model = null;
766
+ let messageCount = 0;
767
+ for (const rawLine of jsonl.split(/\r?\n/)) {
768
+ const line = rawLine.trim();
769
+ if (!line)
770
+ continue;
771
+ let entry2;
772
+ try {
773
+ entry2 = JSON.parse(line);
774
+ } catch {
775
+ continue;
776
+ }
777
+ if (!entry2 || typeof entry2 !== "object")
778
+ continue;
779
+ const obj = entry2;
780
+ if (obj.type !== "assistant")
781
+ continue;
782
+ const message = obj.message;
783
+ const id = message?.id;
784
+ const usage = message?.usage;
785
+ if (typeof id !== "string" || !id || !usage || seen.has(id))
786
+ continue;
787
+ seen.add(id);
788
+ messageCount++;
789
+ components.inputTokens += numberOr(usage.input_tokens, 0);
790
+ components.outputTokens += numberOr(usage.output_tokens, 0);
791
+ components.cacheCreationInputTokens += numberOr(usage.cache_creation_input_tokens, 0);
792
+ components.cacheReadInputTokens += numberOr(usage.cache_read_input_tokens, 0);
793
+ const messageModel = message?.model;
794
+ if (typeof messageModel === "string" && messageModel)
795
+ model = messageModel;
796
+ }
797
+ return { components, model, messageCount };
798
+ }
799
+ function numberOr(value, fallback) {
800
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
801
+ }
802
+ function sanitizeKey(value) {
803
+ return value.replace(/[^A-Za-z0-9]+/g, "-");
804
+ }
805
+ function transcriptPointerPath(projectDir, tmpDir) {
806
+ return `${tmpDir}/claude-transcript-${sanitizeKey(projectDir)}.txt`;
807
+ }
808
+ function resolvePointer(explicitTranscriptPath, deps) {
809
+ if (explicitTranscriptPath)
810
+ return { path: explicitTranscriptPath };
811
+ const projectDir = deps.env.CLAUDE_PROJECT_DIR ?? deps.cwd;
812
+ const tmpDir = deps.env.TMPDIR ?? "/tmp";
813
+ const pointerPath = transcriptPointerPath(projectDir, tmpDir);
814
+ if (!deps.exists(pointerPath)) {
815
+ return {
816
+ error: `No transcript pointer at ${pointerPath} and no --transcript given. ` + "Either this repo installs no track-transcript.sh Stop hook (lacking one is not a defect — " + "name the transcript directly instead), or no session has completed a turn yet."
817
+ };
818
+ }
819
+ let contents;
820
+ try {
821
+ contents = deps.readFile(pointerPath).trim();
822
+ } catch (err) {
823
+ return { error: `Transcript pointer at ${pointerPath} could not be read: ${err.message}` };
824
+ }
825
+ const [pointerSessionId, transcriptPath] = contents.split("\t");
826
+ if (!transcriptPath) {
827
+ return { error: `Transcript pointer file ${pointerPath} is malformed: "${contents}"` };
828
+ }
829
+ const currentSessionId = deps.env.CLAUDE_CODE_SESSION_ID;
830
+ if (currentSessionId && pointerSessionId && currentSessionId !== pointerSessionId) {
831
+ return {
832
+ error: `Transcript pointer at ${pointerPath} is stale: written for session ${pointerSessionId}, ` + `but this session is ${currentSessionId}. Name your own transcript with --transcript instead of ` + "reporting another session's figures as yours."
833
+ };
834
+ }
835
+ return { path: transcriptPath };
836
+ }
837
+ function resolveMeteringCapability(deps, explicitTranscriptPath) {
838
+ const resolved = resolvePointer(explicitTranscriptPath, deps);
839
+ if ("error" in resolved) {
840
+ return { capable: false, reason: "no-transcript-resolved", detail: resolved.error };
841
+ }
842
+ if (!deps.exists(resolved.path)) {
843
+ return {
844
+ capable: false,
845
+ reason: "transcript-unreadable",
846
+ detail: `Resolved transcript path ${resolved.path} does not exist.`
847
+ };
848
+ }
849
+ let jsonl;
850
+ try {
851
+ jsonl = deps.readFile(resolved.path);
852
+ } catch (err) {
853
+ return {
854
+ capable: false,
855
+ reason: "transcript-unreadable",
856
+ detail: `Transcript at ${resolved.path} could not be read: ${err.message}`
857
+ };
858
+ }
859
+ const summary = summarizeTranscript(jsonl);
860
+ if (summary.messageCount === 0) {
861
+ return {
862
+ capable: false,
863
+ reason: "transcript-empty",
864
+ detail: `Transcript at ${resolved.path} yielded zero assistant messages with usage data — ` + "it's empty, unparseable, or not yet flushed to disk."
865
+ };
866
+ }
867
+ return { capable: true, transcriptPath: resolved.path, summary };
868
+ }
727
869
  // ../../packages/aeg-core/src/file-classify.ts
728
870
  function isCodeFile(p) {
729
871
  if (p.endsWith(".md"))
@@ -1368,6 +1510,14 @@ function checkDirectMainPush(fact) {
1368
1510
  return { verdict: "legitimate", mergedPrNumber: first };
1369
1511
  return { verdict: "direct-push", sha: fact.sha };
1370
1512
  }
1513
+ // ../../packages/aeg-core/src/ensure-label.ts
1514
+ var LABEL_COLOR = "B60205";
1515
+ function ensureLabelExists(repoFlag, name, description, io) {
1516
+ const existing = io.listLabelNames(repoFlag);
1517
+ if (existing.includes(name))
1518
+ return;
1519
+ io.createLabel(repoFlag, name, description, LABEL_COLOR);
1520
+ }
1371
1521
  // ../../packages/aeg-core/src/dead-branch-push-audit.ts
1372
1522
  function findDeadBranchPushes(facts) {
1373
1523
  return facts.filter((f) => new Date(f.latestCommitAt).getTime() > new Date(f.resolvedAt).getTime()).map(({ branch, prNumber, prState, resolvedAt, latestCommitAt }) => ({
@@ -1634,6 +1784,91 @@ function buildClaudeCommandOps() {
1634
1784
  ];
1635
1785
  }
1636
1786
 
1787
+ // src/lib/claude-stop-hook-emitter.ts
1788
+ var CLAUDE_STOP_HOOK_GROUP = "Claude Code Stop hook (transcript pointer)";
1789
+ var CLAUDE_STOP_HOOK_MARKER = "track-transcript";
1790
+ var CLAUDE_STOP_HOOK_SCRIPT_PATH = ".claude/hooks/track-transcript.sh";
1791
+ var CLAUDE_SETTINGS_PATH = ".claude/settings.json";
1792
+ var SH_PREAMBLE = `#!/usr/bin/env sh
1793
+ `;
1794
+ function renderTrackTranscriptScriptBody() {
1795
+ return `# Vinaya-managed Stop hook. Records this session's transcript_path (and
1796
+ # session_id) so the token-report adapter can resolve it without scanning
1797
+ # ~/.claude/projects/ for the newest file, which silently grabs another
1798
+ # concurrent session's transcript when two worktrees are active at once.
1799
+ node -e '
1800
+ const fs = require("fs")
1801
+ let data = ""
1802
+ process.stdin.on("data", (c) => { data += c })
1803
+ process.stdin.on("end", () => {
1804
+ let hook
1805
+ try {
1806
+ hook = JSON.parse(data)
1807
+ } catch {
1808
+ return
1809
+ }
1810
+ if (!hook.transcript_path) return
1811
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd()
1812
+ const tmpDir = process.env.TMPDIR || "/tmp"
1813
+ const key = projectDir.replace(/[^A-Za-z0-9]+/g, "-")
1814
+ const pointerPath = tmpDir + "/claude-transcript-" + key + ".txt"
1815
+ const content = (hook.session_id || "") + "\\t" + hook.transcript_path + "\\n"
1816
+ const scratchPath = pointerPath + "." + process.pid + "." + Math.random().toString(36).slice(2) + ".tmp"
1817
+ try {
1818
+ fs.writeFileSync(scratchPath, content, { mode: 0o600, flag: "wx" })
1819
+ } catch {
1820
+ return
1821
+ }
1822
+ try {
1823
+ fs.renameSync(scratchPath, pointerPath)
1824
+ } catch {
1825
+ try { fs.unlinkSync(scratchPath) } catch {}
1826
+ }
1827
+ })
1828
+ '
1829
+ exit 0`;
1830
+ }
1831
+ function renderClaudeSettingsWithStopHook() {
1832
+ const settings = {
1833
+ $schema: "https://json.schemastore.org/claude-code-settings.json",
1834
+ hooks: {
1835
+ Stop: [
1836
+ {
1837
+ hooks: [
1838
+ {
1839
+ type: "command",
1840
+ command: `$CLAUDE_PROJECT_DIR/${CLAUDE_STOP_HOOK_SCRIPT_PATH}`,
1841
+ timeout: 5
1842
+ }
1843
+ ]
1844
+ }
1845
+ ]
1846
+ }
1847
+ };
1848
+ return `${JSON.stringify(settings, null, 2)}
1849
+ `;
1850
+ }
1851
+ function buildClaudeStopHookOps() {
1852
+ return [
1853
+ {
1854
+ kind: "managed-block",
1855
+ path: CLAUDE_STOP_HOOK_SCRIPT_PATH,
1856
+ marker: CLAUDE_STOP_HOOK_MARKER,
1857
+ body: renderTrackTranscriptScriptBody(),
1858
+ comment: "hash",
1859
+ hostPreamble: SH_PREAMBLE,
1860
+ mode: 493,
1861
+ group: CLAUDE_STOP_HOOK_GROUP
1862
+ },
1863
+ {
1864
+ kind: "create-file",
1865
+ path: CLAUDE_SETTINGS_PATH,
1866
+ content: renderClaudeSettingsWithStopHook(),
1867
+ group: CLAUDE_STOP_HOOK_GROUP
1868
+ }
1869
+ ];
1870
+ }
1871
+
1637
1872
  // src/lib/gemini-command-emitter.ts
1638
1873
  var GEMINI_COMMAND_GROUP = "Gemini CLI command (.gemini/commands/)";
1639
1874
  var GEMINI_COMMAND_PATH = ".gemini/commands/vinaya.toml";
@@ -2396,6 +2631,7 @@ function starterDocOwners() {
2396
2631
  }
2397
2632
  var FIXED_LABEL_METADATA = {
2398
2633
  blocked: { color: "e11d21", description: "Execution halted pending an external unblock" },
2634
+ backlog: { color: "ededed", description: "Deliberately unplanned — belongs to no tranche until promoted" },
2399
2635
  "tier-0": { color: "ededed", description: "Trivial / mechanical change" },
2400
2636
  "tier-1": { color: "c5def5", description: "Standard task — code + tests + docs" },
2401
2637
  "tier-3": { color: "d93f0b", description: "Records a decision; ratification-gated" },
@@ -2451,6 +2687,14 @@ var CODEOWNERS_NOTE = `Recommended: require review on vinaya's own workflow file
2451
2687
  Then add this flag to the branch-protection command above:
2452
2688
 
2453
2689
  -F required_pull_request_reviews.require_code_owner_reviews=true`;
2690
+ var PRINCIPALS_NOTE = `Recommended: without this, review-gate trusts nobody's verdicts on this repo.
2691
+
2692
+ Add to vinaya.config.json:
2693
+ "principals": ["<your-github-login>"]
2694
+
2695
+ Anyone reviewing a PR here (code-reviewer/security verdicts, the
2696
+ vinaya/waiver:review label) must be listed, or their comments are ignored
2697
+ and every PR reads as unreviewed. \`vinaya doctor\` reports when this is unset.`;
2454
2698
  function buildInitOps(ctx) {
2455
2699
  const ops = [];
2456
2700
  const hookMode = 493;
@@ -2563,13 +2807,31 @@ function buildInitOps(ctx) {
2563
2807
  }
2564
2808
  if (ctx.agents.has("claude")) {
2565
2809
  ops.push(...buildClaudeCommandOps());
2810
+ ops.push(...buildClaudeStopHookOps());
2566
2811
  }
2567
2812
  if (ctx.agents.has("gemini")) {
2568
2813
  ops.push(buildGeminiCommandOp());
2569
2814
  }
2815
+ if (ctx.agents.size > 0) {
2816
+ const affected = [];
2817
+ if (ctx.agents.has("claude"))
2818
+ affected.push("the `/vinaya <role>` command (.claude/commands/vinaya.md)");
2819
+ if (ctx.agents.has("gemini"))
2820
+ affected.push("the `/vinaya` command (.gemini/commands/vinaya.toml)");
2821
+ if (ctx.agents.has("skills"))
2822
+ affected.push("every .agents/skills/vinaya-*/SKILL.md file");
2823
+ ops.push({
2824
+ kind: "print",
2825
+ message: "The agent-native commands above (`vinaya doctrine`) need `vinaya` resolvable on PATH:\n\n" + ` npm install -g @attalabs/vinaya
2826
+
2827
+ ` + `Without a global install, ${affected.join(" and ")} fail "command not found" the first time an agent ` + "tries to use them.",
2828
+ group: "Agent-native commands (printed, never applied)"
2829
+ });
2830
+ }
2570
2831
  ops.push(...labelOps());
2571
2832
  ops.push({ kind: "print", message: BRANCH_PROTECTION_NOTE, group: "Branch protection (printed, never applied)" });
2572
2833
  ops.push({ kind: "print", message: CODEOWNERS_NOTE, group: "Branch protection (printed, never applied)" });
2834
+ ops.push({ kind: "print", message: PRINCIPALS_NOTE, group: "Review trust (printed, never applied)" });
2573
2835
  return ops;
2574
2836
  }
2575
2837
 
@@ -2703,8 +2965,15 @@ function isSafeRepoRelPath(p) {
2703
2965
  var SafeRepoRelPath = z.string().refine(isSafeRepoRelPath, {
2704
2966
  message: "must be a repo-root-relative path with no `..` segment or absolute root"
2705
2967
  });
2968
+ var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
2969
+ function isCanonicalHookBlockPath(p) {
2970
+ return CANONICAL_HOOK_BLOCK_PREFIXES.some((prefix) => p.startsWith(prefix) && p.length > prefix.length);
2971
+ }
2972
+ var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
2973
+ message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
2974
+ });
2706
2975
  var ManagedBlockRecordSchema = z.object({
2707
- path: SafeRepoRelPath,
2976
+ path: ManagedHookBlockPath,
2708
2977
  marker: z.string(),
2709
2978
  comment: z.enum(["hash", "html"])
2710
2979
  });
@@ -3828,11 +4097,15 @@ function gatherDeadBranchFacts(repoFlag) {
3828
4097
  }
3829
4098
  return facts;
3830
4099
  }
3831
- function ensureLabelExists(repoFlag, name, description) {
4100
+ function listLabelNames(repoFlag) {
3832
4101
  const existing = shSoftJson(["gh", "label", "list", "-R", repoFlag, "--json", "name", "--limit", "200"]) ?? [];
3833
- if (existing.some((l) => l.name === name))
3834
- return;
3835
- shSoft(["gh", "label", "create", name, "-R", repoFlag, "--color", "B60205", "--description", description]);
4102
+ return existing.map((l) => l.name);
4103
+ }
4104
+ function createLabel(repoFlag, name, description, color) {
4105
+ shSoft(["gh", "label", "create", name, "-R", repoFlag, "--color", color, "--description", description]);
4106
+ }
4107
+ function ensureLabelExists2(repoFlag, name, description) {
4108
+ ensureLabelExists(repoFlag, name, description, { listLabelNames, createLabel });
3836
4109
  }
3837
4110
  function commentIdFromUrl(url) {
3838
4111
  const match = url.match(/issuecomment-(\d+)/);
@@ -3870,7 +4143,7 @@ function runDeadBranchAudit(repoFlag) {
3870
4143
  const facts = gatherDeadBranchFacts(repoFlag);
3871
4144
  const findings = findDeadBranchPushes(facts);
3872
4145
  if (findings.length > 0) {
3873
- ensureLabelExists(repoFlag, DEAD_BRANCH_LABEL, DEAD_BRANCH_LABEL_DESCRIPTION);
4146
+ ensureLabelExists2(repoFlag, DEAD_BRANCH_LABEL, DEAD_BRANCH_LABEL_DESCRIPTION);
3874
4147
  for (const finding of findings)
3875
4148
  flagDeadBranchPush(finding, repoFlag);
3876
4149
  }
@@ -3918,7 +4191,7 @@ function incidentAlreadyOpen(sha, repoFlag) {
3918
4191
  return issues.length > 0;
3919
4192
  }
3920
4193
  function openDirectPushIncident(sha, repoFlag) {
3921
- ensureLabelExists(repoFlag, DIRECT_PUSH_LABEL, DIRECT_PUSH_LABEL_DESCRIPTION);
4194
+ ensureLabelExists2(repoFlag, DIRECT_PUSH_LABEL, DIRECT_PUSH_LABEL_DESCRIPTION);
3922
4195
  if (incidentAlreadyOpen(sha, repoFlag)) {
3923
4196
  console.error(`[vinaya audit] incident already open for ${sha} — not duplicating.`);
3924
4197
  return;
@@ -4051,8 +4324,8 @@ function bin(name) {
4051
4324
  function runsUnderAll(spec) {
4052
4325
  return !spec.ownWorkflow;
4053
4326
  }
4054
- function coreCheckRegistry() {
4055
- return [
4327
+ var REGISTRY = [
4328
+ [
4056
4329
  {
4057
4330
  name: "brief-shape",
4058
4331
  run: bin("check-brief-shape"),
@@ -4063,6 +4336,9 @@ function coreCheckRegistry() {
4063
4336
  BRANCH: { optional: true }
4064
4337
  }
4065
4338
  },
4339
+ 0
4340
+ ],
4341
+ [
4066
4342
  {
4067
4343
  name: "doc-coverage",
4068
4344
  run: bin("check-doc-coverage"),
@@ -4078,6 +4354,9 @@ function coreCheckRegistry() {
4078
4354
  GH_TOKEN: { optional: true }
4079
4355
  }
4080
4356
  },
4357
+ 0
4358
+ ],
4359
+ [
4081
4360
  {
4082
4361
  name: "coherence",
4083
4362
  run: bin("check-coherence"),
@@ -4090,6 +4369,9 @@ function coreCheckRegistry() {
4090
4369
  GH_TOKEN: { optional: true }
4091
4370
  }
4092
4371
  },
4372
+ 0
4373
+ ],
4374
+ [
4093
4375
  {
4094
4376
  name: "dispatch-readiness",
4095
4377
  run: bin("check-dispatch-readiness"),
@@ -4099,9 +4381,13 @@ function coreCheckRegistry() {
4099
4381
  AEG_REPO: { optional: true },
4100
4382
  BRANCH: { optional: true },
4101
4383
  GITHUB_TOKEN: { optional: true },
4102
- GH_TOKEN: { optional: true }
4384
+ GH_TOKEN: { optional: true },
4385
+ PREMISE_FILE: { optional: true }
4103
4386
  }
4104
4387
  },
4388
+ 0
4389
+ ],
4390
+ [
4105
4391
  {
4106
4392
  name: "closes-n",
4107
4393
  run: bin("check-closes-n"),
@@ -4116,6 +4402,9 @@ function coreCheckRegistry() {
4116
4402
  GH_TOKEN: { optional: true }
4117
4403
  }
4118
4404
  },
4405
+ 1
4406
+ ],
4407
+ [
4119
4408
  {
4120
4409
  name: "single-plan-pr",
4121
4410
  run: bin("check-single-plan-pr"),
@@ -4128,6 +4417,9 @@ function coreCheckRegistry() {
4128
4417
  GH_TOKEN: { optional: true }
4129
4418
  }
4130
4419
  },
4420
+ 0
4421
+ ],
4422
+ [
4131
4423
  {
4132
4424
  name: "test-plan",
4133
4425
  run: bin("check-test-plan"),
@@ -4139,6 +4431,9 @@ function coreCheckRegistry() {
4139
4431
  BRANCH: { optional: true }
4140
4432
  }
4141
4433
  },
4434
+ 1
4435
+ ],
4436
+ [
4142
4437
  {
4143
4438
  name: "body-bare-digits",
4144
4439
  run: bin("check-body-bare-digits"),
@@ -4154,6 +4449,9 @@ function coreCheckRegistry() {
4154
4449
  GH_TOKEN: { optional: true }
4155
4450
  }
4156
4451
  },
4452
+ 1
4453
+ ],
4454
+ [
4157
4455
  {
4158
4456
  name: "no-disk-state",
4159
4457
  run: bin("check-no-disk-state"),
@@ -4161,6 +4459,9 @@ function coreCheckRegistry() {
4161
4459
  timeoutMs: 15000,
4162
4460
  env: { BASE_SHA: { optional: true } }
4163
4461
  },
4462
+ 0
4463
+ ],
4464
+ [
4164
4465
  {
4165
4466
  name: "registry-gates",
4166
4467
  run: bin("check-registry-gates"),
@@ -4172,6 +4473,9 @@ function coreCheckRegistry() {
4172
4473
  GH_TOKEN: { optional: true }
4173
4474
  }
4174
4475
  },
4476
+ 0
4477
+ ],
4478
+ [
4175
4479
  {
4176
4480
  name: "review-gate",
4177
4481
  run: bin("check-review-gate"),
@@ -4185,6 +4489,9 @@ function coreCheckRegistry() {
4185
4489
  GH_TOKEN: { optional: true }
4186
4490
  }
4187
4491
  },
4492
+ 1
4493
+ ],
4494
+ [
4188
4495
  {
4189
4496
  name: "branch-topology",
4190
4497
  run: bin("check-branch-topology"),
@@ -4197,6 +4504,9 @@ function coreCheckRegistry() {
4197
4504
  GH_TOKEN: { optional: true }
4198
4505
  }
4199
4506
  },
4507
+ 0
4508
+ ],
4509
+ [
4200
4510
  {
4201
4511
  name: "dead-branch-push",
4202
4512
  run: bin("check-dead-branch-push"),
@@ -4208,6 +4518,9 @@ function coreCheckRegistry() {
4208
4518
  GH_TOKEN: { optional: true }
4209
4519
  }
4210
4520
  },
4521
+ 0
4522
+ ],
4523
+ [
4211
4524
  {
4212
4525
  name: "first-push-dispatch",
4213
4526
  run: bin("check-first-push-dispatch"),
@@ -4220,6 +4533,9 @@ function coreCheckRegistry() {
4220
4533
  GH_TOKEN: { optional: true }
4221
4534
  }
4222
4535
  },
4536
+ 0
4537
+ ],
4538
+ [
4223
4539
  {
4224
4540
  name: "doc-coverage-push",
4225
4541
  run: bin("check-doc-coverage-push"),
@@ -4237,6 +4553,9 @@ function coreCheckRegistry() {
4237
4553
  GH_TOKEN: { optional: true }
4238
4554
  }
4239
4555
  },
4556
+ 0
4557
+ ],
4558
+ [
4240
4559
  {
4241
4560
  name: "issue-assignment",
4242
4561
  run: bin("check-issue-assignment"),
@@ -4249,6 +4568,9 @@ function coreCheckRegistry() {
4249
4568
  GH_TOKEN: { optional: true }
4250
4569
  }
4251
4570
  },
4571
+ 0
4572
+ ],
4573
+ [
4252
4574
  {
4253
4575
  name: "evidence-fresh",
4254
4576
  run: bin("check-evidence-fresh"),
@@ -4263,6 +4585,9 @@ function coreCheckRegistry() {
4263
4585
  GH_TOKEN: { optional: true }
4264
4586
  }
4265
4587
  },
4588
+ 1
4589
+ ],
4590
+ [
4266
4591
  {
4267
4592
  name: "reader-resolvable-prose",
4268
4593
  run: bin("check-reader-resolvable-prose"),
@@ -4270,6 +4595,9 @@ function coreCheckRegistry() {
4270
4595
  timeoutMs: 30000,
4271
4596
  env: {}
4272
4597
  },
4598
+ 0
4599
+ ],
4600
+ [
4273
4601
  {
4274
4602
  name: "retired-vocabulary",
4275
4603
  run: bin("check-retired-vocabulary"),
@@ -4277,6 +4605,9 @@ function coreCheckRegistry() {
4277
4605
  timeoutMs: 30000,
4278
4606
  env: {}
4279
4607
  },
4608
+ 0
4609
+ ],
4610
+ [
4280
4611
  {
4281
4612
  name: "doctrine-portability",
4282
4613
  run: bin("check-doctrine-portability"),
@@ -4284,15 +4615,33 @@ function coreCheckRegistry() {
4284
4615
  timeoutMs: 30000,
4285
4616
  env: { BASE_SHA: { optional: true } }
4286
4617
  },
4618
+ 0
4619
+ ],
4620
+ [
4287
4621
  {
4288
4622
  name: "workspace-escape",
4289
4623
  run: bin("check-workspace-escape"),
4290
4624
  scope: "full",
4291
4625
  timeoutMs: 30000,
4292
4626
  env: {}
4293
- }
4294
- ];
4627
+ },
4628
+ 0
4629
+ ],
4630
+ [
4631
+ {
4632
+ name: "main-branch-refusal",
4633
+ run: bin("check-main-branch-refusal"),
4634
+ scope: "full",
4635
+ timeoutMs: 15000,
4636
+ env: {}
4637
+ },
4638
+ 0
4639
+ ]
4640
+ ];
4641
+ function coreCheckRegistry() {
4642
+ return REGISTRY.map(([spec]) => spec);
4295
4643
  }
4644
+ var CORE_CHECK_RING = Object.fromEntries(REGISTRY.map(([spec, ring]) => [spec.name, ring]));
4296
4645
 
4297
4646
  // src/checks/resolver.ts
4298
4647
  var NAMESPACE_SEGMENT = /^[a-z0-9][a-z0-9-]*$/;
@@ -4889,6 +5238,28 @@ var COMMANDS = [
4889
5238
  ],
4890
5239
  status: "shipped"
4891
5240
  },
5241
+ {
5242
+ name: "tokens",
5243
+ description: "Print a role's `Tokens: …` report line — the portable front door over the collection adapter",
5244
+ flags: [
5245
+ { flag: "--phase", description: 'e.g. `"<task-id>: develop"` — required' },
5246
+ { flag: "--role", description: "e.g. `Developer` — required" },
5247
+ { flag: "--model", description: "Overrides the model id the adapter derived, if given" },
5248
+ {
5249
+ flag: "--transcript",
5250
+ description: "Read this transcript directly. Supported primary route — use it whenever you know which transcript " + "is yours, and always in a repo with no track-transcript.sh hook. Omitted resolves via the Stop-hook " + "pointer file, if this repo installs that hook."
5251
+ },
5252
+ {
5253
+ flag: "--in / --out",
5254
+ description: "Manual entry: the exact token figures, for a host whose usage arrives by some other means than a " + "Claude Code transcript. Both required together; skips transcript resolution entirely."
5255
+ }
5256
+ ],
5257
+ details: [
5258
+ "Resolves the Claude Code collection adapter (`resolveMeteringCapability`, `summarizeTranscript`, `formatTokensLine`) from the INSTALLED `@attalabs/aeg-core` package, never by a repo-relative path — the fix for `packages/aeg-core/bin/report-tokens.ts` not existing in an adopter repo with no local `packages/`.",
5259
+ "Refuses — never emits a `0/0/—` line — when no transcript resolves, a resolved transcript can't be read, or it summarizes to zero usage records (empty, unparseable, or not yet flushed to disk). Capability is probed by actually attempting resolution, never declared from the host being Claude Code."
5260
+ ],
5261
+ status: "shipped"
5262
+ },
4892
5263
  {
4893
5264
  name: "doctrine",
4894
5265
  description: "Print the absolute path of the bundled doctrine's front door (aeg-root/skills/aeg/SKILL.md) on this machine",
@@ -5715,7 +6086,7 @@ async function demoBreakCommand(args) {
5715
6086
  // src/commands/doctor.ts
5716
6087
  import { execFileSync as execFileSync8 } from "node:child_process";
5717
6088
  import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync2 } from "node:fs";
5718
- import { join as join13 } from "node:path";
6089
+ import { delimiter, join as join13 } from "node:path";
5719
6090
 
5720
6091
  // src/lib/self-host.ts
5721
6092
  import { readdirSync as readdirSync5, readFileSync as readFileSync11, realpathSync } from "node:fs";
@@ -5870,7 +6241,13 @@ function realDeps3() {
5870
6241
  readHooksPath: readCoreHooksPath,
5871
6242
  nodeVersion: () => process.version,
5872
6243
  bunVersion: () => typeof Bun === "undefined" ? null : Bun.version,
5873
- packageVersion: readVersion
6244
+ packageVersion: readVersion,
6245
+ meteringCapability: () => resolveMeteringCapability({
6246
+ env: process.env,
6247
+ cwd: process.cwd(),
6248
+ exists: existsSync11,
6249
+ readFile: (path) => readFileSync13(path, "utf8")
6250
+ })
5874
6251
  };
5875
6252
  }
5876
6253
  var ok = (check, message) => ({ check, severity: "ok", message });
@@ -5905,6 +6282,8 @@ function labelForPath(path) {
5905
6282
  return "agent-skills";
5906
6283
  if (path === CLAUDE_COMMAND_PATH)
5907
6284
  return "claude-command";
6285
+ if (path === CLAUDE_SETTINGS_PATH)
6286
+ return "claude-stop-hook";
5908
6287
  if (path === GEMINI_COMMAND_PATH)
5909
6288
  return "gemini-command";
5910
6289
  if (path === CHECKS_FOLDER_PLACEHOLDER_PATH || path === ROLES_FOLDER_PLACEHOLDER_PATH)
@@ -6150,6 +6529,14 @@ function diagnoseGlobalConfigChecks() {
6150
6529
  return [];
6151
6530
  return [warn("checks", globalChecksIgnoredWarning(GLOBAL_CONFIG_PATH))];
6152
6531
  }
6532
+ function diagnoseTokenMetering(deps) {
6533
+ const capability = deps.meteringCapability();
6534
+ if (capability.capable)
6535
+ return [];
6536
+ return [
6537
+ info("tokens", `Token-metering capability probe: incapable (${capability.reason}) — ${capability.detail} Real per-turn ` + "token figures cannot currently be collected via `vinaya tokens`. Report them by whatever means your " + "host offers, or supply them directly with `vinaya tokens --in <n> --out <n>`.")
6538
+ ];
6539
+ }
6153
6540
  async function diagnoseEnvironment(deps, hasDrift) {
6154
6541
  const findings = [];
6155
6542
  const auth = await deps.ghAuthStatus();
@@ -6194,6 +6581,22 @@ function diagnoseCodeowners(repoRoot) {
6194
6581
  `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).some((line) => coversWorkflowsDir(line.split(/\s+/)[0] ?? ""));
6195
6582
  return covered ? info("codeowners", ".github/CODEOWNERS covers .github/workflows/**.") : warn("codeowners", ".github/CODEOWNERS exists but has no entry covering .github/workflows/** — workflow file edits can merge unreviewed.");
6196
6583
  }
6584
+ function diagnoseVinayaOnPath(agents) {
6585
+ if (agents.size === 0)
6586
+ return [];
6587
+ const names = process.platform === "win32" ? ["vinaya.cmd", "vinaya.exe", "vinaya.bat"] : ["vinaya"];
6588
+ const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
6589
+ const onPath = dirs.some((dir) => names.some((name) => existsSync11(join13(dir, name))));
6590
+ return [
6591
+ onPath ? ok("vinaya-on-path", "`vinaya` resolves on PATH — agent-native commands will work.") : info("vinaya-on-path", "`vinaya` is not resolvable on PATH — `/vinaya <role>` and the .agents/skills/vinaya-*/SKILL.md files " + 'installed here will fail "command not found" on first use. Run `npm install -g @attalabs/vinaya`.')
6592
+ ];
6593
+ }
6594
+ function diagnosePrincipals(config) {
6595
+ if (config.principals && config.principals.length > 0) {
6596
+ return info("principals", `review-gate trusts ${config.principals.length} declared principal(s).`);
6597
+ }
6598
+ return info("principals", 'vinaya.config.json has no "principals" — review-gate and the waiver-label actor check both fall back to a ' + "hardcoded placeholder allowlist that will not include anyone on this repo. Every reviewer's verdict is " + 'silently ignored (DANGLING) until you add `"principals": ["<your-github-login>", ...]` to vinaya.config.json.');
6599
+ }
6197
6600
  var TEST_INVOCATION_SUBSTRINGS = ["npm test", "npm run test", "bun test", "bunx turbo test"];
6198
6601
  function diagnoseTestCi(repoRoot) {
6199
6602
  let pkg;
@@ -6272,12 +6675,15 @@ async function runDoctor(args, deps) {
6272
6675
  findings.push(...await diagnoseHookRouting(repo.repoRoot, hookDir, deps.readHooksPath));
6273
6676
  findings.push(...diagnoseCustomChecks(repo.repoRoot, configRead.config));
6274
6677
  findings.push(...diagnoseDocOwnersHealth(repo.repoRoot));
6678
+ findings.push(diagnosePrincipals(configRead.config));
6679
+ findings.push(...diagnoseVinayaOnPath(ctx.agents));
6275
6680
  }
6276
6681
  findings.push(...diagnoseBlastRadiusDeprecation(repo.repoRoot, configRead.kind === "ok" ? configRead.config : null));
6277
6682
  findings.push(...diagnoseBriefSchemaDrift(configRead.kind === "ok" ? configRead.config : null));
6278
6683
  findings.push(...diagnoseEnvDeclarations(repo.repoRoot, configRead.kind === "ok" ? configRead.config : null));
6279
6684
  findings.push(...diagnoseCheckClassification(configRead.kind === "ok" ? configRead.config : null));
6280
6685
  findings.push(...diagnoseGlobalConfigChecks());
6686
+ findings.push(...diagnoseTokenMetering(deps));
6281
6687
  findings.push(...await diagnoseEnvironment(deps, hasDrift));
6282
6688
  findings.push(await diagnoseBranchProtection(deps, repo.owner, repo.repo));
6283
6689
  findings.push(diagnoseCodeowners(repo.repoRoot));
@@ -6634,7 +7040,7 @@ function pathFlag(args) {
6634
7040
  const i = args.indexOf("--path");
6635
7041
  return i !== -1 && args[i + 1] ? args[i + 1] : ".";
6636
7042
  }
6637
- var PATH_INJECTION_RE = /[|\r\n]/;
7043
+ var PATH_INJECTION_RE = /[|`\r\n]/;
6638
7044
  function validPathFlag(path) {
6639
7045
  return !PATH_INJECTION_RE.test(path);
6640
7046
  }
@@ -6647,7 +7053,7 @@ async function runInitProduct(args, deps) {
6647
7053
  }
6648
7054
  const productPath = pathFlag(args);
6649
7055
  if (!validPathFlag(productPath)) {
6650
- console.error("Error: --path must not contain '|' or a newline (it is written into a markdown table row).");
7056
+ console.error("Error: --path must not contain '|', a backtick, or a newline (it is written into a markdown table row and must round-trip verbatim).");
6651
7057
  return 2;
6652
7058
  }
6653
7059
  const specsPath = productPath === "." ? "specs/" : `${productPath}/specs/`;
@@ -7568,6 +7974,66 @@ ${registration}
7568
7974
  // src/commands/pr.ts
7569
7975
  import { execFileSync as execFileSync12 } from "node:child_process";
7570
7976
 
7977
+ // src/lib/numstat.ts
7978
+ function summariseNumstat(numstat) {
7979
+ const rows = numstat.split(`
7980
+ `).filter((l) => l.trim() !== "");
7981
+ let added = 0;
7982
+ let deleted = 0;
7983
+ let binary = 0;
7984
+ for (const row of rows) {
7985
+ const [a, d] = row.split("\t");
7986
+ if (a === "-" || d === "-") {
7987
+ binary++;
7988
+ continue;
7989
+ }
7990
+ added += Number(a) || 0;
7991
+ deleted += Number(d) || 0;
7992
+ }
7993
+ const files = `${rows.length} file${rows.length === 1 ? "" : "s"} changed`;
7994
+ const bin2 = binary > 0 ? `, ${binary} binary` : "";
7995
+ return `${files}, ${added} insertion${added === 1 ? "" : "s"}(+), ${deleted} deletion${deleted === 1 ? "" : "s"}(-)${bin2}`;
7996
+ }
7997
+ var EVIDENCE_SUMMARY_PREFIX = "Summary: ";
7998
+
7999
+ // src/checks/scan-context.ts
8000
+ var ZERO_WIDTH = /[\u200B-\u200D\u2060\uFEFF]/g;
8001
+ var HTML_ENTITIES = {
8002
+ "&quot;": '"',
8003
+ "&apos;": "'",
8004
+ "&lt;": "<",
8005
+ "&gt;": ">",
8006
+ "&amp;": "&"
8007
+ };
8008
+ function decodeNamedEntities(body) {
8009
+ return body.replace(/&(?:quot|apos|lt|gt|amp);/g, (m) => HTML_ENTITIES[m] ?? m);
8010
+ }
8011
+ function normalizeBody(rawBody) {
8012
+ return decodeNamedEntities(rawBody.replace(ZERO_WIDTH, ""));
8013
+ }
8014
+ function buildAnchorLookupMask(body) {
8015
+ return maskDetailsBlocks(maskCode(body));
8016
+ }
8017
+
8018
+ class ScanContext {
8019
+ raw;
8020
+ normalised;
8021
+ masked;
8022
+ constructor(raw, normalised, masked) {
8023
+ this.raw = raw;
8024
+ this.normalised = normalised;
8025
+ this.masked = masked;
8026
+ }
8027
+ static from(rawBody) {
8028
+ const normalised = normalizeBody(rawBody);
8029
+ return new ScanContext(rawBody, normalised, buildAnchorLookupMask(normalised));
8030
+ }
8031
+ rawResolutionAgrees(field) {
8032
+ const rawHasPair = anchoredRegionBounds(this.raw, field) !== null;
8033
+ return rawHasPair === (anchoredRegionBounds(this.masked, field) !== null);
8034
+ }
8035
+ }
8036
+
7571
8037
  // src/checks/body-bare-digits-logic.ts
7572
8038
  function blankRange(text, start, end) {
7573
8039
  const region = text.slice(start, end);
@@ -7743,8 +8209,8 @@ var BOUNDED_ANCHOR_BLANK = {
7743
8209
  PROJECT: blankProjectField,
7744
8210
  EVIDENCE: blankEvidenceField
7745
8211
  };
7746
- function buildScanMask(body) {
7747
- let masked = maskDetailsBlocks(maskCode(body));
8212
+ function buildScanMask(ctx) {
8213
+ let masked = ctx.masked;
7748
8214
  masked = blankAnchoredRegions(masked);
7749
8215
  masked = blankPremiseValues(masked);
7750
8216
  masked = blankTokenReportSection(masked);
@@ -7753,17 +8219,6 @@ function buildScanMask(body) {
7753
8219
  }
7754
8220
  var TOKEN_WITH_DIGIT = /\S*\p{Nd}\S*/gu;
7755
8221
  var LIST_MARKER_TOKEN = /^\p{Nd}{1,9}[.)]$/u;
7756
- var ZERO_WIDTH = /[\u200B-\u200D\u2060\uFEFF]/g;
7757
- var HTML_ENTITIES = {
7758
- "&quot;": '"',
7759
- "&apos;": "'",
7760
- "&lt;": "<",
7761
- "&gt;": ">",
7762
- "&amp;": "&"
7763
- };
7764
- function decodeNamedEntities(body) {
7765
- return body.replace(/&(?:quot|apos|lt|gt|amp);/g, (m) => HTML_ENTITIES[m] ?? m);
7766
- }
7767
8222
  function isLineLeadingListMarker(line, matchStart, rawToken) {
7768
8223
  if (!LIST_MARKER_TOKEN.test(rawToken))
7769
8224
  return false;
@@ -7771,8 +8226,9 @@ function isLineLeadingListMarker(line, matchStart, rawToken) {
7771
8226
  return /^ {0,3}$/.test(before);
7772
8227
  }
7773
8228
  function checkBareDigits(rawBody) {
7774
- const body = decodeNamedEntities(rawBody.replace(ZERO_WIDTH, ""));
7775
- const masked = buildScanMask(body);
8229
+ const ctx = ScanContext.from(rawBody);
8230
+ const body = ctx.normalised;
8231
+ const masked = buildScanMask(ctx);
7776
8232
  const maskedLines = masked.split(`
7777
8233
  `);
7778
8234
  const origLines = body.split(`
@@ -8077,10 +8533,26 @@ function renderGroupB(outcomes) {
8077
8533
  `);
8078
8534
  }
8079
8535
  function buildBlockInner(groupA, gateOutcomes) {
8080
- return [`Head: ${groupA.head}`, "", renderGroupA(groupA), "", renderGroupB(gateOutcomes)].join(`
8536
+ return [
8537
+ `Head: ${groupA.head}`,
8538
+ `${EVIDENCE_SUMMARY_PREFIX}\`${summariseNumstat(groupA.numstat)}\``,
8539
+ "",
8540
+ renderGroupA(groupA),
8541
+ "",
8542
+ renderGroupB(gateOutcomes)
8543
+ ].join(`
8081
8544
  `);
8082
8545
  }
8546
+
8547
+ class DivergentEvidenceAnchorError extends Error {
8548
+ constructor() {
8549
+ super("the AEG:EVIDENCE anchor resolves differently before and after normalisation — a zero-width character or an HTML entity inside a marker makes this writer and the checks that read the block disagree about which pair is real. Remove it and re-run.");
8550
+ this.name = "DivergentEvidenceAnchorError";
8551
+ }
8552
+ }
8083
8553
  function replaceEvidenceBlock(body, blockInner) {
8554
+ if (!ScanContext.from(body).rawResolutionAgrees("EVIDENCE"))
8555
+ throw new DivergentEvidenceAnchorError;
8084
8556
  const full = `${EVIDENCE_START}
8085
8557
  ${blockInner}
8086
8558
  ${EVIDENCE_END}`;
@@ -8327,7 +8799,13 @@ function realDeps6() {
8327
8799
  readHooksPath: readCoreHooksPath,
8328
8800
  nodeVersion: () => process.version,
8329
8801
  bunVersion: () => typeof Bun === "undefined" ? null : Bun.version,
8330
- packageVersion: readPackageVersion
8802
+ packageVersion: readPackageVersion,
8803
+ meteringCapability: () => resolveMeteringCapability({
8804
+ env: process.env,
8805
+ cwd: process.cwd(),
8806
+ exists: existsSync21,
8807
+ readFile: (path) => readFileSync23(path, "utf8")
8808
+ })
8331
8809
  },
8332
8810
  confirm: async (q, defaultYes) => promptYesNo(q, defaultYes),
8333
8811
  ask: async (q) => prompt(q),
@@ -8509,6 +8987,12 @@ function renderFindingsSection(findings) {
8509
8987
  function renderTokensLine(role, roleLabel, input) {
8510
8988
  return `Tokens: ${input.taskId}: ${role} — ${roleLabel} — ${input.model} — ${input.tokensIn}/${input.tokensOut}/${input.cost}`;
8511
8989
  }
8990
+ function renderCastByLine(roleLabel, sessionId) {
8991
+ return `Cast by: ${roleLabel} (session ${sessionId})`;
8992
+ }
8993
+ function resolveSessionId(env) {
8994
+ return env.CLAUDE_CODE_SESSION_ID ?? "(unknown)";
8995
+ }
8512
8996
  var CODE_REVIEW_VERDICT_TEXT = {
8513
8997
  APPROVE: "APPROVE",
8514
8998
  REQUEST_CHANGES: "REQUEST CHANGES"
@@ -8530,7 +9014,8 @@ function renderCodeReviewComment(input) {
8530
9014
  `TESTS: ${input.tests}`,
8531
9015
  `DOCS: ${input.docs}`,
8532
9016
  "",
8533
- renderTokensLine("review", "Reviewer", input)
9017
+ renderTokensLine("review", "Reviewer", input),
9018
+ renderCastByLine("Reviewer", input.sessionId)
8534
9019
  ].join(`
8535
9020
  `);
8536
9021
  }
@@ -8553,7 +9038,7 @@ function renderSecurityComment(input) {
8553
9038
  if (input.secretsEvidence !== null) {
8554
9039
  lines.push("```", input.secretsEvidence, "```", "");
8555
9040
  }
8556
- lines.push(`SECRETS: ${input.secrets}`, "", renderTokensLine("security", "Security", input));
9041
+ lines.push(`SECRETS: ${input.secrets}`, "", renderTokensLine("security", "Security", input), renderCastByLine("Security", input.sessionId));
8557
9042
  return lines.join(`
8558
9043
  `);
8559
9044
  }
@@ -8775,7 +9260,8 @@ async function reviewPostCommand(args) {
8775
9260
  const tokensIn = requireTokenField(flags2, "--tokens-in");
8776
9261
  const tokensOut = requireTokenField(flags2, "--tokens-out");
8777
9262
  const cost = requireFlag(flags2, "--cost");
8778
- const tokens = { taskId, model, tokensIn, tokensOut, cost };
9263
+ const sessionId = resolveSessionId(process.env);
9264
+ const tokens = { taskId, model, tokensIn, tokensOut, cost, sessionId };
8779
9265
  const headSha = resolveHeadSha(pr);
8780
9266
  const principalAllowlist = resolvePrincipalAllowlist(loadTrustAnchorConfig());
8781
9267
  let body;
@@ -9007,10 +9493,128 @@ async function runStudio(cwd, args, moduleUrl = import.meta.url) {
9007
9493
  }
9008
9494
  }
9009
9495
 
9496
+ // src/commands/tokens.ts
9497
+ import { existsSync as existsSync23, readFileSync as readFileSync26 } from "node:fs";
9498
+ var USAGE4 = [
9499
+ 'Usage: vinaya tokens --phase "<task-id>: develop" --role Developer',
9500
+ " [--model <id>] [--transcript <path>] [--in <tokens-in> --out <tokens-out>]",
9501
+ "",
9502
+ " --transcript Read this transcript directly. Supported primary route — use it whenever you know",
9503
+ " which transcript is yours, and always in a repo with no track-transcript.sh hook.",
9504
+ " (omitted) Resolve via the Stop-hook pointer file, if this repo installs that hook.",
9505
+ " --in/--out Manual entry: the exact token figures, for a host whose usage arrives by some other",
9506
+ " means than a Claude Code transcript. Both required together; skips transcript",
9507
+ " resolution entirely."
9508
+ ].join(`
9509
+ `);
9510
+ function parseArgs(argv) {
9511
+ let phase;
9512
+ let role;
9513
+ let model;
9514
+ let transcriptPath;
9515
+ let tokensInRaw;
9516
+ let tokensOutRaw;
9517
+ for (let i = 0;i < argv.length; i++) {
9518
+ const arg = argv[i];
9519
+ if (arg === "--phase")
9520
+ phase = argv[++i];
9521
+ else if (arg === "--role")
9522
+ role = argv[++i];
9523
+ else if (arg === "--model")
9524
+ model = argv[++i];
9525
+ else if (arg === "--transcript")
9526
+ transcriptPath = argv[++i];
9527
+ else if (arg === "--in")
9528
+ tokensInRaw = argv[++i];
9529
+ else if (arg === "--out")
9530
+ tokensOutRaw = argv[++i];
9531
+ }
9532
+ if (!phase || !role) {
9533
+ throw new Error(USAGE4);
9534
+ }
9535
+ if (tokensInRaw === undefined !== (tokensOutRaw === undefined)) {
9536
+ throw new Error("vinaya tokens: --in and --out must be given together, or not at all.");
9537
+ }
9538
+ const tokensIn = tokensInRaw === undefined ? undefined : Number(tokensInRaw);
9539
+ const tokensOut = tokensOutRaw === undefined ? undefined : Number(tokensOutRaw);
9540
+ if (tokensIn !== undefined && (!Number.isFinite(tokensIn) || tokensIn < 0)) {
9541
+ throw new Error(`vinaya tokens: --in must be a non-negative integer, got "${tokensInRaw}".`);
9542
+ }
9543
+ if (tokensOut !== undefined && (!Number.isFinite(tokensOut) || tokensOut < 0)) {
9544
+ throw new Error(`vinaya tokens: --out must be a non-negative integer, got "${tokensOutRaw}".`);
9545
+ }
9546
+ return { phase, role, model, transcriptPath, tokensIn, tokensOut };
9547
+ }
9548
+ function manualSummary(tokensIn, tokensOut) {
9549
+ return {
9550
+ components: {
9551
+ inputTokens: tokensIn,
9552
+ outputTokens: tokensOut,
9553
+ cacheCreationInputTokens: 0,
9554
+ cacheReadInputTokens: 0
9555
+ },
9556
+ model: null,
9557
+ messageCount: 1
9558
+ };
9559
+ }
9560
+ function realDeps7() {
9561
+ return {
9562
+ env: process.env,
9563
+ cwd: process.cwd(),
9564
+ exists: existsSync23,
9565
+ readFile: (path) => readFileSync26(path, "utf8")
9566
+ };
9567
+ }
9568
+ function buildTokensResult(parsed, deps) {
9569
+ if (parsed.tokensIn !== undefined && parsed.tokensOut !== undefined) {
9570
+ const summary = manualSummary(parsed.tokensIn, parsed.tokensOut);
9571
+ return {
9572
+ line: formatTokensLine({ phase: parsed.phase, role: parsed.role, summary, modelOverride: parsed.model }),
9573
+ breakdown: undefined
9574
+ };
9575
+ }
9576
+ const capability = resolveMeteringCapability(deps, parsed.transcriptPath);
9577
+ if (!capability.capable) {
9578
+ throw new Error(`vinaya tokens: refused — could not resolve real usage figures (${capability.reason}).
9579
+ ${capability.detail}
9580
+
9581
+ ${USAGE4}`);
9582
+ }
9583
+ return {
9584
+ line: formatTokensLine({
9585
+ phase: parsed.phase,
9586
+ role: parsed.role,
9587
+ summary: capability.summary,
9588
+ modelOverride: parsed.model
9589
+ }),
9590
+ breakdown: formatBreakdown(capability.summary)
9591
+ };
9592
+ }
9593
+ function tokensCommand(argv) {
9594
+ let parsed;
9595
+ try {
9596
+ parsed = parseArgs(argv);
9597
+ } catch (err) {
9598
+ console.error(err.message);
9599
+ process.exit(2);
9600
+ }
9601
+ let result;
9602
+ try {
9603
+ result = buildTokensResult(parsed, realDeps7());
9604
+ } catch (err) {
9605
+ console.error(err.message);
9606
+ process.exit(1);
9607
+ }
9608
+ if (result.breakdown)
9609
+ console.error(result.breakdown);
9610
+ process.stdout.write(`${result.line}
9611
+ `);
9612
+ }
9613
+
9010
9614
  // src/commands/upgrade.ts
9011
- import { existsSync as existsSync23, readFileSync as readFileSync26, rmSync as rmSync5, writeFileSync as writeFileSync13 } from "node:fs";
9615
+ import { existsSync as existsSync24, readFileSync as readFileSync27, rmSync as rmSync5, writeFileSync as writeFileSync13 } from "node:fs";
9012
9616
  import { join as join26 } from "node:path";
9013
- function realDeps7() {
9617
+ function realDeps8() {
9014
9618
  return {
9015
9619
  detectRepo: detectGitRepo,
9016
9620
  hookDirFor: resolveHookDir,
@@ -9028,11 +9632,11 @@ function flags2(args) {
9028
9632
  }
9029
9633
  function readManifest3(repoRoot2) {
9030
9634
  const p = join26(repoRoot2, CONFIG_PATH);
9031
- if (!existsSync23(p))
9635
+ if (!existsSync24(p))
9032
9636
  return { kind: "missing" };
9033
9637
  let raw;
9034
9638
  try {
9035
- raw = JSON.parse(readFileSync26(p, "utf-8"));
9639
+ raw = JSON.parse(readFileSync27(p, "utf-8"));
9036
9640
  } catch (err) {
9037
9641
  return { kind: "invalid", error: `invalid JSON: ${err.message}` };
9038
9642
  }
@@ -9049,16 +9653,16 @@ function readManifest3(repoRoot2) {
9049
9653
  }
9050
9654
  function writeManifestVersion(repoRoot2, manifest) {
9051
9655
  const configAbs = join26(repoRoot2, CONFIG_PATH);
9052
- const seed = JSON.parse(readFileSync26(configAbs, "utf-8"));
9656
+ const seed = JSON.parse(readFileSync27(configAbs, "utf-8"));
9053
9657
  const updated = { ...manifest, version: MANAGED_MANIFEST_VERSION };
9054
9658
  writeFileSync13(configAbs, `${JSON.stringify({ ...seed, managed: updated }, null, 2)}
9055
9659
  `, "utf-8");
9056
9660
  }
9057
9661
  function stripFor(repoRoot2, path, marker, comment) {
9058
9662
  const abs2 = resolveManagedBlockPath(repoRoot2, path);
9059
- if (!existsSync23(abs2))
9663
+ if (!existsSync24(abs2))
9060
9664
  return { path, marker, comment, present: false, removesHost: false };
9061
- const stripped = stripBlockFromContent(readFileSync26(abs2, "utf-8"), marker, comment);
9665
+ const stripped = stripBlockFromContent(readFileSync27(abs2, "utf-8"), marker, comment);
9062
9666
  if (stripped === null)
9063
9667
  return { path, marker, comment, present: false, removesHost: false };
9064
9668
  return { path, marker, comment, present: true, removesHost: blockStripLeavesEmpty(stripped) };
@@ -9085,9 +9689,9 @@ function planHookRouting(repoRoot2, manifest, recorded, hooksPathValue) {
9085
9689
  const blocked = [];
9086
9690
  for (const b of legacy) {
9087
9691
  const abs2 = resolveManagedBlockPath(repoRoot2, b.path);
9088
- if (!existsSync23(abs2))
9692
+ if (!existsSync24(abs2))
9089
9693
  continue;
9090
- const stripped = stripBlockFromContent(readFileSync26(abs2, "utf-8"), b.marker, b.comment);
9694
+ const stripped = stripBlockFromContent(readFileSync27(abs2, "utf-8"), b.marker, b.comment);
9091
9695
  if (stripped === null)
9092
9696
  blocked.push(`${b.path} exists without vinaya's managed block`);
9093
9697
  else if (!blockStripLeavesEmpty(stripped))
@@ -9127,11 +9731,11 @@ function planUpgrade(ops, repoRoot2, manifest, routing) {
9127
9731
  for (const op of ops) {
9128
9732
  if (op.kind === "create-file") {
9129
9733
  const abs2 = join26(repoRoot2, op.path);
9130
- const exists = existsSync23(abs2);
9734
+ const exists = existsSync24(abs2);
9131
9735
  const owned = ownedFiles.has(op.path) || isDefaultedAgentVendorPath(op.path, manifest);
9132
9736
  let action;
9133
9737
  let triggerChange;
9134
- if (op.path === CONFIG_PATH || op.path === DOC_OWNERS_PATH) {
9738
+ if ((op.path === CONFIG_PATH || op.path === DOC_OWNERS_PATH) && exists) {
9135
9739
  action = "keep";
9136
9740
  } else if (!owned) {
9137
9741
  action = exists ? "refuse-foreign" : "not-installed";
@@ -9139,7 +9743,7 @@ function planUpgrade(ops, repoRoot2, manifest, routing) {
9139
9743
  action = "recreate";
9140
9744
  hasChanges = true;
9141
9745
  } else {
9142
- const diskContent = readFileSync26(abs2, "utf-8");
9746
+ const diskContent = readFileSync27(abs2, "utf-8");
9143
9747
  if (diskContent !== op.content) {
9144
9748
  action = "regenerate";
9145
9749
  hasChanges = true;
@@ -9160,11 +9764,11 @@ function planUpgrade(ops, repoRoot2, manifest, routing) {
9160
9764
  let action;
9161
9765
  if (!owned) {
9162
9766
  action = "not-installed";
9163
- } else if (!existsSync23(abs2)) {
9767
+ } else if (!existsSync24(abs2)) {
9164
9768
  action = "recreate-host";
9165
9769
  hasChanges = true;
9166
9770
  } else {
9167
- const content = readFileSync26(abs2, "utf-8");
9771
+ const content = readFileSync27(abs2, "utf-8");
9168
9772
  const { begin, end } = markerLines(op.marker, op.comment);
9169
9773
  if (!(content.includes(begin) && content.includes(end))) {
9170
9774
  action = "recreate-append";
@@ -9270,7 +9874,7 @@ function renderUpgradeDiff(plan) {
9270
9874
  }
9271
9875
  function regenerateBlock(repoRoot2, op) {
9272
9876
  const abs2 = resolveManagedBlockPath(repoRoot2, op.path);
9273
- const content = readFileSync26(abs2, "utf-8");
9877
+ const content = readFileSync27(abs2, "utf-8");
9274
9878
  const stripped = stripBlockFromContent(content, op.marker, op.comment);
9275
9879
  if (stripped !== null) {
9276
9880
  writeFileSync13(abs2, stripped.endsWith(`
@@ -9298,9 +9902,9 @@ function applyUpgrade(plan, repoRoot2) {
9298
9902
  if (!s.present)
9299
9903
  continue;
9300
9904
  const abs2 = resolveManagedBlockPath(repoRoot2, s.path);
9301
- if (!existsSync23(abs2))
9905
+ if (!existsSync24(abs2))
9302
9906
  continue;
9303
- const stripped = stripBlockFromContent(readFileSync26(abs2, "utf-8"), s.marker, s.comment);
9907
+ const stripped = stripBlockFromContent(readFileSync27(abs2, "utf-8"), s.marker, s.comment);
9304
9908
  if (stripped === null)
9305
9909
  continue;
9306
9910
  if (blockStripLeavesEmpty(stripped))
@@ -9385,7 +9989,7 @@ Vinaya upgraded.
9385
9989
  return 0;
9386
9990
  }
9387
9991
  async function upgradeCommand(args) {
9388
- process.exit(await runUpgrade(args, realDeps7()));
9992
+ process.exit(await runUpgrade(args, realDeps8()));
9389
9993
  }
9390
9994
 
9391
9995
  // src/commands/waiver.ts
@@ -9526,7 +10130,7 @@ function printHelp() {
9526
10130
  // src/index.ts
9527
10131
  var PACKAGE_ROOT = join27(dirname9(fileURLToPath2(import.meta.url)), "..");
9528
10132
  function readVersion2() {
9529
- const pkg = JSON.parse(readFileSync27(join27(PACKAGE_ROOT, "package.json"), "utf-8"));
10133
+ const pkg = JSON.parse(readFileSync28(join27(PACKAGE_ROOT, "package.json"), "utf-8"));
9530
10134
  return pkg.version;
9531
10135
  }
9532
10136
  var [, , command, ...args] = process.argv;
@@ -9679,6 +10283,10 @@ try {
9679
10283
  await quickstartCommand(args);
9680
10284
  break;
9681
10285
  }
10286
+ case "tokens": {
10287
+ tokensCommand(args);
10288
+ break;
10289
+ }
9682
10290
  default:
9683
10291
  console.error(`Unknown command: ${command}`);
9684
10292
  printHelp();