@compaction/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +663 -0
- package/README.md +319 -0
- package/dist/cli/commands/adapter.d.ts +2 -0
- package/dist/cli/commands/adapter.js +49 -0
- package/dist/cli/commands/aggregate.d.ts +2 -0
- package/dist/cli/commands/aggregate.js +60 -0
- package/dist/cli/commands/analyze.d.ts +2 -0
- package/dist/cli/commands/analyze.js +55 -0
- package/dist/cli/commands/apply-context.d.ts +12 -0
- package/dist/cli/commands/apply-context.js +80 -0
- package/dist/cli/commands/apply.d.ts +2 -0
- package/dist/cli/commands/apply.js +65 -0
- package/dist/cli/commands/approve.d.ts +2 -0
- package/dist/cli/commands/approve.js +55 -0
- package/dist/cli/commands/audit.d.ts +2 -0
- package/dist/cli/commands/audit.js +63 -0
- package/dist/cli/commands/billing-delta.d.ts +2 -0
- package/dist/cli/commands/billing-delta.js +246 -0
- package/dist/cli/commands/capture-claude-code.d.ts +19 -0
- package/dist/cli/commands/capture-claude-code.js +198 -0
- package/dist/cli/commands/capture-provider-usage.d.ts +35 -0
- package/dist/cli/commands/capture-provider-usage.js +113 -0
- package/dist/cli/commands/capture.d.ts +2 -0
- package/dist/cli/commands/capture.js +76 -0
- package/dist/cli/commands/compact.d.ts +2 -0
- package/dist/cli/commands/compact.js +206 -0
- package/dist/cli/commands/eval.d.ts +2 -0
- package/dist/cli/commands/eval.js +66 -0
- package/dist/cli/commands/export-session-seed.d.ts +11 -0
- package/dist/cli/commands/export-session-seed.js +42 -0
- package/dist/cli/commands/feedback.d.ts +2 -0
- package/dist/cli/commands/feedback.js +233 -0
- package/dist/cli/commands/import.d.ts +2 -0
- package/dist/cli/commands/import.js +60 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +216 -0
- package/dist/cli/commands/integrations.d.ts +2 -0
- package/dist/cli/commands/integrations.js +29 -0
- package/dist/cli/commands/launch-treatment-session.d.ts +14 -0
- package/dist/cli/commands/launch-treatment-session.js +50 -0
- package/dist/cli/commands/optimize.d.ts +2 -0
- package/dist/cli/commands/optimize.js +33 -0
- package/dist/cli/commands/recommend.d.ts +4 -0
- package/dist/cli/commands/recommend.js +66 -0
- package/dist/cli/commands/review.d.ts +2 -0
- package/dist/cli/commands/review.js +60 -0
- package/dist/cli/commands/run.d.ts +2 -0
- package/dist/cli/commands/run.js +49 -0
- package/dist/cli/commands/spend.d.ts +2 -0
- package/dist/cli/commands/spend.js +24 -0
- package/dist/cli/commands/summary.d.ts +2 -0
- package/dist/cli/commands/summary.js +19 -0
- package/dist/cli/engine-degrade.d.ts +40 -0
- package/dist/cli/engine-degrade.js +59 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +59 -0
- package/dist/core/adapter-validation.d.ts +32 -0
- package/dist/core/adapter-validation.js +185 -0
- package/dist/core/adapters/claude-code-adapter.d.ts +51 -0
- package/dist/core/adapters/claude-code-adapter.js +539 -0
- package/dist/core/adapters/claude-code-discovery.d.ts +40 -0
- package/dist/core/adapters/claude-code-discovery.js +100 -0
- package/dist/core/aggregate-format.d.ts +62 -0
- package/dist/core/aggregate-format.js +349 -0
- package/dist/core/api-client/client.d.ts +43 -0
- package/dist/core/api-client/client.js +143 -0
- package/dist/core/api-client/config.d.ts +36 -0
- package/dist/core/api-client/config.js +44 -0
- package/dist/core/api-client/index.d.ts +13 -0
- package/dist/core/api-client/index.js +14 -0
- package/dist/core/api-client/payload.d.ts +93 -0
- package/dist/core/api-client/payload.js +180 -0
- package/dist/core/api-client/types.d.ts +95 -0
- package/dist/core/api-client/types.js +21 -0
- package/dist/core/artifact-writer.d.ts +2 -0
- package/dist/core/artifact-writer.js +14 -0
- package/dist/core/audit-report.d.ts +91 -0
- package/dist/core/audit-report.js +422 -0
- package/dist/core/billing-delta/billing-delta-record.d.ts +290 -0
- package/dist/core/billing-delta/billing-delta-record.js +394 -0
- package/dist/core/capture-adapter.d.ts +74 -0
- package/dist/core/capture-adapter.js +2 -0
- package/dist/core/command-runner.d.ts +29 -0
- package/dist/core/command-runner.js +96 -0
- package/dist/core/content-hash.d.ts +1 -0
- package/dist/core/content-hash.js +5 -0
- package/dist/core/cost-calculator.d.ts +6 -0
- package/dist/core/cost-calculator.js +20 -0
- package/dist/core/feedback-bundle.d.ts +159 -0
- package/dist/core/feedback-bundle.js +406 -0
- package/dist/core/openai-agents-capture.d.ts +66 -0
- package/dist/core/openai-agents-capture.js +475 -0
- package/dist/core/policy-types.d.ts +85 -0
- package/dist/core/policy-types.js +29 -0
- package/dist/core/pricing.d.ts +23 -0
- package/dist/core/pricing.js +55 -0
- package/dist/core/provider-usage/credential-redaction.d.ts +23 -0
- package/dist/core/provider-usage/credential-redaction.js +60 -0
- package/dist/core/provider-usage/provider-usage-adapter.d.ts +65 -0
- package/dist/core/provider-usage/provider-usage-adapter.js +147 -0
- package/dist/core/provider-usage/provider-usage-client.d.ts +89 -0
- package/dist/core/provider-usage/provider-usage-client.js +96 -0
- package/dist/core/report-generator.d.ts +7 -0
- package/dist/core/report-generator.js +275 -0
- package/dist/core/run-aggregator.d.ts +50 -0
- package/dist/core/run-aggregator.js +251 -0
- package/dist/core/run-labels.d.ts +52 -0
- package/dist/core/run-labels.js +80 -0
- package/dist/core/run-trace-converter.d.ts +3 -0
- package/dist/core/run-trace-converter.js +94 -0
- package/dist/core/safety-report.d.ts +52 -0
- package/dist/core/safety-report.js +440 -0
- package/dist/core/savings.d.ts +22 -0
- package/dist/core/savings.js +36 -0
- package/dist/core/session-aggregate.d.ts +211 -0
- package/dist/core/session-aggregate.js +490 -0
- package/dist/core/skill-injection-detector.d.ts +51 -0
- package/dist/core/skill-injection-detector.js +146 -0
- package/dist/core/spend-attribution.d.ts +169 -0
- package/dist/core/spend-attribution.js +611 -0
- package/dist/core/standalone-capsule-provenance.d.ts +4 -0
- package/dist/core/standalone-capsule-provenance.js +62 -0
- package/dist/core/state-capsule.d.ts +3 -0
- package/dist/core/state-capsule.js +147 -0
- package/dist/core/token-accounting.d.ts +62 -0
- package/dist/core/token-accounting.js +81 -0
- package/dist/core/token-estimator.d.ts +3 -0
- package/dist/core/token-estimator.js +18 -0
- package/dist/core/trace-adapters.d.ts +53 -0
- package/dist/core/trace-adapters.js +714 -0
- package/dist/core/trace-fingerprint.d.ts +79 -0
- package/dist/core/trace-fingerprint.js +102 -0
- package/dist/core/trace-intake.d.ts +41 -0
- package/dist/core/trace-intake.js +164 -0
- package/dist/core/trace-parser.d.ts +203 -0
- package/dist/core/trace-parser.js +143 -0
- package/dist/core/types.d.ts +238 -0
- package/dist/core/types.js +2 -0
- package/dist/core/usage-metadata.d.ts +51 -0
- package/dist/core/usage-metadata.js +122 -0
- package/dist/core/waste-detector.d.ts +8 -0
- package/dist/core/waste-detector.js +58 -0
- package/package.json +90 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { calculateCost } from "./cost-calculator.js";
|
|
2
|
+
import { isKnownModel } from "./pricing.js";
|
|
3
|
+
function formatTokenBreakdown(tokens) {
|
|
4
|
+
return `${tokens.totalTokens} total (${tokens.inputTokens} input, ${tokens.outputTokens} output)`;
|
|
5
|
+
}
|
|
6
|
+
function formatFindings(findings) {
|
|
7
|
+
if (findings.length === 0) {
|
|
8
|
+
return ["Waste findings: none detected by the conservative v0 rules."];
|
|
9
|
+
}
|
|
10
|
+
return [
|
|
11
|
+
`Waste findings: ${findings.length}`,
|
|
12
|
+
...findings.map((finding, index) => `${index + 1}. ${finding.category}: ${finding.summary} (${finding.estimatedTokens} estimated tokens; messages ${finding.messageIds.join(", ")})`)
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
function placeholderPricingWarning(model) {
|
|
16
|
+
return isKnownModel(model) ? null : "Note: cost figures use placeholder pricing — model not in local price table.";
|
|
17
|
+
}
|
|
18
|
+
function sumWastedInputTokens(findings) {
|
|
19
|
+
return findings.reduce((total, f) => total + f.estimatedTokens, 0);
|
|
20
|
+
}
|
|
21
|
+
function formatPostCompactionCostLines(model, tokens, cost, findings, cache) {
|
|
22
|
+
if (findings.length === 0) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const wastedInputTokens = sumWastedInputTokens(findings);
|
|
26
|
+
const postCompactionTokens = {
|
|
27
|
+
inputTokens: Math.max(0, tokens.inputTokens - wastedInputTokens),
|
|
28
|
+
outputTokens: tokens.outputTokens,
|
|
29
|
+
totalTokens: Math.max(0, tokens.totalTokens - wastedInputTokens)
|
|
30
|
+
};
|
|
31
|
+
const postCompactionCost = calculateCost(model, postCompactionTokens, cache);
|
|
32
|
+
const savingPerRun = cost.totalCostUsd - postCompactionCost.totalCostUsd;
|
|
33
|
+
return [
|
|
34
|
+
`Estimated cost after compaction: $${postCompactionCost.totalCostUsd.toFixed(6)} (estimated)`,
|
|
35
|
+
` Saving per run: $${savingPerRun.toFixed(6)} (estimated)`
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
function formatCacheAwareCostLines(cost, tokens, usage) {
|
|
39
|
+
const hasCacheRead = (usage?.cache_read_input_tokens ?? 0) > 0;
|
|
40
|
+
const hasCacheCreation = (usage?.cache_creation_input_tokens ?? 0) > 0;
|
|
41
|
+
const hasCacheData = hasCacheRead || hasCacheCreation;
|
|
42
|
+
if (!hasCacheData) {
|
|
43
|
+
return [`Estimated cost: $${cost.totalCostUsd.toFixed(6)} ($${cost.inputCostUsd.toFixed(6)} input, $${cost.outputCostUsd.toFixed(6)} output)`];
|
|
44
|
+
}
|
|
45
|
+
const lines = [];
|
|
46
|
+
lines.push(`Estimated cost (cache-adjusted estimate): $${cost.totalCostUsd.toFixed(6)}`);
|
|
47
|
+
// Use estimated input tokens from TokenEstimate — these match the cost figure.
|
|
48
|
+
// usage.input_tokens is the provider-reported count (different from the estimated count used for cost).
|
|
49
|
+
lines.push(` Standard input: $${cost.inputCostUsd.toFixed(6)} (${tokens.inputTokens} estimated tokens at standard rate)`);
|
|
50
|
+
lines.push(` Output: $${cost.outputCostUsd.toFixed(6)}`);
|
|
51
|
+
if (hasCacheRead && cost.cacheReadCostUsd !== undefined) {
|
|
52
|
+
lines.push(` Cache read: $${cost.cacheReadCostUsd.toFixed(6)} (${usage.cache_read_input_tokens} tokens at ~10% of input rate — estimated)`);
|
|
53
|
+
}
|
|
54
|
+
if (hasCacheCreation && cost.cacheCreationCostUsd !== undefined) {
|
|
55
|
+
lines.push(` Cache creation: $${cost.cacheCreationCostUsd.toFixed(6)} (${usage.cache_creation_input_tokens} tokens at ~125% of input rate — estimated)`);
|
|
56
|
+
}
|
|
57
|
+
return lines;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Render the report-only skill-injection advisory section. This is ADVISORY: it is
|
|
61
|
+
* distinct from the actual compaction summary and nothing is compacted. All token
|
|
62
|
+
* figures are estimated (chars/4), not billing-confirmed, not realized, not applied.
|
|
63
|
+
*/
|
|
64
|
+
function formatSkillInjectionAdvisory(advisory) {
|
|
65
|
+
if (!advisory) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const lines = [
|
|
69
|
+
"",
|
|
70
|
+
"Skill-injection repetition (ADVISORY — report-only; nothing compacted)",
|
|
71
|
+
` Label: ${advisory.estimate_label}`
|
|
72
|
+
];
|
|
73
|
+
if (advisory.skills.length === 0) {
|
|
74
|
+
lines.push(" No repeated byte-identical same-skill role:user injections detected.");
|
|
75
|
+
return lines;
|
|
76
|
+
}
|
|
77
|
+
lines.push(` Retained first copies: ${advisory.first_copy_count}`, ` Redundant byte-identical copies: ${advisory.total_redundant_byte_identical_copies}`, ` Total addressable (est, chars/4): ${advisory.total_addressable_estimated_tokens} tokens`, " Per-skill attribution (redundant byte-identical copies / est tokens):");
|
|
78
|
+
for (const skill of advisory.skills) {
|
|
79
|
+
lines.push(` - ${skill.skill_name}: ${skill.redundant_byte_identical_copies} copies, ${skill.estimated_tokens} est tokens`);
|
|
80
|
+
}
|
|
81
|
+
lines.push(` Recommendation: ${advisory.recommendation}`);
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
export function formatAnalyzeReport(trace, tokens, cost, findings = [], usage, skillInjectionAdvisory) {
|
|
85
|
+
return [
|
|
86
|
+
`Trace: ${trace.title} (${trace.id})`,
|
|
87
|
+
`Model: ${trace.model}`,
|
|
88
|
+
`Source: ${trace.source}`,
|
|
89
|
+
trace.command ? `Command: ${trace.command.command} ${trace.command.args.join(" ")}`.trim() : null,
|
|
90
|
+
trace.durationMs === undefined ? null : `Duration: ${trace.durationMs} ms`,
|
|
91
|
+
trace.exitCode === undefined ? null : `Exit code: ${trace.exitCode}`,
|
|
92
|
+
`Messages: ${trace.messages.length}`,
|
|
93
|
+
`Estimated tokens: ${formatTokenBreakdown(tokens)}`,
|
|
94
|
+
placeholderPricingWarning(trace.model),
|
|
95
|
+
...formatCacheAwareCostLines(cost, tokens, usage),
|
|
96
|
+
...formatPostCompactionCostLines(trace.model, tokens, cost, findings, {
|
|
97
|
+
cacheReadTokens: usage?.cache_read_input_tokens,
|
|
98
|
+
cacheCreationTokens: usage?.cache_creation_input_tokens
|
|
99
|
+
}),
|
|
100
|
+
...formatFindings(findings),
|
|
101
|
+
"Suggested compaction: remove duplicate tool outputs only when an earlier identical copy remains in the trace.",
|
|
102
|
+
...formatSkillInjectionAdvisory(skillInjectionAdvisory)
|
|
103
|
+
].filter((line) => line !== null).join("\n");
|
|
104
|
+
}
|
|
105
|
+
export function formatCompactionReport(report) {
|
|
106
|
+
return [
|
|
107
|
+
`Run: ${report.run_id}`,
|
|
108
|
+
`Trace: ${report.trace_title}`,
|
|
109
|
+
`Model: ${report.model}`,
|
|
110
|
+
placeholderPricingWarning(report.model),
|
|
111
|
+
`Original input tokens: ${report.original_input_tokens}`,
|
|
112
|
+
`Compacted input tokens: ${report.compacted_input_tokens}`,
|
|
113
|
+
`${report.savings_scope === "policy_level" ? "Policy-level tokens saved" : "Tokens saved"}: ${report.tokens_saved} (${report.percent_reduction.toFixed(2)}% reduction)`,
|
|
114
|
+
`Cost before per run: $${report.cost_before_per_run.toFixed(6)} (estimated)`,
|
|
115
|
+
`Cost after per run: $${report.cost_after_per_run.toFixed(6)} (estimated)`,
|
|
116
|
+
`Savings per run: $${report.saving_per_run.toFixed(6)} (estimated)`,
|
|
117
|
+
`Policy: ${report.policy_name}`,
|
|
118
|
+
`Waste pattern: ${report.waste_pattern ?? "none"}`,
|
|
119
|
+
`Compacted messages: ${report.compacted_message_ids.length === 0 ? "none" : report.compacted_message_ids.join(", ")}`
|
|
120
|
+
].filter((line) => line !== null).join("\n");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Render the value-proof tier classification for the human-facing report. This
|
|
124
|
+
* classifies the already-reported token/cost deltas against the four ROADMAP
|
|
125
|
+
* "MVP value path" claims (strongest last, never conflated). It adds no new
|
|
126
|
+
* numbers and makes no new/stronger claim — it states plainly that tier 3
|
|
127
|
+
* (billing-confirmed) is NOT claimed and tier 4 (fixed-plan) is NOT quantified.
|
|
128
|
+
*/
|
|
129
|
+
function formatValueProofLines(report) {
|
|
130
|
+
const vp = report.value_proof;
|
|
131
|
+
if (!vp) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
return [
|
|
135
|
+
"",
|
|
136
|
+
"## Value Proof (ROADMAP tiers — strongest last, not conflated)",
|
|
137
|
+
"",
|
|
138
|
+
`- Tier 1 — ${vp.trace_token_reduction.claim}: ${vp.trace_token_reduction.tokens_saved} tokens (${vp.trace_token_reduction.percent_reduction.toFixed(2)}% reduction).`,
|
|
139
|
+
`- Tier 2 — ${vp.estimated_provider_cost_reduction.claim}: $${vp.estimated_provider_cost_reduction.saving_per_run_usd.toFixed(6)}/run (estimated).`,
|
|
140
|
+
`- Tier 3 — ${vp.billing_confirmed_savings.claim}: ${vp.billing_confirmed_savings.note}`,
|
|
141
|
+
`- Tier 4 — ${vp.fixed_plan_workflow_extension_value.claim}: ${vp.fixed_plan_workflow_extension_value.note}`
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Render the concise "Where spend came from (estimated)" section for report.md. This
|
|
146
|
+
* reuses the spend_by_source summary already computed via the spend-attribution module.
|
|
147
|
+
* Figures are LOCAL ESTIMATES (chars/4 trace tokens / price-table cost) — clearly NOT
|
|
148
|
+
* billing-confirmed and NOT realized savings. Consistent with the value-proof tiers
|
|
149
|
+
* (tier-1 trace-token estimates / tier-2 price-table cost estimates).
|
|
150
|
+
*/
|
|
151
|
+
function formatSpendBySourceLines(report) {
|
|
152
|
+
const spend = report.spend_by_source;
|
|
153
|
+
if (!spend) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
const roleRows = spend.top_roles.length === 0
|
|
157
|
+
? ["- No role/category spend attributed."]
|
|
158
|
+
: spend.top_roles.map((role) => `- ${role.name}: ${role.estimated_tokens} est tokens ($${role.estimated_cost_usd.toFixed(6)} est)`);
|
|
159
|
+
const toolRows = spend.top_tool_outputs.length === 0
|
|
160
|
+
? ["- No repeated tool-output spend attributed."]
|
|
161
|
+
: spend.top_tool_outputs.map((tool) => `- ${tool.tool_name}: ${tool.estimated_tokens} est tokens across ${tool.repeated_count} output(s) ($${tool.estimated_cost_usd.toFixed(6)} est)`);
|
|
162
|
+
return [
|
|
163
|
+
"",
|
|
164
|
+
"## Where Spend Came From (estimated)",
|
|
165
|
+
"",
|
|
166
|
+
"> ESTIMATED: chars/4 trace-token heuristic and price-table cost. These show where estimated context spend comes from — NOT billing-confirmed, NOT realized savings.",
|
|
167
|
+
"",
|
|
168
|
+
"Top sources by est tokens — roles/categories:",
|
|
169
|
+
...roleRows,
|
|
170
|
+
"",
|
|
171
|
+
"Top sources by est tokens — repeated tool outputs:",
|
|
172
|
+
...toolRows,
|
|
173
|
+
"",
|
|
174
|
+
spend.top_policy_candidate
|
|
175
|
+
? `Top policy candidate: ${spend.top_policy_candidate.policy_name} (${spend.top_policy_candidate.estimated_tokens_saved} est tokens saved, $${spend.top_policy_candidate.estimated_saving_per_run_usd.toFixed(6)} est/run)`
|
|
176
|
+
: "Top policy candidate: none"
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
export function formatCompactionMarkdownReport(report, policy) {
|
|
180
|
+
const pricingNote = placeholderPricingWarning(report.model);
|
|
181
|
+
return [
|
|
182
|
+
`# Compaction Report: ${report.trace_title}`,
|
|
183
|
+
"",
|
|
184
|
+
"## Summary",
|
|
185
|
+
"",
|
|
186
|
+
`- Run ID: ${report.run_id}`,
|
|
187
|
+
`- Model: ${report.model}`,
|
|
188
|
+
`- Artifact version: ${report.artifact_version}`,
|
|
189
|
+
`- Created at: ${report.created_at}`,
|
|
190
|
+
`- Generated at: ${report.generated_at}`,
|
|
191
|
+
pricingNote ? `\n> ${pricingNote}` : null,
|
|
192
|
+
"",
|
|
193
|
+
"## Savings",
|
|
194
|
+
"",
|
|
195
|
+
`- Original input tokens: ${report.original_input_tokens}`,
|
|
196
|
+
`- Compacted input tokens: ${report.compacted_input_tokens}`,
|
|
197
|
+
`${report.savings_scope === "policy_level" ? "- Policy-level tokens saved" : "- Tokens saved"}: ${report.tokens_saved}`,
|
|
198
|
+
`- Percent reduction: ${report.percent_reduction.toFixed(2)}%`,
|
|
199
|
+
`- Cost before per run: $${report.cost_before_per_run.toFixed(6)} (estimated)`,
|
|
200
|
+
`- Cost after per run: $${report.cost_after_per_run.toFixed(6)} (estimated)`,
|
|
201
|
+
`- Saving per run: $${report.saving_per_run.toFixed(6)} (estimated)`,
|
|
202
|
+
...formatValueProofLines(report),
|
|
203
|
+
...formatSpendBySourceLines(report),
|
|
204
|
+
"",
|
|
205
|
+
"## Waste Pattern",
|
|
206
|
+
"",
|
|
207
|
+
`- Policy: ${report.policy_name}`,
|
|
208
|
+
`- Pattern: ${report.waste_pattern ?? "none"}`,
|
|
209
|
+
`- Source message: ${report.source_message_id ?? "none"}`,
|
|
210
|
+
`- Repeated finding message count: ${report.repeated_count}`,
|
|
211
|
+
`- Compacted messages: ${report.compacted_message_ids.length === 0 ? "none" : report.compacted_message_ids.join(", ")}`,
|
|
212
|
+
"",
|
|
213
|
+
"## Policy",
|
|
214
|
+
"",
|
|
215
|
+
`- Version: ${policy.policy_version}`,
|
|
216
|
+
`- Trigger: ${policy.trigger}`,
|
|
217
|
+
`- Condition: ${policy.condition}`,
|
|
218
|
+
`- Action: ${policy.action}`,
|
|
219
|
+
`- Expected savings: ${policy.expected_savings}`,
|
|
220
|
+
"",
|
|
221
|
+
"## Safety Guarantees",
|
|
222
|
+
"",
|
|
223
|
+
...policy.safety_guarantees.map((guarantee) => `- ${guarantee}`),
|
|
224
|
+
"",
|
|
225
|
+
"## Risk Notes",
|
|
226
|
+
"",
|
|
227
|
+
...policy.risk_notes.map((note) => `- ${note}`)
|
|
228
|
+
].filter((line) => line !== null).join("\n");
|
|
229
|
+
}
|
|
230
|
+
export function formatPrCommentReport(report, policy, artifactNames) {
|
|
231
|
+
const riskOrSafetyNote = policy.risk_notes[0] ?? policy.safety_guarantees[0] ?? "Review the compacted trace before applying this policy.";
|
|
232
|
+
const pricingNote = placeholderPricingWarning(report.model);
|
|
233
|
+
return [
|
|
234
|
+
"## compaction.dev summary",
|
|
235
|
+
"",
|
|
236
|
+
`Trace title: ${report.trace_title}`,
|
|
237
|
+
`Model: ${report.model}`,
|
|
238
|
+
pricingNote ? `> ${pricingNote}` : null,
|
|
239
|
+
"",
|
|
240
|
+
"### Token savings",
|
|
241
|
+
"",
|
|
242
|
+
`- Before input tokens: ${report.original_input_tokens}`,
|
|
243
|
+
`- After input tokens: ${report.compacted_input_tokens}`,
|
|
244
|
+
`${report.savings_scope === "policy_level" ? "- Policy-level tokens saved" : "- Tokens saved"}: ${report.tokens_saved}`,
|
|
245
|
+
`- Percent reduction: ${report.percent_reduction.toFixed(2)}%`,
|
|
246
|
+
"",
|
|
247
|
+
"### Cost impact",
|
|
248
|
+
"",
|
|
249
|
+
`- Cost before per run: $${report.cost_before_per_run.toFixed(6)} (estimated)`,
|
|
250
|
+
`- Cost after per run: $${report.cost_after_per_run.toFixed(6)} (estimated)`,
|
|
251
|
+
`- Saving per run: $${report.saving_per_run.toFixed(6)} (estimated)`,
|
|
252
|
+
...(report.value_proof
|
|
253
|
+
? [
|
|
254
|
+
"",
|
|
255
|
+
"### Value-proof tiers (strongest last, not conflated)",
|
|
256
|
+
"",
|
|
257
|
+
`- Tier 1 — ${report.value_proof.trace_token_reduction.claim}.`,
|
|
258
|
+
`- Tier 2 — ${report.value_proof.estimated_provider_cost_reduction.claim}.`,
|
|
259
|
+
`- Tier 3 — billing-confirmed savings: NOT claimed (no provider usage/billing delta measured).`,
|
|
260
|
+
`- Tier 4 — fixed-plan workflow-extension value: NOT quantified here.`
|
|
261
|
+
]
|
|
262
|
+
: []),
|
|
263
|
+
"",
|
|
264
|
+
"### Policy and safety",
|
|
265
|
+
"",
|
|
266
|
+
`- Policy applied: ${policy.policy_name}`,
|
|
267
|
+
`- Waste pattern: ${report.waste_pattern ?? "none"}`,
|
|
268
|
+
`- Risk level or safety note: ${riskOrSafetyNote}`,
|
|
269
|
+
"",
|
|
270
|
+
"### Generated artifacts",
|
|
271
|
+
"",
|
|
272
|
+
...artifactNames.map((artifactName) => `- ${artifactName}`)
|
|
273
|
+
].filter((line) => line !== null).join("\n");
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=report-generator.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CompactionReport, WasteFinding } from "./types.js";
|
|
2
|
+
export interface RunSummaryBucket {
|
|
3
|
+
total_runs: number;
|
|
4
|
+
total_original_input_tokens: number;
|
|
5
|
+
total_compacted_input_tokens: number;
|
|
6
|
+
total_tokens_saved: number;
|
|
7
|
+
total_cost_before_per_run: number;
|
|
8
|
+
total_cost_after_per_run: number;
|
|
9
|
+
total_saving_per_run: number;
|
|
10
|
+
}
|
|
11
|
+
export interface TopSavingsRun {
|
|
12
|
+
run_id: string;
|
|
13
|
+
trace_title: string;
|
|
14
|
+
report_path: string;
|
|
15
|
+
tokens_saved: number;
|
|
16
|
+
percent_reduction: number;
|
|
17
|
+
saving_per_run: number;
|
|
18
|
+
policy_name: string;
|
|
19
|
+
waste_pattern: WasteFinding["category"] | null;
|
|
20
|
+
}
|
|
21
|
+
export interface SkippedReportFile {
|
|
22
|
+
path: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}
|
|
25
|
+
export interface RunSummary extends RunSummaryBucket {
|
|
26
|
+
average_percent_reduction: number;
|
|
27
|
+
savings_by_policy: Record<string, RunSummaryBucket>;
|
|
28
|
+
savings_by_waste_pattern: Record<string, RunSummaryBucket>;
|
|
29
|
+
top_savings_runs: TopSavingsRun[];
|
|
30
|
+
skipped_files: SkippedReportFile[];
|
|
31
|
+
generated_at: string;
|
|
32
|
+
source_glob: string;
|
|
33
|
+
}
|
|
34
|
+
interface LoadedReport {
|
|
35
|
+
path: string;
|
|
36
|
+
report: CompactionReport;
|
|
37
|
+
}
|
|
38
|
+
export declare function validateCompactionReport(value: unknown): {
|
|
39
|
+
report?: CompactionReport;
|
|
40
|
+
reason?: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function readCompactionRunReports(runsDirectory?: string): Promise<{
|
|
43
|
+
reports: LoadedReport[];
|
|
44
|
+
skippedFiles: SkippedReportFile[];
|
|
45
|
+
}>;
|
|
46
|
+
export declare function aggregateCompactionReports(reports: LoadedReport[], skippedFiles?: SkippedReportFile[], generatedAt?: string, sourceGlob?: string): RunSummary;
|
|
47
|
+
export declare function createRunSummary(runsDirectory?: string): Promise<RunSummary>;
|
|
48
|
+
export declare function formatRunSummary(summary: RunSummary): string;
|
|
49
|
+
export declare function formatRunSummaryMarkdown(summary: RunSummary): string;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const REQUIRED_STRING_FIELDS = ["run_id", "trace_title", "policy_name"];
|
|
4
|
+
const REQUIRED_NUMBER_FIELDS = [
|
|
5
|
+
"original_input_tokens",
|
|
6
|
+
"compacted_input_tokens",
|
|
7
|
+
"tokens_saved",
|
|
8
|
+
"percent_reduction",
|
|
9
|
+
"cost_before_per_run",
|
|
10
|
+
"cost_after_per_run",
|
|
11
|
+
"saving_per_run"
|
|
12
|
+
];
|
|
13
|
+
function emptyBucket() {
|
|
14
|
+
return {
|
|
15
|
+
total_runs: 0,
|
|
16
|
+
total_original_input_tokens: 0,
|
|
17
|
+
total_compacted_input_tokens: 0,
|
|
18
|
+
total_tokens_saved: 0,
|
|
19
|
+
total_cost_before_per_run: 0,
|
|
20
|
+
total_cost_after_per_run: 0,
|
|
21
|
+
total_saving_per_run: 0
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function roundTo(value, digits) {
|
|
25
|
+
const factor = 10 ** digits;
|
|
26
|
+
return Math.round((value + Number.EPSILON) * factor) / factor;
|
|
27
|
+
}
|
|
28
|
+
function addReportToBucket(bucket, report) {
|
|
29
|
+
bucket.total_runs += 1;
|
|
30
|
+
bucket.total_original_input_tokens += report.original_input_tokens;
|
|
31
|
+
bucket.total_compacted_input_tokens += report.compacted_input_tokens;
|
|
32
|
+
bucket.total_tokens_saved += report.tokens_saved;
|
|
33
|
+
bucket.total_cost_before_per_run += report.cost_before_per_run;
|
|
34
|
+
bucket.total_cost_after_per_run += report.cost_after_per_run;
|
|
35
|
+
bucket.total_saving_per_run += report.saving_per_run;
|
|
36
|
+
}
|
|
37
|
+
function normalizeBucket(bucket) {
|
|
38
|
+
bucket.total_cost_before_per_run = roundTo(bucket.total_cost_before_per_run, 6);
|
|
39
|
+
bucket.total_cost_after_per_run = roundTo(bucket.total_cost_after_per_run, 6);
|
|
40
|
+
bucket.total_saving_per_run = roundTo(bucket.total_saving_per_run, 6);
|
|
41
|
+
}
|
|
42
|
+
function isFiniteNumber(value) {
|
|
43
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
44
|
+
}
|
|
45
|
+
export function validateCompactionReport(value) {
|
|
46
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
47
|
+
return { reason: "report is not a JSON object" };
|
|
48
|
+
}
|
|
49
|
+
const report = value;
|
|
50
|
+
for (const field of REQUIRED_STRING_FIELDS) {
|
|
51
|
+
if (typeof report[field] !== "string" || report[field].trim() === "") {
|
|
52
|
+
return { reason: `missing or invalid ${field}` };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const field of REQUIRED_NUMBER_FIELDS) {
|
|
56
|
+
if (!isFiniteNumber(report[field])) {
|
|
57
|
+
return { reason: `missing or invalid ${field}` };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (report.waste_pattern !== null && report.waste_pattern !== undefined && typeof report.waste_pattern !== "string") {
|
|
61
|
+
return { reason: "missing or invalid waste_pattern" };
|
|
62
|
+
}
|
|
63
|
+
return { report: report };
|
|
64
|
+
}
|
|
65
|
+
async function findReportPaths(runsDirectory) {
|
|
66
|
+
try {
|
|
67
|
+
const entries = await readdir(runsDirectory, { withFileTypes: true });
|
|
68
|
+
return entries
|
|
69
|
+
.filter((entry) => entry.isDirectory())
|
|
70
|
+
.map((entry) => join(runsDirectory, entry.name, "report.json"))
|
|
71
|
+
.sort();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "unknown";
|
|
75
|
+
if (code === "ENOENT") {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function readCompactionRunReports(runsDirectory = ".compaction/runs") {
|
|
82
|
+
const reportPaths = await findReportPaths(runsDirectory);
|
|
83
|
+
const reports = [];
|
|
84
|
+
const skippedFiles = [];
|
|
85
|
+
for (const reportPath of reportPaths) {
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(await readFile(reportPath, "utf8"));
|
|
88
|
+
const validation = validateCompactionReport(parsed);
|
|
89
|
+
if (validation.report === undefined) {
|
|
90
|
+
skippedFiles.push({ path: reportPath, reason: validation.reason ?? "invalid report" });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
reports.push({ path: reportPath, report: validation.report });
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
97
|
+
const reason = code === "ENOENT" ? "report.json not found" : error instanceof SyntaxError ? "invalid JSON" : "could not read report";
|
|
98
|
+
skippedFiles.push({ path: reportPath, reason });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { reports, skippedFiles };
|
|
102
|
+
}
|
|
103
|
+
export function aggregateCompactionReports(reports, skippedFiles = [], generatedAt = new Date().toISOString(), sourceGlob = ".compaction/runs/*/report.json") {
|
|
104
|
+
const summary = {
|
|
105
|
+
...emptyBucket(),
|
|
106
|
+
average_percent_reduction: 0,
|
|
107
|
+
savings_by_policy: {},
|
|
108
|
+
savings_by_waste_pattern: {},
|
|
109
|
+
top_savings_runs: [],
|
|
110
|
+
skipped_files: skippedFiles,
|
|
111
|
+
generated_at: generatedAt,
|
|
112
|
+
source_glob: sourceGlob
|
|
113
|
+
};
|
|
114
|
+
let percentReductionTotal = 0;
|
|
115
|
+
for (const { path, report } of reports) {
|
|
116
|
+
addReportToBucket(summary, report);
|
|
117
|
+
percentReductionTotal += report.percent_reduction;
|
|
118
|
+
const policyBucket = (summary.savings_by_policy[report.policy_name] ??= emptyBucket());
|
|
119
|
+
addReportToBucket(policyBucket, report);
|
|
120
|
+
const wastePattern = report.waste_pattern ?? "none";
|
|
121
|
+
const wasteBucket = (summary.savings_by_waste_pattern[wastePattern] ??= emptyBucket());
|
|
122
|
+
addReportToBucket(wasteBucket, report);
|
|
123
|
+
summary.top_savings_runs.push({
|
|
124
|
+
run_id: report.run_id,
|
|
125
|
+
trace_title: report.trace_title,
|
|
126
|
+
report_path: path,
|
|
127
|
+
tokens_saved: report.tokens_saved,
|
|
128
|
+
percent_reduction: report.percent_reduction,
|
|
129
|
+
saving_per_run: report.saving_per_run,
|
|
130
|
+
policy_name: report.policy_name,
|
|
131
|
+
waste_pattern: report.waste_pattern
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
summary.average_percent_reduction = reports.length === 0 ? 0 : roundTo(percentReductionTotal / reports.length, 2);
|
|
135
|
+
normalizeBucket(summary);
|
|
136
|
+
for (const bucket of Object.values(summary.savings_by_policy)) {
|
|
137
|
+
normalizeBucket(bucket);
|
|
138
|
+
}
|
|
139
|
+
for (const bucket of Object.values(summary.savings_by_waste_pattern)) {
|
|
140
|
+
normalizeBucket(bucket);
|
|
141
|
+
}
|
|
142
|
+
summary.top_savings_runs = summary.top_savings_runs
|
|
143
|
+
.sort((left, right) => {
|
|
144
|
+
if (right.tokens_saved !== left.tokens_saved) {
|
|
145
|
+
return right.tokens_saved - left.tokens_saved;
|
|
146
|
+
}
|
|
147
|
+
return right.saving_per_run - left.saving_per_run;
|
|
148
|
+
})
|
|
149
|
+
.slice(0, 5);
|
|
150
|
+
return summary;
|
|
151
|
+
}
|
|
152
|
+
export async function createRunSummary(runsDirectory = ".compaction/runs") {
|
|
153
|
+
const { reports, skippedFiles } = await readCompactionRunReports(runsDirectory);
|
|
154
|
+
return aggregateCompactionReports(reports, skippedFiles, new Date().toISOString(), `${runsDirectory}/*/report.json`);
|
|
155
|
+
}
|
|
156
|
+
function formatCurrency(value) {
|
|
157
|
+
return `$${value.toFixed(6)}`;
|
|
158
|
+
}
|
|
159
|
+
function formatBucketLines(bucket) {
|
|
160
|
+
return [
|
|
161
|
+
`runs: ${bucket.total_runs}`,
|
|
162
|
+
`original input tokens: ${bucket.total_original_input_tokens}`,
|
|
163
|
+
`compacted input tokens: ${bucket.total_compacted_input_tokens}`,
|
|
164
|
+
`tokens saved: ${bucket.total_tokens_saved}`,
|
|
165
|
+
`cost before per run: ${formatCurrency(bucket.total_cost_before_per_run)}`,
|
|
166
|
+
`cost after per run: ${formatCurrency(bucket.total_cost_after_per_run)}`,
|
|
167
|
+
`saving per run: ${formatCurrency(bucket.total_saving_per_run)}`
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
export function formatRunSummary(summary) {
|
|
171
|
+
const policyLines = Object.entries(summary.savings_by_policy).flatMap(([policyName, bucket]) => [
|
|
172
|
+
`- ${policyName}`,
|
|
173
|
+
...formatBucketLines(bucket).map((line) => ` - ${line}`)
|
|
174
|
+
]);
|
|
175
|
+
const wastePatternLines = Object.entries(summary.savings_by_waste_pattern).flatMap(([wastePattern, bucket]) => [
|
|
176
|
+
`- ${wastePattern}`,
|
|
177
|
+
...formatBucketLines(bucket).map((line) => ` - ${line}`)
|
|
178
|
+
]);
|
|
179
|
+
const topRunLines = summary.top_savings_runs.map((run, index) => `${index + 1}. ${run.run_id} (${run.trace_title}) — ${run.tokens_saved} tokens saved, ${run.percent_reduction.toFixed(2)}% reduction, ${formatCurrency(run.saving_per_run)} estimated saving per run`);
|
|
180
|
+
const skippedLines = summary.skipped_files.map((file) => `- ${file.path}: ${file.reason}`);
|
|
181
|
+
return [
|
|
182
|
+
"compaction summary",
|
|
183
|
+
`Source: ${summary.source_glob}`,
|
|
184
|
+
`Generated at: ${summary.generated_at}`,
|
|
185
|
+
"",
|
|
186
|
+
"Totals",
|
|
187
|
+
...formatBucketLines(summary).map((line) => `- ${line}`),
|
|
188
|
+
`- average percent reduction: ${summary.average_percent_reduction.toFixed(2)}%`,
|
|
189
|
+
"",
|
|
190
|
+
"Savings by policy",
|
|
191
|
+
...(policyLines.length === 0 ? ["- none"] : policyLines),
|
|
192
|
+
"",
|
|
193
|
+
"Savings by waste pattern",
|
|
194
|
+
...(wastePatternLines.length === 0 ? ["- none"] : wastePatternLines),
|
|
195
|
+
"",
|
|
196
|
+
"Top savings runs",
|
|
197
|
+
...(topRunLines.length === 0 ? ["- none"] : topRunLines),
|
|
198
|
+
"",
|
|
199
|
+
"Skipped files",
|
|
200
|
+
...(skippedLines.length === 0 ? ["- none"] : skippedLines)
|
|
201
|
+
].join("\n");
|
|
202
|
+
}
|
|
203
|
+
export function formatRunSummaryMarkdown(summary) {
|
|
204
|
+
const policyRows = Object.entries(summary.savings_by_policy).map(([policyName, bucket]) => `| ${policyName} | ${bucket.total_runs} | ${bucket.total_tokens_saved} | ${formatCurrency(bucket.total_saving_per_run)} |`);
|
|
205
|
+
const wastePatternRows = Object.entries(summary.savings_by_waste_pattern).map(([wastePattern, bucket]) => `| ${wastePattern} | ${bucket.total_runs} | ${bucket.total_tokens_saved} | ${formatCurrency(bucket.total_saving_per_run)} |`);
|
|
206
|
+
const topRunRows = summary.top_savings_runs.map((run) => `| ${run.run_id} | ${run.trace_title} | ${run.tokens_saved} | ${run.percent_reduction.toFixed(2)}% | ${formatCurrency(run.saving_per_run)} |`);
|
|
207
|
+
const skippedRows = summary.skipped_files.map((file) => `| ${file.path} | ${file.reason} |`);
|
|
208
|
+
return [
|
|
209
|
+
"# Compaction Run Summary",
|
|
210
|
+
"",
|
|
211
|
+
`- Source: ${summary.source_glob}`,
|
|
212
|
+
`- Generated at: ${summary.generated_at}`,
|
|
213
|
+
"",
|
|
214
|
+
"## Totals",
|
|
215
|
+
"",
|
|
216
|
+
`- Total runs: ${summary.total_runs}`,
|
|
217
|
+
`- Total original input tokens: ${summary.total_original_input_tokens}`,
|
|
218
|
+
`- Total compacted input tokens: ${summary.total_compacted_input_tokens}`,
|
|
219
|
+
`- Total tokens saved: ${summary.total_tokens_saved}`,
|
|
220
|
+
`- Average percent reduction: ${summary.average_percent_reduction.toFixed(2)}%`,
|
|
221
|
+
`- Total cost before per run: ${formatCurrency(summary.total_cost_before_per_run)}`,
|
|
222
|
+
`- Total cost after per run: ${formatCurrency(summary.total_cost_after_per_run)}`,
|
|
223
|
+
`- Total saving per run: ${formatCurrency(summary.total_saving_per_run)}`,
|
|
224
|
+
`- Saving figures are estimated and summed over the ${summary.total_runs} recorded run${summary.total_runs === 1 ? "" : "s"}.`,
|
|
225
|
+
"",
|
|
226
|
+
"## Savings by policy",
|
|
227
|
+
"",
|
|
228
|
+
"| Policy | Runs | Tokens saved | Saving per run |",
|
|
229
|
+
"| --- | ---: | ---: | ---: |",
|
|
230
|
+
...(policyRows.length === 0 ? ["| none | 0 | 0 | $0.000000 |"] : policyRows),
|
|
231
|
+
"",
|
|
232
|
+
"## Savings by waste pattern",
|
|
233
|
+
"",
|
|
234
|
+
"| Waste pattern | Runs | Tokens saved | Saving per run |",
|
|
235
|
+
"| --- | ---: | ---: | ---: |",
|
|
236
|
+
...(wastePatternRows.length === 0 ? ["| none | 0 | 0 | $0.000000 |"] : wastePatternRows),
|
|
237
|
+
"",
|
|
238
|
+
"## Top savings runs",
|
|
239
|
+
"",
|
|
240
|
+
"| Run ID | Trace title | Tokens saved | Percent reduction | Saving per run |",
|
|
241
|
+
"| --- | --- | ---: | ---: | ---: |",
|
|
242
|
+
...(topRunRows.length === 0 ? ["| none | none | 0 | 0.00% | $0.000000 |"] : topRunRows),
|
|
243
|
+
"",
|
|
244
|
+
"## Skipped files",
|
|
245
|
+
"",
|
|
246
|
+
"| Path | Reason |",
|
|
247
|
+
"| --- | --- |",
|
|
248
|
+
...(skippedRows.length === 0 ? ["| none | none |"] : skippedRows)
|
|
249
|
+
].join("\n");
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=run-aggregator.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-only run labels (Strong-MVP Track E, task 3).
|
|
3
|
+
*
|
|
4
|
+
* A tiny, OPTIONAL, file-based tag a developer can attach to a recorded run so the
|
|
5
|
+
* multi-session aggregate can group / filter / attribute it. These labels are written
|
|
6
|
+
* to `.compaction/runs/<run>/run-labels.json` — they are LOCAL ONLY and are NEVER
|
|
7
|
+
* uploaded. They carry no trace content: only short operator-supplied strings
|
|
8
|
+
* (project / workflow / provider-runtime / a free-form user label / an explicit
|
|
9
|
+
* session id).
|
|
10
|
+
*
|
|
11
|
+
* Honesty: a label is metadata the operator typed. It does NOT change any token, cost,
|
|
12
|
+
* savings, or verification figure, and it carries no evidence weight — it is purely an
|
|
13
|
+
* organizing tag for the local rollup. Provider/runtime supplied as a LABEL is operator-
|
|
14
|
+
* asserted, never provider-verified, and is kept distinct from the token-source evidence
|
|
15
|
+
* label (provider-reported vs local-estimate) that the token accounting carries.
|
|
16
|
+
*/
|
|
17
|
+
/** Marker used wherever a label was not supplied rather than guessed. */
|
|
18
|
+
export declare const NO_LABEL: "unlabeled";
|
|
19
|
+
/** Schema version for the run-labels file (additive changes bump the minor). */
|
|
20
|
+
export declare const RUN_LABELS_VERSION = "1.0.0";
|
|
21
|
+
/** The exact, bounded set of label fields. No free-form map — only these keys exist. */
|
|
22
|
+
export interface RunLabels {
|
|
23
|
+
/** Local-only optional label fields. Absent ≡ unlabeled; never inferred. */
|
|
24
|
+
project?: string;
|
|
25
|
+
workflow?: string;
|
|
26
|
+
/** Operator-ASSERTED provider/runtime (NOT provider-verified). */
|
|
27
|
+
provider?: string;
|
|
28
|
+
/** Free-form local user label. */
|
|
29
|
+
user_label?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Session id used to group multiple runs into one session. When absent, the run is
|
|
32
|
+
* grouped into its own single-run session keyed by run id (every run is at least its
|
|
33
|
+
* own session). Never inferred from content.
|
|
34
|
+
*/
|
|
35
|
+
session?: string;
|
|
36
|
+
}
|
|
37
|
+
/** The on-disk run-labels artifact (local file only). */
|
|
38
|
+
export interface RunLabelsFile extends RunLabels {
|
|
39
|
+
labels_version: string;
|
|
40
|
+
written_at: string;
|
|
41
|
+
/** Constant honesty note persisted into the file. */
|
|
42
|
+
note: string;
|
|
43
|
+
}
|
|
44
|
+
export declare const RUN_LABELS_NOTE: string;
|
|
45
|
+
/** Normalize a supplied label set to the bounded, trimmed shape (drops empty/invalid fields). */
|
|
46
|
+
export declare function normalizeRunLabels(labels: RunLabels): RunLabels;
|
|
47
|
+
/** True when at least one label field is present after normalization. */
|
|
48
|
+
export declare function hasAnyLabel(labels: RunLabels): boolean;
|
|
49
|
+
/** Build the on-disk artifact from a supplied (already-normalizable) label set. */
|
|
50
|
+
export declare function buildRunLabelsFile(labels: RunLabels, writtenAt?: string): RunLabelsFile;
|
|
51
|
+
/** Parse a value read from a run-labels.json into the bounded label set (tolerant; drops junk). */
|
|
52
|
+
export declare function parseRunLabels(value: unknown): RunLabels;
|