@vibe-agent-toolkit/cli 0.2.0-rc.2 → 0.2.0-rc.3
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/dist/bin/vat +66 -15
- package/dist/bin/vat.js +66 -15
- package/dist/bin/vat.js.map +1 -1
- package/dist/bin.js +155 -36
- package/dist/bin.js.map +1 -1
- package/dist/command-loaders.d.ts +44 -0
- package/dist/command-loaders.d.ts.map +1 -0
- package/dist/command-loaders.js +58 -0
- package/dist/command-loaders.js.map +1 -0
- package/dist/commands/agent/index.js +1 -1
- package/dist/commands/agent/install.d.ts.map +1 -1
- package/dist/commands/agent/install.js +46 -5
- package/dist/commands/agent/install.js.map +1 -1
- package/dist/commands/audit/distributed-tree.d.ts +1 -1
- package/dist/commands/audit/distributed-tree.d.ts.map +1 -1
- package/dist/commands/audit/distributed-tree.js.map +1 -1
- package/dist/commands/audit/hierarchical-output.d.ts +1 -1
- package/dist/commands/audit/hierarchical-output.d.ts.map +1 -1
- package/dist/commands/audit/hierarchical-output.js +1 -1
- package/dist/commands/audit/hierarchical-output.js.map +1 -1
- package/dist/commands/audit.d.ts +22 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +223 -24
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/build.d.ts +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +1 -1
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/cache/cache-control.d.ts +73 -0
- package/dist/commands/cache/cache-control.d.ts.map +1 -0
- package/dist/commands/cache/cache-control.js +77 -0
- package/dist/commands/cache/cache-control.js.map +1 -0
- package/dist/commands/cache/clear.d.ts +17 -3
- package/dist/commands/cache/clear.d.ts.map +1 -1
- package/dist/commands/cache/clear.js +68 -3
- package/dist/commands/cache/clear.js.map +1 -1
- package/dist/commands/cache/index.d.ts +13 -63
- package/dist/commands/cache/index.d.ts.map +1 -1
- package/dist/commands/cache/index.js +38 -79
- package/dist/commands/cache/index.js.map +1 -1
- package/dist/commands/claude/budget.d.ts +157 -0
- package/dist/commands/claude/budget.d.ts.map +1 -0
- package/dist/commands/claude/budget.js +412 -0
- package/dist/commands/claude/budget.js.map +1 -0
- package/dist/commands/claude/context.d.ts +211 -0
- package/dist/commands/claude/context.d.ts.map +1 -0
- package/dist/commands/claude/context.js +709 -0
- package/dist/commands/claude/context.js.map +1 -0
- package/dist/commands/claude/index.d.ts +13 -0
- package/dist/commands/claude/index.d.ts.map +1 -1
- package/dist/commands/claude/index.js +24 -2
- package/dist/commands/claude/index.js.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.js +18 -20
- package/dist/commands/claude/marketplace/git-publish.js.map +1 -1
- package/dist/commands/claude/marketplace/validate.d.ts +1 -1
- package/dist/commands/claude/marketplace/validate.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/validate.js +1 -1
- package/dist/commands/claude/marketplace/validate.js.map +1 -1
- package/dist/commands/claude/plugin/build.d.ts +1 -1
- package/dist/commands/claude/plugin/build.d.ts.map +1 -1
- package/dist/commands/claude/plugin/build.js +10 -2
- package/dist/commands/claude/plugin/build.js.map +1 -1
- package/dist/commands/claude/plugin/install.d.ts.map +1 -1
- package/dist/commands/claude/plugin/install.js +22 -2
- package/dist/commands/claude/plugin/install.js.map +1 -1
- package/dist/commands/consistency-check.d.ts.map +1 -1
- package/dist/commands/consistency-check.js +3 -3
- package/dist/commands/consistency-check.js.map +1 -1
- package/dist/commands/corpus/report.d.ts +1 -1
- package/dist/commands/corpus/report.d.ts.map +1 -1
- package/dist/commands/corpus/runner.js +1 -1
- package/dist/commands/corpus/runner.js.map +1 -1
- package/dist/commands/corpus/scan.d.ts.map +1 -1
- package/dist/commands/corpus/scan.js +17 -8
- package/dist/commands/corpus/scan.js.map +1 -1
- package/dist/commands/doctor.d.ts +36 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +96 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/inventory.d.ts +13 -1
- package/dist/commands/inventory.d.ts.map +1 -1
- package/dist/commands/inventory.js +118 -13
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/mcp/collections.d.ts +1 -1
- package/dist/commands/mcp/collections.d.ts.map +1 -1
- package/dist/commands/phase-utils.d.ts +1 -1
- package/dist/commands/phase-utils.d.ts.map +1 -1
- package/dist/commands/rag/index-command.d.ts +39 -0
- package/dist/commands/rag/index-command.d.ts.map +1 -1
- package/dist/commands/rag/index-command.js +28 -2
- package/dist/commands/rag/index-command.js.map +1 -1
- package/dist/commands/rag/index.d.ts.map +1 -1
- package/dist/commands/rag/index.js +21 -10
- package/dist/commands/rag/index.js.map +1 -1
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +10 -2
- package/dist/commands/resources/index.js.map +1 -1
- package/dist/commands/resources/scan.d.ts +24 -0
- package/dist/commands/resources/scan.d.ts.map +1 -1
- package/dist/commands/resources/scan.js +18 -5
- package/dist/commands/resources/scan.js.map +1 -1
- package/dist/commands/resources/validate.d.ts +1 -1
- package/dist/commands/resources/validate.d.ts.map +1 -1
- package/dist/commands/resources/validate.js +6 -3
- package/dist/commands/resources/validate.js.map +1 -1
- package/dist/commands/skill/review-checklist.d.ts.map +1 -1
- package/dist/commands/skill/review-checklist.js +1 -0
- package/dist/commands/skill/review-checklist.js.map +1 -1
- package/dist/commands/skill/review.js +2 -2
- package/dist/commands/skill/review.js.map +1 -1
- package/dist/commands/skill/test/configure.d.ts +19 -0
- package/dist/commands/skill/test/configure.d.ts.map +1 -1
- package/dist/commands/skill/test/configure.js +18 -3
- package/dist/commands/skill/test/configure.js.map +1 -1
- package/dist/commands/skill/test/run.d.ts +51 -4
- package/dist/commands/skill/test/run.d.ts.map +1 -1
- package/dist/commands/skill/test/run.js +112 -10
- package/dist/commands/skill/test/run.js.map +1 -1
- package/dist/commands/skills/build.d.ts +1 -1
- package/dist/commands/skills/build.d.ts.map +1 -1
- package/dist/commands/skills/build.js +72 -37
- package/dist/commands/skills/build.js.map +1 -1
- package/dist/commands/skills/package.d.ts +1 -1
- package/dist/commands/skills/package.d.ts.map +1 -1
- package/dist/commands/skills/package.js.map +1 -1
- package/dist/commands/skills/validate.d.ts +1 -1
- package/dist/commands/skills/validate.d.ts.map +1 -1
- package/dist/commands/skills/validate.js +37 -19
- package/dist/commands/skills/validate.js.map +1 -1
- package/dist/commands/verify.d.ts +2 -2
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +3 -3
- package/dist/commands/verify.js.map +1 -1
- package/dist/utils/command-error.d.ts +1 -1
- package/dist/utils/command-error.d.ts.map +1 -1
- package/dist/utils/command-error.js +1 -1
- package/dist/utils/command-error.js.map +1 -1
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +7 -1
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/context-budget-issues.d.ts +108 -0
- package/dist/utils/context-budget-issues.d.ts.map +1 -0
- package/dist/utils/context-budget-issues.js +280 -0
- package/dist/utils/context-budget-issues.js.map +1 -0
- package/dist/utils/corpus-target.d.ts +48 -0
- package/dist/utils/corpus-target.d.ts.map +1 -0
- package/dist/utils/corpus-target.js +61 -0
- package/dist/utils/corpus-target.js.map +1 -0
- package/dist/utils/issue-anchor.d.ts +2 -2
- package/dist/utils/issue-anchor.d.ts.map +1 -1
- package/dist/utils/issue-anchor.js +1 -1
- package/dist/utils/issue-rendering.d.ts +1 -1
- package/dist/utils/issue-rendering.d.ts.map +1 -1
- package/dist/utils/issue-rendering.js +1 -1
- package/dist/utils/issue-rendering.js.map +1 -1
- package/dist/utils/issue-severity.d.ts +1 -1
- package/dist/utils/issue-severity.d.ts.map +1 -1
- package/dist/utils/issue-severity.js +1 -1
- package/dist/utils/issue-severity.js.map +1 -1
- package/dist/utils/optional-backend.d.ts +77 -0
- package/dist/utils/optional-backend.d.ts.map +1 -0
- package/dist/utils/optional-backend.js +115 -0
- package/dist/utils/optional-backend.js.map +1 -0
- package/dist/utils/output.d.ts +17 -0
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +19 -0
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/population-wiring.d.ts +59 -0
- package/dist/utils/population-wiring.d.ts.map +1 -0
- package/dist/utils/population-wiring.js +55 -0
- package/dist/utils/population-wiring.js.map +1 -0
- package/dist/utils/projection-store.d.ts +186 -0
- package/dist/utils/projection-store.d.ts.map +1 -0
- package/dist/utils/projection-store.js +255 -0
- package/dist/utils/projection-store.js.map +1 -0
- package/dist/utils/resource-loader.d.ts +127 -1
- package/dist/utils/resource-loader.d.ts.map +1 -1
- package/dist/utils/resource-loader.js +224 -7
- package/dist/utils/resource-loader.js.map +1 -1
- package/dist/utils/verdict-helpers.d.ts +1 -1
- package/dist/utils/verdict-helpers.d.ts.map +1 -1
- package/dist/utils/verdict-helpers.js +1 -1
- package/dist/utils/verdict-helpers.js.map +1 -1
- package/docs/agent.md +2 -2
- package/docs/mcp.md +2 -2
- package/docs/resources.md +8 -1
- package/package.json +15 -12
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vat claude budget [paths...]` — is the always-loaded context at a working
|
|
3
|
+
* location over its budget?
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is a command and not a check inside `vat resources validate`
|
|
6
|
+
*
|
|
7
|
+
* It used to be one: default-on, with a `--no-context-budget` opt-out. That is
|
|
8
|
+
* gone, in both directions — `vat resources validate` has no knowledge of the
|
|
9
|
+
* budget at all any more, not a flag, not a default. The ruling behind the move
|
|
10
|
+
* is short: *a validation run must not emit findings nobody asked for.* An
|
|
11
|
+
* `info` finding attached to a lane people run for link and anchor errors is a
|
|
12
|
+
* finding they scroll past, and a check people scroll past is a check they
|
|
13
|
+
* eventually silence.
|
|
14
|
+
*
|
|
15
|
+
* ## The sibling split: `context` QUERIES, `budget` CHECKS
|
|
16
|
+
*
|
|
17
|
+
* `vat claude context` answers a question and
|
|
18
|
+
* never gates — totals, admissions, the declared limits, no verdict, always
|
|
19
|
+
* exit 0. This command is the other half of that pair: it applies a THRESHOLD,
|
|
20
|
+
* produces findings with severities an adopter can configure, and lets an
|
|
21
|
+
* adopter who promotes the code to `error` fail their build with it. Two verbs
|
|
22
|
+
* because they are two acts, and folding a verdict into the query would make
|
|
23
|
+
* the query's number a number people learn to stop reading.
|
|
24
|
+
*
|
|
25
|
+
* ## Everything below is orchestration — deliberately
|
|
26
|
+
*
|
|
27
|
+
* Which directories exist, what each pays, and whether that is over budget are
|
|
28
|
+
* all decided in `@vibe-agent-toolkit/resources` (`buildClaudeContextPopulation`
|
|
29
|
+
* and `sweepAlwaysLoadedBudgets`); the wording and grouping of the findings in
|
|
30
|
+
* `utils/context-budget-issues.ts`; severity resolution in the two shared
|
|
31
|
+
* filters every other lane runs. This module populates one tree, calls them in
|
|
32
|
+
* order, and renders. It decides nothing about context loading of its own,
|
|
33
|
+
* which is the CLI-stays-dumb rule applied to the lane where the temptation is
|
|
34
|
+
* strongest.
|
|
35
|
+
*
|
|
36
|
+
* ⛔ The threshold is never re-spelled here. It is a MEASURED quantity that
|
|
37
|
+
* lives beside the detector (`DEFAULT_ALWAYS_LOADED_CONTEXT_TOKENS`), and a
|
|
38
|
+
* literal in this package would be a second source of truth that nothing
|
|
39
|
+
* compares.
|
|
40
|
+
*
|
|
41
|
+
* ## The stated bounds ride ONCE on the REPORT, in every format
|
|
42
|
+
*
|
|
43
|
+
* The sibling query publishes 23 stated limits; this command applies a threshold
|
|
44
|
+
* to the same measurement, so it publishes {@link ALWAYS_LOADED_BUDGET_LIMITS}
|
|
45
|
+
* and {@link CLAUDE_CONTEXT_BOUNDS_STATEMENT} beside its verdict — the half that
|
|
46
|
+
* GATES is the half whose bounds a reader most needs. Both are imported, never
|
|
47
|
+
* restated: the sentence is domain content owned beside the list it frames.
|
|
48
|
+
*
|
|
49
|
+
* ⛔ They belong to the REPORT, never to a finding. A per-finding copy is the
|
|
50
|
+
* defect this lane already shipped once on the query side, and it is invisible to
|
|
51
|
+
* any assertion that checks presence rather than counting occurrences.
|
|
52
|
+
*/
|
|
53
|
+
import { ALWAYS_LOADED_BUDGET_LIMITS, buildClaudeContextPopulation, CLAUDE_CONTEXT_BOUNDS_STATEMENT, DEFAULT_ALWAYS_LOADED_CONTEXT_TOKENS, sweepAlwaysLoadedBudgets, } from '@vibe-agent-toolkit/resources';
|
|
54
|
+
import { applyAllowFilter, calculateValidationStatus, countBySeverity, } from '@vibe-agent-toolkit/schema';
|
|
55
|
+
import { findProjectRoot } from '@vibe-agent-toolkit/utils';
|
|
56
|
+
import { Command, Option } from 'commander';
|
|
57
|
+
import { handleCommandError } from '../../utils/command-error.js';
|
|
58
|
+
import { loadConfig } from '../../utils/config-loader.js';
|
|
59
|
+
import { contextBudgetIssues, scopeSweepToPaths } from '../../utils/context-budget-issues.js';
|
|
60
|
+
import { targetPathWithin } from '../../utils/corpus-target.js';
|
|
61
|
+
import { resolveIssueSeverity } from '../../utils/issue-severity.js';
|
|
62
|
+
import { createLogger } from '../../utils/logger.js';
|
|
63
|
+
import { writeJsonOutput, writeStdoutSync, writeYamlOutput } from '../../utils/output.js';
|
|
64
|
+
import { populationWiring } from '../../utils/population-wiring.js';
|
|
65
|
+
import { withPopulationCache } from '../../utils/projection-store.js';
|
|
66
|
+
import { gitTrackerForProjectRoot } from '../audit/distributed-tree.js';
|
|
67
|
+
/** How this command names itself in a refusal. */
|
|
68
|
+
const COMMAND_NAME = 'vat claude budget';
|
|
69
|
+
/** Soft wrap width for the printed limit statements. */
|
|
70
|
+
const WRAP_COLUMNS = 96;
|
|
71
|
+
/**
|
|
72
|
+
* Create the `vat claude budget [paths...]` command.
|
|
73
|
+
*
|
|
74
|
+
* @returns The configured Commander command
|
|
75
|
+
*/
|
|
76
|
+
export function createBudgetCommand() {
|
|
77
|
+
const command = new Command('budget');
|
|
78
|
+
command
|
|
79
|
+
.description('Check whether the always-loaded context at a working location exceeds its budget')
|
|
80
|
+
.argument('[paths...]', 'Directories to check, with everything beneath them (default: the whole tree)')
|
|
81
|
+
.addOption(new Option('--format <format>', 'Output format: text (default), yaml, or json').choices([
|
|
82
|
+
'text',
|
|
83
|
+
'yaml',
|
|
84
|
+
'json',
|
|
85
|
+
]).default('text'))
|
|
86
|
+
.option('--debug', 'Verbose logging to stderr')
|
|
87
|
+
.action(claudeBudgetCommand)
|
|
88
|
+
.addHelpText('after', `
|
|
89
|
+
Description:
|
|
90
|
+
Reports ALWAYS_LOADED_CONTEXT_BUDGET for each instruction chain whose
|
|
91
|
+
always-loaded context exceeds the budget. The measured chain is the
|
|
92
|
+
repo-root CLAUDE.md, every CLAUDE.md on the path down to the working
|
|
93
|
+
location, one level of @ imports from each, and any unscoped rules file in
|
|
94
|
+
the root .claude/rules/. An AGENTS.md is measured only where a CLAUDE.md
|
|
95
|
+
imports it (@AGENTS.md) — Claude Code does not load it by name, so a repo
|
|
96
|
+
standardised on AGENTS.md alone has nothing here to measure. Path-scoped
|
|
97
|
+
rules (a paths: list) are excluded: they load when the agent touches a
|
|
98
|
+
matching file, not at launch.
|
|
99
|
+
|
|
100
|
+
ONE finding per chain, not per directory: directories loading the same files
|
|
101
|
+
share a finding, which names how many working locations pay it. Naming paths
|
|
102
|
+
narrows WHICH chains are reported; the payer count stays the whole tree's,
|
|
103
|
+
because that is the number that was measured.
|
|
104
|
+
|
|
105
|
+
This is the CHECK. 'vat claude context' is the QUERY — same lane, no
|
|
106
|
+
threshold and no verdict.
|
|
107
|
+
|
|
108
|
+
Configuration (vibe-agent-toolkit.config.yaml):
|
|
109
|
+
resources.validation.thresholds.alwaysLoadedContextTokens moves the budget
|
|
110
|
+
(default ${String(DEFAULT_ALWAYS_LOADED_CONTEXT_TOKENS)}). resources.validation.severity.ALWAYS_LOADED_CONTEXT_BUDGET
|
|
111
|
+
silences it (ignore) or promotes it to warning/error. A project with no
|
|
112
|
+
config file is checked at the same default budget.
|
|
113
|
+
|
|
114
|
+
Output:
|
|
115
|
+
- threshold: the budget every chain was measured against
|
|
116
|
+
- scope/unmatchedScope: what was asked about, and what matched nothing
|
|
117
|
+
- status/issueCounts: the worst actionable severity, plus every severity
|
|
118
|
+
- workingLocations, distinctChains, skippedUnknownLocations: whole-tree
|
|
119
|
+
facts, so an empty findings list is legible
|
|
120
|
+
- findings: one per over-budget chain, ascending by directory
|
|
121
|
+
- limits/boundsStatement: what this verdict does not settle, signed
|
|
122
|
+
over-report or under-report. On the REPORT, never on a
|
|
123
|
+
finding — they bound the method, not any one chain, so
|
|
124
|
+
they are stated exactly once however many chains flag
|
|
125
|
+
|
|
126
|
+
Text to stdout by default; --format yaml|json for a program. Diagnostics
|
|
127
|
+
and blob-stage refusals go to stderr.
|
|
128
|
+
|
|
129
|
+
Exit Codes:
|
|
130
|
+
0 - Reported (findings at the default info severity do not gate)
|
|
131
|
+
1 - A finding resolved to error severity via config
|
|
132
|
+
2 - System error (a path outside the corpus root, unreadable tree)
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
$ vat claude budget packages/cli # is it over budget where I work?
|
|
136
|
+
`);
|
|
137
|
+
return command;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Action handler for `vat claude budget [paths...]`.
|
|
141
|
+
*
|
|
142
|
+
* @param pathArgs - Directories to check; empty means the whole tree
|
|
143
|
+
* @param options - The command's flags
|
|
144
|
+
*/
|
|
145
|
+
export async function claudeBudgetCommand(pathArgs, options) {
|
|
146
|
+
const logger = createLogger(options.debug === true ? { debug: true } : {});
|
|
147
|
+
const startTime = Date.now();
|
|
148
|
+
try {
|
|
149
|
+
const root = findProjectRoot(process.cwd()) ?? process.cwd();
|
|
150
|
+
// 🔑 Every argument is resolved BEFORE the population. A path outside the
|
|
151
|
+
// corpus is a usage error, and finding it afterwards would charge the caller
|
|
152
|
+
// a full population — minutes on a cold cache — to be told they mistyped.
|
|
153
|
+
const scope = scopeWithin(root, pathArgs);
|
|
154
|
+
const config = loadConfig(root);
|
|
155
|
+
const validation = config?.resources?.validation;
|
|
156
|
+
const threshold = validation?.thresholds?.alwaysLoadedContextTokens
|
|
157
|
+
?? DEFAULT_ALWAYS_LOADED_CONTEXT_TOKENS;
|
|
158
|
+
const sweep = await sweepBudgets(root, threshold, logger);
|
|
159
|
+
const scoped = scopeSweepToPaths(sweep, scope);
|
|
160
|
+
// The same two shared mechanisms every other lane uses, in the same order:
|
|
161
|
+
// `validation.allow` suppresses by path glob, then `validation.severity`
|
|
162
|
+
// resolves — BOTH directions, so an adopter promoting this code to `error`
|
|
163
|
+
// gets an error, which is the case that gets forgotten.
|
|
164
|
+
const findings = resolveIssueSeverity(applyAllowFilter(contextBudgetIssues(scoped.sweep), validation ?? {}).emitted, validation);
|
|
165
|
+
warnUnmatched(scoped.unmatchedScope, logger);
|
|
166
|
+
const report = buildReport({ root, threshold, scope, sweep, scoped, findings });
|
|
167
|
+
emit(report, options.format ?? 'text');
|
|
168
|
+
// Only `error` gates. At the code's `info` default this is always 0, so the
|
|
169
|
+
// budget cannot fail a build unless an adopter asked it to.
|
|
170
|
+
process.exit(report.issueCounts.errors > 0 ? 1 : 0);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
handleCommandError(error, logger, startTime, 'claude budget');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Assemble the report from the run's parts.
|
|
178
|
+
*
|
|
179
|
+
* ⛔ The counters come from the UNSCOPED sweep. They answer "what was measured",
|
|
180
|
+
* and a scope changes only what is reported — sourcing them from the narrowed
|
|
181
|
+
* view would let `vat claude budget docs` claim the tree has one chain.
|
|
182
|
+
*
|
|
183
|
+
* ⛔ This is the ONE place the stated bounds enter the machine-readable output,
|
|
184
|
+
* and keeping it a single assignment is what makes "stated once" structural
|
|
185
|
+
* rather than a habit: there is no per-finding site left where a copy could be
|
|
186
|
+
* reintroduced. They are attached unconditionally — a run that flagged nothing
|
|
187
|
+
* used the same method, and a reader acting on "within budget" needs them most.
|
|
188
|
+
*
|
|
189
|
+
* ⛔ Exported so it can be TESTED. What has to be pinned is a COUNT — that the
|
|
190
|
+
* block appears exactly once across a MULTI-finding report — and a presence
|
|
191
|
+
* check passes identically with a copy per finding.
|
|
192
|
+
*
|
|
193
|
+
* @param input - The run's parts
|
|
194
|
+
* @returns The report, ready to serialize
|
|
195
|
+
*/
|
|
196
|
+
export function buildReport(input) {
|
|
197
|
+
const { root, threshold, scope, sweep, scoped, findings } = input;
|
|
198
|
+
return {
|
|
199
|
+
root,
|
|
200
|
+
threshold,
|
|
201
|
+
scope,
|
|
202
|
+
unmatchedScope: scoped.unmatchedScope,
|
|
203
|
+
status: calculateValidationStatus(findings),
|
|
204
|
+
issueCounts: countBySeverity(findings),
|
|
205
|
+
workingLocations: sweep.evaluatedDirectories,
|
|
206
|
+
distinctChains: sweep.queriedDirectories,
|
|
207
|
+
skippedUnknownLocations: sweep.skippedUnknownLocations,
|
|
208
|
+
findings,
|
|
209
|
+
boundsStatement: CLAUDE_CONTEXT_BOUNDS_STATEMENT,
|
|
210
|
+
limits: ALWAYS_LOADED_BUDGET_LIMITS,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Resolve the requested scope, defaulting to the whole tree.
|
|
215
|
+
*
|
|
216
|
+
* ⚠️ No arguments means the WHOLE TREE, not the current directory — the
|
|
217
|
+
* opposite of `vat claude context`'s default, and deliberately so. This is a
|
|
218
|
+
* check: the useful bare invocation is "tell me about everything", the way a
|
|
219
|
+
* linter run from a subdirectory still lints the project. Naming a directory is
|
|
220
|
+
* how a caller asks the narrower question.
|
|
221
|
+
*
|
|
222
|
+
* @param root - The discovered project root
|
|
223
|
+
* @param pathArgs - The path arguments, possibly empty
|
|
224
|
+
* @returns Root-relative directories; `['']` for the whole tree
|
|
225
|
+
*/
|
|
226
|
+
function scopeWithin(root, pathArgs) {
|
|
227
|
+
if (pathArgs.length === 0)
|
|
228
|
+
return [''];
|
|
229
|
+
return pathArgs.map((pathArg) => targetPathWithin(root, pathArg, COMMAND_NAME));
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Say so, loudly, when a requested path named no working location.
|
|
233
|
+
*
|
|
234
|
+
* ⛔ `warn` (stderr), never `debug`. Such a scope produces zero findings, which
|
|
235
|
+
* is byte-identical to a clean bill of health — and a default run that stayed
|
|
236
|
+
* silent about it would publish that confusion rather than the fact behind it.
|
|
237
|
+
*
|
|
238
|
+
* @param unmatched - The requested paths that matched nothing
|
|
239
|
+
* @param logger - Where the warning goes
|
|
240
|
+
*/
|
|
241
|
+
function warnUnmatched(unmatched, logger) {
|
|
242
|
+
if (unmatched.length === 0)
|
|
243
|
+
return;
|
|
244
|
+
logger.warn(`Warning: no working location matched ${unmatched.join(', ')} — nothing was checked there.`
|
|
245
|
+
+ ' This is NOT a report that those paths are within budget.');
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Populate the Claude-context lane for one tree and sweep every location.
|
|
249
|
+
*
|
|
250
|
+
* ⚠️ Roots at the discovered project root and NOWHERE else, whatever paths were
|
|
251
|
+
* named. A budget is a property of a position in the directory tree, and rooting
|
|
252
|
+
* a population at a subdirectory would compute one against a corpus whose
|
|
253
|
+
* ancestors are missing — a confidently wrong number.
|
|
254
|
+
*
|
|
255
|
+
* @param root - The absolute corpus root; the ONLY tree populated
|
|
256
|
+
* @param threshold - The always-loaded token budget
|
|
257
|
+
* @param logger - Where blob-stage refusals go. **stderr**, never stdout: this
|
|
258
|
+
* command's stdout is the document a caller parses
|
|
259
|
+
* @returns Every working location's budget
|
|
260
|
+
*/
|
|
261
|
+
async function sweepBudgets(root, threshold, logger) {
|
|
262
|
+
const projection = await withPopulationCache({ root }, async (cache) => {
|
|
263
|
+
const gitTracker = await gitTrackerForProjectRoot(root);
|
|
264
|
+
return buildClaudeContextPopulation({ root, ...populationWiring(logger, gitTracker, cache) });
|
|
265
|
+
});
|
|
266
|
+
return sweepAlwaysLoadedBudgets(projection, threshold);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Write the report in the requested format.
|
|
270
|
+
*
|
|
271
|
+
* @param report - The report
|
|
272
|
+
* @param format - The selected format
|
|
273
|
+
*/
|
|
274
|
+
function emit(report, format) {
|
|
275
|
+
if (format === 'json') {
|
|
276
|
+
writeJsonOutput(report);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (format === 'yaml') {
|
|
280
|
+
writeYamlOutput(report);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
writeStdoutSync(renderReportText(report));
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Render the report for a person.
|
|
287
|
+
*
|
|
288
|
+
* ⛔ Exported so the "stated once" property can be pinned in TEXT as well as in
|
|
289
|
+
* json. The two renderings are separate code paths and this lane has already
|
|
290
|
+
* shipped a defect where they disagreed while each looked right alone.
|
|
291
|
+
*
|
|
292
|
+
* @param report - The report
|
|
293
|
+
* @returns The text rendering, newline-terminated
|
|
294
|
+
*/
|
|
295
|
+
export function renderReportText(report) {
|
|
296
|
+
const lines = [
|
|
297
|
+
`Always-loaded context budget at ${report.root}`,
|
|
298
|
+
` budget ${count(report.threshold)} tokens`
|
|
299
|
+
+ ` · ${count(report.workingLocations)} working locations`
|
|
300
|
+
+ ` · ${count(report.distinctChains)} distinct chains`
|
|
301
|
+
+ ` · ${count(report.skippedUnknownLocations)} unrealized`,
|
|
302
|
+
'',
|
|
303
|
+
...findingLines(report.findings),
|
|
304
|
+
...unmatchedLines(report.unmatchedScope),
|
|
305
|
+
...limitLines(report),
|
|
306
|
+
];
|
|
307
|
+
return `${lines.join('\n')}\n`;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The stated bounds, printed once at the foot of the report.
|
|
311
|
+
*
|
|
312
|
+
* ⛔ Never omitted, and never per finding. A clean report is subject to exactly
|
|
313
|
+
* the same signed bounds as a flagged one — more so, since "within budget" is the
|
|
314
|
+
* verdict a reader is most likely to act on without reading further.
|
|
315
|
+
*
|
|
316
|
+
* The sibling `vat claude context` prints a section of the same shape from its
|
|
317
|
+
* own list. They are deliberately NOT sharing a renderer: two commands, two
|
|
318
|
+
* report types, and the query additionally prints the modelled-behaviour
|
|
319
|
+
* citations that no bound of this verdict rests on.
|
|
320
|
+
*
|
|
321
|
+
* @param report - The report carrying the bounds
|
|
322
|
+
* @returns The section's lines
|
|
323
|
+
*/
|
|
324
|
+
function limitLines(report) {
|
|
325
|
+
const lines = ['What this verdict does not settle', ...wrapped(report.boundsStatement, ' '), ''];
|
|
326
|
+
for (const limit of report.limits) {
|
|
327
|
+
lines.push(` ${limit.direction}: ${limit.id}`);
|
|
328
|
+
lines.push(...wrapped(limit.statement, ' '));
|
|
329
|
+
}
|
|
330
|
+
return lines;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Soft-wrap a statement under a fixed indent.
|
|
334
|
+
*
|
|
335
|
+
* The statements run to several sentences and are the part of this output a
|
|
336
|
+
* reader has to actually read; unwrapped they arrive as one 500-column line that
|
|
337
|
+
* a terminal breaks mid-word. A word longer than the width overflows its own line
|
|
338
|
+
* rather than being split — no character of a statement is ever dropped.
|
|
339
|
+
*
|
|
340
|
+
* @param text - The statement
|
|
341
|
+
* @param indent - Leading whitespace for every produced line
|
|
342
|
+
* @returns One or more indented lines
|
|
343
|
+
*/
|
|
344
|
+
function wrapped(text, indent) {
|
|
345
|
+
const width = Math.max(WRAP_COLUMNS - indent.length, 24);
|
|
346
|
+
const lines = [];
|
|
347
|
+
let pending = '';
|
|
348
|
+
for (const word of text.split(' ')) {
|
|
349
|
+
const candidate = pending === '' ? word : `${pending} ${word}`;
|
|
350
|
+
if (pending !== '' && candidate.length > width) {
|
|
351
|
+
lines.push(indent + pending);
|
|
352
|
+
pending = word;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
pending = candidate;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (pending !== '')
|
|
359
|
+
lines.push(indent + pending);
|
|
360
|
+
return lines;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* The findings, or the sentence that says there were none.
|
|
364
|
+
*
|
|
365
|
+
* ⛔ The empty case is a SENTENCE, not blank output. A command that prints
|
|
366
|
+
* nothing is indistinguishable from one that failed to run.
|
|
367
|
+
*
|
|
368
|
+
* @param findings - The reported findings
|
|
369
|
+
* @returns The lines, blank-terminated
|
|
370
|
+
*/
|
|
371
|
+
function findingLines(findings) {
|
|
372
|
+
if (findings.length === 0)
|
|
373
|
+
return ['Every instruction chain checked is within budget.', ''];
|
|
374
|
+
const lines = [];
|
|
375
|
+
for (const finding of findings) {
|
|
376
|
+
lines.push(`${finding.severity} ${finding.code} at ${finding.location ?? '<corpus root>'}`);
|
|
377
|
+
lines.push(` ${finding.message}`);
|
|
378
|
+
if (finding.fix !== undefined)
|
|
379
|
+
lines.push(` Fix: ${finding.fix}`);
|
|
380
|
+
lines.push('');
|
|
381
|
+
}
|
|
382
|
+
return lines;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* The unmatched-scope notice, printed in the document as well as on stderr.
|
|
386
|
+
*
|
|
387
|
+
* @param unmatched - The requested paths that matched nothing
|
|
388
|
+
* @returns The lines, or none
|
|
389
|
+
*/
|
|
390
|
+
function unmatchedLines(unmatched) {
|
|
391
|
+
if (unmatched.length === 0)
|
|
392
|
+
return [];
|
|
393
|
+
return [
|
|
394
|
+
`Matched no working location: ${unmatched.join(', ')}`,
|
|
395
|
+
' Nothing was checked there — this is not a report that they are within budget.',
|
|
396
|
+
'',
|
|
397
|
+
];
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* A count as a reader reads it.
|
|
401
|
+
*
|
|
402
|
+
* Pinned to `en-US` rather than left to the host locale, so the same run renders
|
|
403
|
+
* byte-identically on two developers' machines — the same rule the findings
|
|
404
|
+
* themselves follow in `utils/context-budget-issues.ts`.
|
|
405
|
+
*
|
|
406
|
+
* @param value - The number
|
|
407
|
+
* @returns Its thousands-separated form
|
|
408
|
+
*/
|
|
409
|
+
function count(value) {
|
|
410
|
+
return value.toLocaleString('en-US');
|
|
411
|
+
}
|
|
412
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../../src/commands/claude/budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vat claude context [path]` — what loads into an agent's context here, and
|
|
3
|
+
* what it costs.
|
|
4
|
+
*
|
|
5
|
+
* The first ANALYSIS verb under `vat claude`, which until now held only
|
|
6
|
+
* management verbs (plugins, marketplaces, org administration). It answers a
|
|
7
|
+
* question, changes nothing, and never gates: there is no threshold anywhere in
|
|
8
|
+
* this file, and the only non-zero exit is {@link handleCommandError}'s 2 for a
|
|
9
|
+
* system failure. A number that fails a build teaches people to stop reading it.
|
|
10
|
+
*
|
|
11
|
+
* ## Everything below is orchestration and formatting — deliberately
|
|
12
|
+
*
|
|
13
|
+
* Which files load, why, and what they cost are all decided in
|
|
14
|
+
* `@vibe-agent-toolkit/resources` (`whatLoadsAt` and `account`). This module
|
|
15
|
+
* populates, calls them, and renders. It computes nothing about context loading
|
|
16
|
+
* of its own, which is the CLI-stays-dumb rule applied to a lane where the
|
|
17
|
+
* temptation is strong: the totals are right there and a "just one more derived
|
|
18
|
+
* field" would put a second, unowned model of the vendor's behaviour in the
|
|
19
|
+
* package no other package can depend on.
|
|
20
|
+
*
|
|
21
|
+
* ## The limits ride WITH the run — ONCE — in every format
|
|
22
|
+
*
|
|
23
|
+
* Spec §11 puts the stated limits in the command's own output rather than in a
|
|
24
|
+
* doc, because a limit a reader has to go and find is a limit that never reaches
|
|
25
|
+
* the person acting on the number. So {@link CLAUDE_CONTEXT_LIMITS} and
|
|
26
|
+
* {@link CLAUDE_CONTEXT_MODELLED_BEHAVIOURS} are fields of the yaml/json
|
|
27
|
+
* ENVELOPE and a printed section of the text rendering, and
|
|
28
|
+
* {@link CLAUDE_CONTEXT_BOUNDS_STATEMENT} — *neither a floor nor a ceiling* —
|
|
29
|
+
* appears in all three. All three are imported, never restated: the sentence is
|
|
30
|
+
* domain content owned beside the list it frames, so every other consumer of the
|
|
31
|
+
* list reaches it too.
|
|
32
|
+
*
|
|
33
|
+
* ⛔ They belong to the ENVELOPE, never to an answer, and both renderings obey
|
|
34
|
+
* that identically — see {@link ContextEnvelope} for why. Attaching them per
|
|
35
|
+
* answer was not a formatting preference: on a `--all` sweep of this repository
|
|
36
|
+
* (6,224 answers) `--format json` measured 76,877,016 bytes and measures
|
|
37
|
+
* 6,581,468 with the block hoisted — 70.3 MB of ONE byte-identical paragraph,
|
|
38
|
+
* repeated. That is the JSON spelling of the very burial
|
|
39
|
+
* {@link renderEnvelopeText} refuses to do in text.
|
|
40
|
+
*
|
|
41
|
+
* ⛔ Nothing here may read as "complete". Many of the stated limits are signed
|
|
42
|
+
* `over-report`/`under-report` and apply whether or not `unknownTokenRows`,
|
|
43
|
+
* `skippedOversizeRows` and `prunedRows` are all zero, so zeroed counters are not
|
|
44
|
+
* a licence for the words "total cost", "full" or "all context".
|
|
45
|
+
*
|
|
46
|
+
* ⛔ A `null` token or byte count is an UNKNOWN SIZE and is never rendered as 0 —
|
|
47
|
+
* see {@link chargeText}. A confident zero is indistinguishable from a measured
|
|
48
|
+
* one, and the same reasoning is why an unrealized path answers `unknown` rather
|
|
49
|
+
* than an empty table.
|
|
50
|
+
*/
|
|
51
|
+
import { type AccountedRow, type ContextTotals, type DiscoverableContext, type GradedCondition, type LoadedContextAnswer, type ModelledBehaviour, type Projection, type StatedLimit } from '@vibe-agent-toolkit/resources';
|
|
52
|
+
import { Command } from 'commander';
|
|
53
|
+
/** How the answer is rendered. `text` is for a person; the other two are for a program. */
|
|
54
|
+
export type ContextOutputFormat = 'text' | 'yaml' | 'json';
|
|
55
|
+
/** Flags `vat claude context` accepts. */
|
|
56
|
+
export interface ClaudeContextOptions {
|
|
57
|
+
format?: ContextOutputFormat;
|
|
58
|
+
debug?: boolean;
|
|
59
|
+
all?: boolean;
|
|
60
|
+
discoverable?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The envelope every run emits, however many paths were asked about.
|
|
64
|
+
*
|
|
65
|
+
* ⛔ `answers` is an array **even for one path**, so a consumer never branches on
|
|
66
|
+
* arity. The single-path shape is the N=1 case of the sweep, not a different
|
|
67
|
+
* document — a caller that special-cased "one path means a bare object" is the
|
|
68
|
+
* defect this shape exists to prevent, and pre-1.0 is when to pay for it.
|
|
69
|
+
*
|
|
70
|
+
* `root` rides here rather than on each answer because it is a property of the
|
|
71
|
+
* POPULATION, and one run populates exactly one tree. Repeating it per answer
|
|
72
|
+
* would invite a reader to believe two answers could carry different roots.
|
|
73
|
+
*
|
|
74
|
+
* ⛔ The three limit fields ride here for the SAME reason, and it is the reason
|
|
75
|
+
* {@link renderEnvelopeText} already prints them once: they bound the
|
|
76
|
+
* MEASUREMENT METHOD, not any one path. An answer carrying its own copy reads
|
|
77
|
+
* as though that path had caveats of its own — and repeated across a sweep it
|
|
78
|
+
* is pure duplication, byte-identical every time.
|
|
79
|
+
*/
|
|
80
|
+
export interface ContextEnvelope {
|
|
81
|
+
readonly root: string;
|
|
82
|
+
readonly answers: readonly (ContextAnswerDocument | ContextUnknownDocument)[];
|
|
83
|
+
/** What these answers do not settle, in either direction. Stated once. */
|
|
84
|
+
readonly boundsStatement: string;
|
|
85
|
+
/** The signed over/under-report bounds on the method. Stated once. */
|
|
86
|
+
readonly limits: readonly StatedLimit[];
|
|
87
|
+
/** The vendor behaviours modelled, each cited. Stated once. */
|
|
88
|
+
readonly modelledBehaviours: readonly ModelledBehaviour[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The yaml/json document for a path the projection realizes.
|
|
92
|
+
*
|
|
93
|
+
* `rows` are `AccountedRow`s verbatim rather than a narrowed projection of them:
|
|
94
|
+
* re-shaping the rows here would be the CLI deciding which of the accounting
|
|
95
|
+
* module's facts a consumer is allowed to see, and `resourceId` and `bytes` are
|
|
96
|
+
* exactly what a consumer joining this answer to another projection query needs.
|
|
97
|
+
*/
|
|
98
|
+
export interface ContextAnswerDocument {
|
|
99
|
+
readonly kind: 'answer';
|
|
100
|
+
readonly input: string;
|
|
101
|
+
readonly directory: string;
|
|
102
|
+
readonly file: string | null;
|
|
103
|
+
readonly totals: ContextTotals;
|
|
104
|
+
readonly rows: readonly AccountedRow[];
|
|
105
|
+
readonly conditions: readonly GradedCondition[];
|
|
106
|
+
readonly overBudgetRules: readonly string[];
|
|
107
|
+
readonly unattributedImports: readonly string[];
|
|
108
|
+
/**
|
|
109
|
+
* What the loaded set POINTS AT in one hop and the harness does not load, or
|
|
110
|
+
* null when `--discoverable` was not asked for.
|
|
111
|
+
*
|
|
112
|
+
* ⛔ `null` rather than an empty object, and the difference is the whole
|
|
113
|
+
* reason the field is nullable: an empty `rows` array means "nothing here
|
|
114
|
+
* links anywhere", and a run that never looked must not be readable as that.
|
|
115
|
+
* The same distinction {@link ContextUnknownDocument} exists to draw, one
|
|
116
|
+
* level down.
|
|
117
|
+
*
|
|
118
|
+
* ⛔ Its tokens are NEVER added to `totals`. A markdown link is voluntary —
|
|
119
|
+
* nothing loads it — so folding it into a context-budget figure would charge
|
|
120
|
+
* a session for documents it may never open.
|
|
121
|
+
*/
|
|
122
|
+
readonly discoverable: DiscoverableContext | null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The yaml/json document for a path the projection never realized.
|
|
126
|
+
*
|
|
127
|
+
* ⛔ Structurally different from an answer, and that is the point: it carries no
|
|
128
|
+
* `totals` and no `rows`, so a consumer cannot accidentally read "VAT never
|
|
129
|
+
* looked at this path" as "nothing loads here". `explanation` says the same thing
|
|
130
|
+
* to a person who is reading the document rather than switching on `kind`.
|
|
131
|
+
*/
|
|
132
|
+
export interface ContextUnknownDocument {
|
|
133
|
+
readonly kind: 'unknown';
|
|
134
|
+
readonly input: string;
|
|
135
|
+
readonly reason: 'path-not-realized';
|
|
136
|
+
readonly explanation: string;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Create the `vat claude context [path]` command.
|
|
140
|
+
*
|
|
141
|
+
* @returns The configured Commander command
|
|
142
|
+
*/
|
|
143
|
+
export declare function createContextCommand(): Command;
|
|
144
|
+
/**
|
|
145
|
+
* Action handler for `vat claude context [path]`.
|
|
146
|
+
*
|
|
147
|
+
* @param pathArg - The path to answer for, or undefined for the current directory
|
|
148
|
+
* @param options - The command's flags
|
|
149
|
+
*/
|
|
150
|
+
export declare function claudeContextCommand(pathArgs: readonly string[], options: ClaudeContextOptions): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* The answer document for one query.
|
|
153
|
+
*
|
|
154
|
+
* Split from the handler so the handler stays a straight line: the accounting
|
|
155
|
+
* call needs the `claude-md` identity set, and threading that through the
|
|
156
|
+
* handler body put three statements between the query and its document.
|
|
157
|
+
*
|
|
158
|
+
* Returns the document alone — rendering moved to {@link renderEnvelopeText},
|
|
159
|
+
* which needs every document at once to print the limits exactly once, and the
|
|
160
|
+
* limits themselves are attached by {@link contextEnvelope}, never here.
|
|
161
|
+
*
|
|
162
|
+
* ⛔ Exported so it can be TESTED. What has to be pinned is an ABSENCE — that no
|
|
163
|
+
* limit field is on the returned document — and an absence is only observable by
|
|
164
|
+
* calling the function that would have added it.
|
|
165
|
+
*
|
|
166
|
+
* @param answer - The query's answer
|
|
167
|
+
* @param projection - The populated projection, for the `claude-md` tag rows
|
|
168
|
+
* @param discoverable - Whether to compute the one-hop reachable set
|
|
169
|
+
* @returns The answer document
|
|
170
|
+
*/
|
|
171
|
+
export declare function answerDocument(answer: LoadedContextAnswer, projection: Projection, discoverable: boolean): ContextAnswerDocument;
|
|
172
|
+
/**
|
|
173
|
+
* Wrap the run's documents in the envelope, with the limits attached ONCE.
|
|
174
|
+
*
|
|
175
|
+
* The one place {@link CLAUDE_CONTEXT_LIMITS} and its two companions enter the
|
|
176
|
+
* machine-readable output. Keeping that a single assignment is what makes the
|
|
177
|
+
* "stated once" property structural rather than a habit: there is no per-answer
|
|
178
|
+
* site left where a copy could be reintroduced.
|
|
179
|
+
*
|
|
180
|
+
* ⛔ Exported so it can be TESTED alongside {@link answerDocument} — the
|
|
181
|
+
* regression this guards (an answer growing its own copy of the block) is a
|
|
182
|
+
* property of the two functions TOGETHER, and a test that can reach only one of
|
|
183
|
+
* them cannot see it.
|
|
184
|
+
*
|
|
185
|
+
* @param root - The corpus root that was enumerated
|
|
186
|
+
* @param answers - One document per requested path, in the order requested
|
|
187
|
+
* @returns The envelope, ready to serialize
|
|
188
|
+
*/
|
|
189
|
+
export declare function contextEnvelope(root: string, answers: readonly (ContextAnswerDocument | ContextUnknownDocument)[]): ContextEnvelope;
|
|
190
|
+
/**
|
|
191
|
+
* What one row costs, or why it costs nothing.
|
|
192
|
+
*
|
|
193
|
+
* ⛔ A null token count renders as "size unknown", never as `0`. The `charged`
|
|
194
|
+
* branch cannot be reached with a null — `chargeOf` classifies those
|
|
195
|
+
* `unknown-size` first — but the check is written as a value test rather than a
|
|
196
|
+
* `?? 0`, so if that ever changed the output would say what it knows instead of
|
|
197
|
+
* asserting a measurement it does not have.
|
|
198
|
+
*
|
|
199
|
+
* **Exported solely so that rule can be pinned by a test that would FAIL if a
|
|
200
|
+
* `?? 0` were introduced.** The property lives in the RENDERER and nowhere else:
|
|
201
|
+
* an assertion over the json rows only re-states what `account` already
|
|
202
|
+
* guarantees, and on any real tree every row carries a blob, so such an
|
|
203
|
+
* assertion compares `false === false` and passes without ever reaching the code
|
|
204
|
+
* that could print a zero. It is not part of this module's command surface and
|
|
205
|
+
* nothing outside its own test may call it.
|
|
206
|
+
*
|
|
207
|
+
* @param row - The accounted row
|
|
208
|
+
* @returns The cost phrase
|
|
209
|
+
*/
|
|
210
|
+
export declare function chargeText(row: AccountedRow): string;
|
|
211
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/commands/claude/context.ts"],"names":[],"mappings":"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"}
|