@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.
- package/README.md +7 -0
- package/aeg-root/contracts/archivist-tranche-archivist.md +2 -2
- package/aeg-root/contracts/tranche-archivist-planner.md +8 -8
- package/aeg-root/enforcement.md +6 -4
- package/aeg-root/process.md +5 -5
- package/aeg-root/roles/archivist.md +9 -10
- package/aeg-root/roles/brief-author.md +1 -1
- package/aeg-root/roles/developer.md +1 -1
- package/aeg-root/roles/planner.md +1 -1
- package/aeg-root/roles/principal.md +1 -1
- package/aeg-root/roles/tranche-archivist.md +3 -16
- package/aeg-root/skills/aeg/SKILL.md +30 -1
- package/aeg-root/skills/aeg-roles/SKILL.md +1 -1
- package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
- package/aeg-root/state-machine.md +8 -6
- package/aeg-root/task-model.md +1 -1
- package/aeg-root/templates/pr-report-template.md +4 -0
- package/dist/checks/bin/check-body-bare-digits.js +192 -27
- package/dist/checks/bin/check-branch-topology.js +518 -11
- package/dist/checks/bin/check-brief-shape.js +104 -11
- package/dist/checks/bin/check-closes-n.js +518 -11
- package/dist/checks/bin/check-coherence.js +526 -12
- package/dist/checks/bin/check-dead-branch-push.js +104 -11
- package/dist/checks/bin/check-dispatch-readiness.js +621 -14
- package/dist/checks/bin/check-doc-coverage-push.js +170 -20
- package/dist/checks/bin/check-doc-coverage.js +170 -20
- package/dist/checks/bin/check-doctrine-portability.js +112 -12
- package/dist/checks/bin/check-evidence-fresh.js +213 -20
- package/dist/checks/bin/check-first-push-dispatch.js +526 -12
- package/dist/checks/bin/check-issue-assignment.js +518 -11
- package/dist/checks/bin/check-main-branch-refusal.js +3724 -0
- package/dist/checks/bin/check-no-disk-state.js +104 -11
- package/dist/checks/bin/check-reader-resolvable-prose.js +186 -17
- package/dist/checks/bin/check-registry-gates.js +185 -16
- package/dist/checks/bin/check-retired-vocabulary.js +185 -16
- package/dist/checks/bin/check-review-gate.js +112 -12
- package/dist/checks/bin/check-single-plan-pr.js +104 -11
- package/dist/checks/bin/check-test-plan.js +104 -11
- package/dist/checks/bin/check-workspace-escape.js +104 -11
- package/dist/index.js +664 -56
- package/package.json +1 -1
- package/studio-standalone/_node_modules/@attalabs/aeg-core/bin/verify-coherence.ts +1089 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/required-server-files.json +5 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/{[externals]__0pvgf28._.js → [externals]__1agj1ww._.js} +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
- 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
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
- 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
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3noqvds-2bvrd.js → 0ea8-8nopz29h.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0la0hhmjprdxm.css +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1lk7h-0buf4ab.js +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3gehliafvmjo9.js → 2twnwe77d4n-m.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
- package/aeg-root/coordination.md +0 -292
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/2iysedx-n06xc.js +0 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/42wxpzjhw7oy4.css +0 -1
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_clientMiddlewareManifest.js +0 -0
- /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
|
|
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:
|
|
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
|
|
4100
|
+
function listLabelNames(repoFlag) {
|
|
3832
4101
|
const existing = shSoftJson(["gh", "label", "list", "-R", repoFlag, "--json", "name", "--limit", "200"]) ?? [];
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4055
|
-
|
|
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 = /[
|
|
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
|
+
""": '"',
|
|
8003
|
+
"'": "'",
|
|
8004
|
+
"<": "<",
|
|
8005
|
+
">": ">",
|
|
8006
|
+
"&": "&"
|
|
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(
|
|
7747
|
-
let masked =
|
|
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
|
-
""": '"',
|
|
7759
|
-
"'": "'",
|
|
7760
|
-
"<": "<",
|
|
7761
|
-
">": ">",
|
|
7762
|
-
"&": "&"
|
|
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
|
|
7775
|
-
const
|
|
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 [
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
9635
|
+
if (!existsSync24(p))
|
|
9032
9636
|
return { kind: "missing" };
|
|
9033
9637
|
let raw;
|
|
9034
9638
|
try {
|
|
9035
|
-
raw = JSON.parse(
|
|
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(
|
|
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 (!
|
|
9663
|
+
if (!existsSync24(abs2))
|
|
9060
9664
|
return { path, marker, comment, present: false, removesHost: false };
|
|
9061
|
-
const stripped = stripBlockFromContent(
|
|
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 (!
|
|
9692
|
+
if (!existsSync24(abs2))
|
|
9089
9693
|
continue;
|
|
9090
|
-
const stripped = stripBlockFromContent(
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
9767
|
+
} else if (!existsSync24(abs2)) {
|
|
9164
9768
|
action = "recreate-host";
|
|
9165
9769
|
hasChanges = true;
|
|
9166
9770
|
} else {
|
|
9167
|
-
const content =
|
|
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 =
|
|
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 (!
|
|
9905
|
+
if (!existsSync24(abs2))
|
|
9302
9906
|
continue;
|
|
9303
|
-
const stripped = stripBlockFromContent(
|
|
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,
|
|
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(
|
|
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();
|