@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.
Files changed (143) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +319 -0
  3. package/dist/cli/commands/adapter.d.ts +2 -0
  4. package/dist/cli/commands/adapter.js +49 -0
  5. package/dist/cli/commands/aggregate.d.ts +2 -0
  6. package/dist/cli/commands/aggregate.js +60 -0
  7. package/dist/cli/commands/analyze.d.ts +2 -0
  8. package/dist/cli/commands/analyze.js +55 -0
  9. package/dist/cli/commands/apply-context.d.ts +12 -0
  10. package/dist/cli/commands/apply-context.js +80 -0
  11. package/dist/cli/commands/apply.d.ts +2 -0
  12. package/dist/cli/commands/apply.js +65 -0
  13. package/dist/cli/commands/approve.d.ts +2 -0
  14. package/dist/cli/commands/approve.js +55 -0
  15. package/dist/cli/commands/audit.d.ts +2 -0
  16. package/dist/cli/commands/audit.js +63 -0
  17. package/dist/cli/commands/billing-delta.d.ts +2 -0
  18. package/dist/cli/commands/billing-delta.js +246 -0
  19. package/dist/cli/commands/capture-claude-code.d.ts +19 -0
  20. package/dist/cli/commands/capture-claude-code.js +198 -0
  21. package/dist/cli/commands/capture-provider-usage.d.ts +35 -0
  22. package/dist/cli/commands/capture-provider-usage.js +113 -0
  23. package/dist/cli/commands/capture.d.ts +2 -0
  24. package/dist/cli/commands/capture.js +76 -0
  25. package/dist/cli/commands/compact.d.ts +2 -0
  26. package/dist/cli/commands/compact.js +206 -0
  27. package/dist/cli/commands/eval.d.ts +2 -0
  28. package/dist/cli/commands/eval.js +66 -0
  29. package/dist/cli/commands/export-session-seed.d.ts +11 -0
  30. package/dist/cli/commands/export-session-seed.js +42 -0
  31. package/dist/cli/commands/feedback.d.ts +2 -0
  32. package/dist/cli/commands/feedback.js +233 -0
  33. package/dist/cli/commands/import.d.ts +2 -0
  34. package/dist/cli/commands/import.js +60 -0
  35. package/dist/cli/commands/init.d.ts +2 -0
  36. package/dist/cli/commands/init.js +216 -0
  37. package/dist/cli/commands/integrations.d.ts +2 -0
  38. package/dist/cli/commands/integrations.js +29 -0
  39. package/dist/cli/commands/launch-treatment-session.d.ts +14 -0
  40. package/dist/cli/commands/launch-treatment-session.js +50 -0
  41. package/dist/cli/commands/optimize.d.ts +2 -0
  42. package/dist/cli/commands/optimize.js +33 -0
  43. package/dist/cli/commands/recommend.d.ts +4 -0
  44. package/dist/cli/commands/recommend.js +66 -0
  45. package/dist/cli/commands/review.d.ts +2 -0
  46. package/dist/cli/commands/review.js +60 -0
  47. package/dist/cli/commands/run.d.ts +2 -0
  48. package/dist/cli/commands/run.js +49 -0
  49. package/dist/cli/commands/spend.d.ts +2 -0
  50. package/dist/cli/commands/spend.js +24 -0
  51. package/dist/cli/commands/summary.d.ts +2 -0
  52. package/dist/cli/commands/summary.js +19 -0
  53. package/dist/cli/engine-degrade.d.ts +40 -0
  54. package/dist/cli/engine-degrade.js +59 -0
  55. package/dist/cli/index.d.ts +2 -0
  56. package/dist/cli/index.js +59 -0
  57. package/dist/core/adapter-validation.d.ts +32 -0
  58. package/dist/core/adapter-validation.js +185 -0
  59. package/dist/core/adapters/claude-code-adapter.d.ts +51 -0
  60. package/dist/core/adapters/claude-code-adapter.js +539 -0
  61. package/dist/core/adapters/claude-code-discovery.d.ts +40 -0
  62. package/dist/core/adapters/claude-code-discovery.js +100 -0
  63. package/dist/core/aggregate-format.d.ts +62 -0
  64. package/dist/core/aggregate-format.js +349 -0
  65. package/dist/core/api-client/client.d.ts +43 -0
  66. package/dist/core/api-client/client.js +143 -0
  67. package/dist/core/api-client/config.d.ts +36 -0
  68. package/dist/core/api-client/config.js +44 -0
  69. package/dist/core/api-client/index.d.ts +13 -0
  70. package/dist/core/api-client/index.js +14 -0
  71. package/dist/core/api-client/payload.d.ts +93 -0
  72. package/dist/core/api-client/payload.js +180 -0
  73. package/dist/core/api-client/types.d.ts +95 -0
  74. package/dist/core/api-client/types.js +21 -0
  75. package/dist/core/artifact-writer.d.ts +2 -0
  76. package/dist/core/artifact-writer.js +14 -0
  77. package/dist/core/audit-report.d.ts +91 -0
  78. package/dist/core/audit-report.js +422 -0
  79. package/dist/core/billing-delta/billing-delta-record.d.ts +290 -0
  80. package/dist/core/billing-delta/billing-delta-record.js +394 -0
  81. package/dist/core/capture-adapter.d.ts +74 -0
  82. package/dist/core/capture-adapter.js +2 -0
  83. package/dist/core/command-runner.d.ts +29 -0
  84. package/dist/core/command-runner.js +96 -0
  85. package/dist/core/content-hash.d.ts +1 -0
  86. package/dist/core/content-hash.js +5 -0
  87. package/dist/core/cost-calculator.d.ts +6 -0
  88. package/dist/core/cost-calculator.js +20 -0
  89. package/dist/core/feedback-bundle.d.ts +159 -0
  90. package/dist/core/feedback-bundle.js +406 -0
  91. package/dist/core/openai-agents-capture.d.ts +66 -0
  92. package/dist/core/openai-agents-capture.js +475 -0
  93. package/dist/core/policy-types.d.ts +85 -0
  94. package/dist/core/policy-types.js +29 -0
  95. package/dist/core/pricing.d.ts +23 -0
  96. package/dist/core/pricing.js +55 -0
  97. package/dist/core/provider-usage/credential-redaction.d.ts +23 -0
  98. package/dist/core/provider-usage/credential-redaction.js +60 -0
  99. package/dist/core/provider-usage/provider-usage-adapter.d.ts +65 -0
  100. package/dist/core/provider-usage/provider-usage-adapter.js +147 -0
  101. package/dist/core/provider-usage/provider-usage-client.d.ts +89 -0
  102. package/dist/core/provider-usage/provider-usage-client.js +96 -0
  103. package/dist/core/report-generator.d.ts +7 -0
  104. package/dist/core/report-generator.js +275 -0
  105. package/dist/core/run-aggregator.d.ts +50 -0
  106. package/dist/core/run-aggregator.js +251 -0
  107. package/dist/core/run-labels.d.ts +52 -0
  108. package/dist/core/run-labels.js +80 -0
  109. package/dist/core/run-trace-converter.d.ts +3 -0
  110. package/dist/core/run-trace-converter.js +94 -0
  111. package/dist/core/safety-report.d.ts +52 -0
  112. package/dist/core/safety-report.js +440 -0
  113. package/dist/core/savings.d.ts +22 -0
  114. package/dist/core/savings.js +36 -0
  115. package/dist/core/session-aggregate.d.ts +211 -0
  116. package/dist/core/session-aggregate.js +490 -0
  117. package/dist/core/skill-injection-detector.d.ts +51 -0
  118. package/dist/core/skill-injection-detector.js +146 -0
  119. package/dist/core/spend-attribution.d.ts +169 -0
  120. package/dist/core/spend-attribution.js +611 -0
  121. package/dist/core/standalone-capsule-provenance.d.ts +4 -0
  122. package/dist/core/standalone-capsule-provenance.js +62 -0
  123. package/dist/core/state-capsule.d.ts +3 -0
  124. package/dist/core/state-capsule.js +147 -0
  125. package/dist/core/token-accounting.d.ts +62 -0
  126. package/dist/core/token-accounting.js +81 -0
  127. package/dist/core/token-estimator.d.ts +3 -0
  128. package/dist/core/token-estimator.js +18 -0
  129. package/dist/core/trace-adapters.d.ts +53 -0
  130. package/dist/core/trace-adapters.js +714 -0
  131. package/dist/core/trace-fingerprint.d.ts +79 -0
  132. package/dist/core/trace-fingerprint.js +102 -0
  133. package/dist/core/trace-intake.d.ts +41 -0
  134. package/dist/core/trace-intake.js +164 -0
  135. package/dist/core/trace-parser.d.ts +203 -0
  136. package/dist/core/trace-parser.js +143 -0
  137. package/dist/core/types.d.ts +238 -0
  138. package/dist/core/types.js +2 -0
  139. package/dist/core/usage-metadata.d.ts +51 -0
  140. package/dist/core/usage-metadata.js +122 -0
  141. package/dist/core/waste-detector.d.ts +8 -0
  142. package/dist/core/waste-detector.js +58 -0
  143. package/package.json +90 -0
@@ -0,0 +1,422 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { writeJsonArtifact, writeTextArtifact } from "./artifact-writer.js";
4
+ import { createRunSummary } from "./run-aggregator.js";
5
+ export const AUDIT_ARTIFACT_DIRECTORY = ".compaction/audits";
6
+ export const AUDIT_ARTIFACT_NAMES = ["audit-report.json", "audit-report.md"];
7
+ function roundCurrency(value) {
8
+ return Number(value.toFixed(6));
9
+ }
10
+ function roundPercent(value) {
11
+ return Number(value.toFixed(2));
12
+ }
13
+ function emptySourceSummary(sourceDirectory, artifactName, missing) {
14
+ return {
15
+ source_directory: sourceDirectory,
16
+ artifact_name: artifactName,
17
+ total_artifacts: 0,
18
+ skipped_files: [],
19
+ missing,
20
+ note: missing ? `${sourceDirectory} does not exist; no ${artifactName} artifacts were summarized.` : null
21
+ };
22
+ }
23
+ function isRecord(value) {
24
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25
+ }
26
+ function isFiniteNumber(value) {
27
+ return typeof value === "number" && Number.isFinite(value);
28
+ }
29
+ function requiredString(record, field) {
30
+ return typeof record[field] === "string" && record[field].trim() !== "" ? record[field] : null;
31
+ }
32
+ function validateSafetyReport(value) {
33
+ if (!isRecord(value)) {
34
+ return { reason: "safety report is not a JSON object" };
35
+ }
36
+ const runId = requiredString(value, "run_id");
37
+ const status = value.status;
38
+ const riskLevel = value.risk_level;
39
+ if (runId === null)
40
+ return { reason: "missing or invalid run_id" };
41
+ if (status !== "pass" && status !== "warn" && status !== "fail")
42
+ return { reason: "missing or invalid status" };
43
+ if (riskLevel !== "low" && riskLevel !== "medium" && riskLevel !== "high")
44
+ return { reason: "missing or invalid risk_level" };
45
+ if (!isFiniteNumber(value.tokens_saved))
46
+ return { reason: "missing or invalid tokens_saved" };
47
+ return {
48
+ report: {
49
+ run_id: runId,
50
+ status,
51
+ risk_level: riskLevel,
52
+ tokens_saved: value.tokens_saved,
53
+ warnings: Array.isArray(value.warnings) ? value.warnings.filter((item) => typeof item === "string") : [],
54
+ failures: Array.isArray(value.failures) ? value.failures.filter((item) => typeof item === "string") : []
55
+ }
56
+ };
57
+ }
58
+ function validateRecommendationReport(value) {
59
+ if (!isRecord(value))
60
+ return { reason: "recommendation is not a JSON object" };
61
+ const traceId = requiredString(value, "trace_id");
62
+ if (traceId === null)
63
+ return { reason: "missing or invalid trace_id" };
64
+ if (value.recommended_mode !== "observe" && value.recommended_mode !== "recommend" && value.recommended_mode !== "apply_with_approval" && value.recommended_mode !== "auto_eligible") {
65
+ return { reason: "missing or invalid recommended_mode" };
66
+ }
67
+ for (const field of ["tokens_saved"]) {
68
+ if (!isFiniteNumber(value[field]))
69
+ return { reason: `missing or invalid ${field}` };
70
+ }
71
+ return { report: value };
72
+ }
73
+ function validateApplyReport(value) {
74
+ if (!isRecord(value))
75
+ return { reason: "apply report is not a JSON object" };
76
+ const traceId = requiredString(value, "trace_id");
77
+ if (traceId === null)
78
+ return { reason: "missing or invalid trace_id" };
79
+ if (typeof value.applied !== "boolean")
80
+ return { reason: "missing or invalid applied" };
81
+ if (value.safety_status !== "pass" && value.safety_status !== "warn" && value.safety_status !== "fail" && value.safety_status !== "missing") {
82
+ return { reason: "missing or invalid safety_status" };
83
+ }
84
+ for (const field of ["tokens_saved", "saving_per_run"]) {
85
+ if (!isFiniteNumber(value[field]))
86
+ return { reason: `missing or invalid ${field}` };
87
+ }
88
+ return { report: value };
89
+ }
90
+ async function findChildArtifactPaths(directory, artifactName) {
91
+ try {
92
+ const entries = await readdir(directory, { withFileTypes: true });
93
+ return {
94
+ paths: entries
95
+ .filter((entry) => entry.isDirectory())
96
+ .map((entry) => join(directory, entry.name, artifactName))
97
+ .sort(),
98
+ missing: false
99
+ };
100
+ }
101
+ catch (error) {
102
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "unknown";
103
+ if (code === "ENOENT") {
104
+ return { paths: [], missing: true };
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+ async function readChildJsonArtifacts(directory, artifactName, validate) {
110
+ const { paths, missing } = await findChildArtifactPaths(directory, artifactName);
111
+ const source = emptySourceSummary(directory, artifactName, missing);
112
+ const artifacts = [];
113
+ for (const path of paths) {
114
+ try {
115
+ const parsed = JSON.parse(await readFile(path, "utf8"));
116
+ const validation = validate(parsed);
117
+ if (validation.report === undefined) {
118
+ source.skipped_files.push({ path, reason: validation.reason ?? "invalid artifact" });
119
+ continue;
120
+ }
121
+ artifacts.push({ path, value: validation.report });
122
+ }
123
+ catch (error) {
124
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
125
+ const reason = code === "ENOENT" ? `${artifactName} not found` : error instanceof SyntaxError ? "invalid JSON" : `could not read ${artifactName}`;
126
+ source.skipped_files.push({ path, reason });
127
+ }
128
+ }
129
+ source.total_artifacts = artifacts.length;
130
+ if (!missing && artifacts.length === 0 && source.skipped_files.length === 0) {
131
+ source.note = `${directory} exists but no ${artifactName} artifacts were found.`;
132
+ }
133
+ return { artifacts, source };
134
+ }
135
+ function policySummaries(summary) {
136
+ return Object.entries(summary.savings_by_policy)
137
+ .map(([policyName, bucket]) => ({ policy_name: policyName, ...bucket }))
138
+ .sort((left, right) => right.total_tokens_saved - left.total_tokens_saved || left.policy_name.localeCompare(right.policy_name));
139
+ }
140
+ function wastePatternSummaries(summary) {
141
+ return Object.entries(summary.savings_by_waste_pattern)
142
+ .map(([wastePattern, bucket]) => ({ waste_pattern: wastePattern, ...bucket }))
143
+ .sort((left, right) => right.total_tokens_saved - left.total_tokens_saved || left.waste_pattern.localeCompare(right.waste_pattern));
144
+ }
145
+ function createSafetySummary(source, reports) {
146
+ const statusCounts = { pass: 0, warn: 0, fail: 0 };
147
+ const riskLevelCounts = { low: 0, medium: 0, high: 0 };
148
+ const warnings = new Set();
149
+ const failures = new Set();
150
+ let totalTokensSaved = 0;
151
+ for (const { value } of reports) {
152
+ statusCounts[value.status] += 1;
153
+ riskLevelCounts[value.risk_level] += 1;
154
+ totalTokensSaved += value.tokens_saved;
155
+ value.warnings.forEach((warning) => warnings.add(warning));
156
+ value.failures.forEach((failure) => failures.add(failure));
157
+ }
158
+ return {
159
+ source,
160
+ total_reports: reports.length,
161
+ status_counts: statusCounts,
162
+ risk_level_counts: riskLevelCounts,
163
+ total_tokens_saved_with_safety_reports: totalTokensSaved,
164
+ warnings: [...warnings].sort(),
165
+ failures: [...failures].sort(),
166
+ note: reports.length === 0 ? "No safety-report.json artifacts were available to summarize." : null
167
+ };
168
+ }
169
+ function createRecommendationSummary(source, reports) {
170
+ const modeCounts = { observe: 0, recommend: 0, apply_with_approval: 0, auto_eligible: 0 };
171
+ const nextSteps = new Set();
172
+ let totalTokensSaved = 0;
173
+ for (const { value } of reports) {
174
+ modeCounts[value.recommended_mode] += 1;
175
+ totalTokensSaved += value.tokens_saved;
176
+ nextSteps.add(value.required_next_step);
177
+ }
178
+ return {
179
+ source,
180
+ total_recommendations: reports.length,
181
+ mode_counts: modeCounts,
182
+ total_tokens_saved: totalTokensSaved,
183
+ recommended_next_steps: [...nextSteps].sort(),
184
+ note: reports.length === 0 ? "No recommendation.json artifacts were available to summarize." : null
185
+ };
186
+ }
187
+ function createApplySummary(source, reports) {
188
+ if (source.missing && reports.length === 0) {
189
+ return undefined;
190
+ }
191
+ const safetyStatusCounts = { pass: 0, warn: 0, fail: 0, missing: 0 };
192
+ const refusalReasons = new Set();
193
+ let appliedCount = 0;
194
+ let totalTokensSaved = 0;
195
+ let totalSavingPerRun = 0;
196
+ for (const { value } of reports) {
197
+ safetyStatusCounts[value.safety_status] += 1;
198
+ if (value.applied) {
199
+ appliedCount += 1;
200
+ totalTokensSaved += value.tokens_saved;
201
+ totalSavingPerRun += value.saving_per_run;
202
+ }
203
+ else if (value.refusal_reason) {
204
+ refusalReasons.add(value.refusal_reason);
205
+ }
206
+ }
207
+ return {
208
+ source,
209
+ total_apply_reports: reports.length,
210
+ applied_count: appliedCount,
211
+ refused_count: reports.length - appliedCount,
212
+ safety_status_counts: safetyStatusCounts,
213
+ total_tokens_saved_from_applied_reports: totalTokensSaved,
214
+ total_saving_per_run_from_applied_reports: roundCurrency(totalSavingPerRun),
215
+ refusal_reasons: [...refusalReasons].sort(),
216
+ note: reports.length === 0 ? "No apply-report.json artifacts were available to summarize." : null
217
+ };
218
+ }
219
+ function auditIdFromGeneratedAt(generatedAt) {
220
+ return `audit-${generatedAt.replace(/[^0-9]/g, "").slice(0, 14)}`;
221
+ }
222
+ function runSourceSummary(summary, runsDirectory) {
223
+ return {
224
+ source_directory: runsDirectory,
225
+ artifact_name: "report.json",
226
+ total_artifacts: summary.total_runs,
227
+ skipped_files: summary.skipped_files,
228
+ missing: false,
229
+ note: summary.total_runs === 0 ? "No valid .compaction/runs/*/report.json artifacts were available to summarize." : null
230
+ };
231
+ }
232
+ function createSuggestedNextSteps(input) {
233
+ const nextSteps = [];
234
+ if (input.summary.total_runs === 0) {
235
+ nextSteps.push("Run local compaction commands to generate .compaction/runs/*/report.json before using this audit for savings review.");
236
+ }
237
+ else {
238
+ nextSteps.push("Review the top savings runs and confirm the underlying local reports match the workflow being audited.");
239
+ }
240
+ if (input.recommendations.total_recommendations === 0) {
241
+ nextSteps.push("Run compaction recommend on relevant local traces if recommendation artifacts are needed for the audit package.");
242
+ }
243
+ else {
244
+ nextSteps.push("Review recommendation modes and required next steps before applying any optimization.");
245
+ }
246
+ if (input.safety.total_reports === 0) {
247
+ nextSteps.push("Generate or inspect safety-report.json artifacts before using this package for approval decisions.");
248
+ }
249
+ else if (input.safety.status_counts.fail > 0 || input.safety.status_counts.warn > 0) {
250
+ nextSteps.push("Resolve warning or failing safety checks before applying optimizations.");
251
+ }
252
+ if (input.apply === undefined) {
253
+ nextSteps.push("Apply artifacts were not present; keep this audit as advisory until an explicit local apply run is generated.");
254
+ }
255
+ else if (input.apply.total_apply_reports === 0) {
256
+ nextSteps.push("The apply artifact folder exists but contains no valid apply reports; inspect skipped files or rerun apply mode if needed.");
257
+ }
258
+ else {
259
+ nextSteps.push("Inspect applied-trace artifacts before reusing any applied optimization output.");
260
+ }
261
+ return nextSteps;
262
+ }
263
+ function createLimitations(input) {
264
+ return [
265
+ "This audit package is generated from local artifacts only and makes no provider calls.",
266
+ "Savings are deterministic local estimates copied or aggregated from existing reports, not provider billing records.",
267
+ "Saving totals are estimated and summed only over the compaction run reports actually present locally; no run volume is assumed or extrapolated.",
268
+ "Missing recommendation, safety, or apply artifacts are reported as missing rather than inferred.",
269
+ "Agent Cost Audit v0 does not include a hosted service, dashboard, database, auth, billing, model routing, or provider integration.",
270
+ ...(input.summary.total_runs === 0 ? ["No valid compaction run reports were found, so savings totals are zero."] : []),
271
+ ...(input.recommendations.total_recommendations === 0 ? ["No valid recommendation artifacts were found, so recommendation summary is limited."] : []),
272
+ ...(input.apply === undefined ? ["No apply artifact directory was found, so applied optimization results are not included."] : [])
273
+ ];
274
+ }
275
+ export async function createAuditReport(directories = {}, generatedAt = new Date().toISOString()) {
276
+ const resolvedDirectories = {
277
+ runs: directories.runs ?? ".compaction/runs",
278
+ recommendations: directories.recommendations ?? ".compaction/recommendations",
279
+ apply: directories.apply ?? ".compaction/apply",
280
+ audits: directories.audits ?? AUDIT_ARTIFACT_DIRECTORY
281
+ };
282
+ const runSummary = await createRunSummary(resolvedDirectories.runs);
283
+ const runsSource = runSourceSummary(runSummary, resolvedDirectories.runs);
284
+ const { artifacts: safetyReports, source: safetySource } = await readChildJsonArtifacts(resolvedDirectories.runs, "safety-report.json", validateSafetyReport);
285
+ const { artifacts: recommendationReports, source: recommendationSource } = await readChildJsonArtifacts(resolvedDirectories.recommendations, "recommendation.json", validateRecommendationReport);
286
+ const { artifacts: applyReports, source: applySource } = await readChildJsonArtifacts(resolvedDirectories.apply, "apply-report.json", validateApplyReport);
287
+ const safetySummary = createSafetySummary(safetySource, safetyReports);
288
+ const recommendationSummary = createRecommendationSummary(recommendationSource, recommendationReports);
289
+ const applySummary = createApplySummary(applySource, applyReports);
290
+ const suggestedNextSteps = createSuggestedNextSteps({ summary: runSummary, recommendations: recommendationSummary, safety: safetySummary, apply: applySummary });
291
+ const limitations = createLimitations({ summary: runSummary, recommendations: recommendationSummary, apply: applySummary });
292
+ return {
293
+ audit_id: auditIdFromGeneratedAt(generatedAt),
294
+ generated_at: generatedAt,
295
+ total_runs: runSummary.total_runs,
296
+ total_original_input_tokens: runSummary.total_original_input_tokens,
297
+ total_compacted_input_tokens: runSummary.total_compacted_input_tokens,
298
+ total_tokens_saved: runSummary.total_tokens_saved,
299
+ average_percent_reduction: roundPercent(runSummary.average_percent_reduction),
300
+ total_cost_before_per_run: roundCurrency(runSummary.total_cost_before_per_run),
301
+ total_cost_after_per_run: roundCurrency(runSummary.total_cost_after_per_run),
302
+ total_saving_per_run: roundCurrency(runSummary.total_saving_per_run),
303
+ policies_detected: policySummaries(runSummary),
304
+ top_waste_patterns: wastePatternSummaries(runSummary),
305
+ safety_summary: safetySummary,
306
+ recommendation_summary: recommendationSummary,
307
+ ...(applySummary ? { apply_summary: applySummary } : {}),
308
+ top_savings_runs: runSummary.top_savings_runs,
309
+ suggested_next_steps: suggestedNextSteps,
310
+ limitations,
311
+ sources: {
312
+ runs: runsSource,
313
+ recommendations: recommendationSource,
314
+ apply: applySource
315
+ }
316
+ };
317
+ }
318
+ function formatCurrency(value) {
319
+ return `$${value.toFixed(6)}`;
320
+ }
321
+ function formatPolicyRows(policies) {
322
+ return policies.map((policy) => `| ${policy.policy_name} | ${policy.total_runs} | ${policy.total_tokens_saved} | ${formatCurrency(policy.total_saving_per_run)} |`);
323
+ }
324
+ function formatWasteRows(patterns) {
325
+ return patterns.map((pattern) => `| ${pattern.waste_pattern} | ${pattern.total_runs} | ${pattern.total_tokens_saved} | ${formatCurrency(pattern.total_saving_per_run)} |`);
326
+ }
327
+ function formatTopRunRows(runs) {
328
+ return runs.map((run) => `| ${run.run_id} | ${run.trace_title} | ${run.tokens_saved} | ${run.percent_reduction.toFixed(2)}% | ${formatCurrency(run.saving_per_run)} |`);
329
+ }
330
+ export function formatAuditMarkdown(report) {
331
+ return [
332
+ "# Agent Cost Audit",
333
+ "",
334
+ "## Summary",
335
+ "",
336
+ `- Audit id: ${report.audit_id}`,
337
+ `- Generated at: ${report.generated_at}`,
338
+ `- Total runs: ${report.total_runs}`,
339
+ `- Local-only package: yes; this audit summarizes existing local artifacts only.`,
340
+ `- Missing recommendation data: ${report.recommendation_summary.total_recommendations === 0 ? "yes" : "no"}`,
341
+ `- Missing apply data: ${report.apply_summary === undefined || report.apply_summary.total_apply_reports === 0 ? "yes" : "no"}`,
342
+ "",
343
+ "## Savings",
344
+ "",
345
+ `- Total original input tokens: ${report.total_original_input_tokens}`,
346
+ `- Total compacted input tokens: ${report.total_compacted_input_tokens}`,
347
+ `- Total tokens saved: ${report.total_tokens_saved}`,
348
+ `- Average percent reduction: ${report.average_percent_reduction.toFixed(2)}%`,
349
+ `- Total cost before per run: ${formatCurrency(report.total_cost_before_per_run)}`,
350
+ `- Total cost after per run: ${formatCurrency(report.total_cost_after_per_run)}`,
351
+ `- Total saving per run: ${formatCurrency(report.total_saving_per_run)}`,
352
+ `- Saving figures are estimated and summed over the ${report.total_runs} recorded run${report.total_runs === 1 ? "" : "s"} only; no run volume is assumed or extrapolated.`,
353
+ "",
354
+ "## Waste Patterns",
355
+ "",
356
+ "| Waste pattern | Runs | Tokens saved | Saving per run |",
357
+ "| --- | ---: | ---: | ---: |",
358
+ ...(report.top_waste_patterns.length === 0 ? ["| none | 0 | 0 | $0.000000 |"] : formatWasteRows(report.top_waste_patterns)),
359
+ "",
360
+ "## Policies",
361
+ "",
362
+ "| Policy | Runs | Tokens saved | Saving per run |",
363
+ "| --- | ---: | ---: | ---: |",
364
+ ...(report.policies_detected.length === 0 ? ["| none | 0 | 0 | $0.000000 |"] : formatPolicyRows(report.policies_detected)),
365
+ "",
366
+ "## Safety",
367
+ "",
368
+ `- Safety reports: ${report.safety_summary.total_reports}`,
369
+ `- Status counts: pass ${report.safety_summary.status_counts.pass}, warn ${report.safety_summary.status_counts.warn}, fail ${report.safety_summary.status_counts.fail}`,
370
+ `- Risk counts: low ${report.safety_summary.risk_level_counts.low}, medium ${report.safety_summary.risk_level_counts.medium}, high ${report.safety_summary.risk_level_counts.high}`,
371
+ ...(report.safety_summary.note ? [`- Note: ${report.safety_summary.note}`] : []),
372
+ "",
373
+ "## Recommendations",
374
+ "",
375
+ `- Recommendation reports: ${report.recommendation_summary.total_recommendations}`,
376
+ `- Mode counts: observe ${report.recommendation_summary.mode_counts.observe}, recommend ${report.recommendation_summary.mode_counts.recommend}, apply_with_approval ${report.recommendation_summary.mode_counts.apply_with_approval}, auto_eligible ${report.recommendation_summary.mode_counts.auto_eligible}`,
377
+ ...(report.recommendation_summary.note ? [`- Note: ${report.recommendation_summary.note}`] : []),
378
+ "",
379
+ ...(report.apply_summary
380
+ ? [
381
+ "## Applied Optimizations",
382
+ "",
383
+ `- Apply reports: ${report.apply_summary.total_apply_reports}`,
384
+ `- Applied: ${report.apply_summary.applied_count}`,
385
+ `- Refused: ${report.apply_summary.refused_count}`,
386
+ `- Tokens saved from applied reports: ${report.apply_summary.total_tokens_saved_from_applied_reports}`,
387
+ `- Saving per run from applied reports: ${formatCurrency(report.apply_summary.total_saving_per_run_from_applied_reports)}`,
388
+ ...(report.apply_summary.note ? [`- Note: ${report.apply_summary.note}`] : []),
389
+ ""
390
+ ]
391
+ : []),
392
+ "## Top Runs",
393
+ "",
394
+ "| Run ID | Trace title | Tokens saved | Percent reduction | Saving per run |",
395
+ "| --- | --- | ---: | ---: | ---: |",
396
+ ...(report.top_savings_runs.length === 0 ? ["| none | none | 0 | 0.00% | $0.000000 |"] : formatTopRunRows(report.top_savings_runs)),
397
+ "",
398
+ "## Suggested Next Steps",
399
+ "",
400
+ ...report.suggested_next_steps.map((step) => `- ${step}`),
401
+ "",
402
+ "## Limitations",
403
+ "",
404
+ ...report.limitations.map((limitation) => `- ${limitation}`)
405
+ ].join("\n");
406
+ }
407
+ export async function writeAuditArtifacts(directories = {}, generatedAt = new Date().toISOString()) {
408
+ const audit = await createAuditReport(directories, generatedAt);
409
+ const markdown = formatAuditMarkdown(audit);
410
+ const auditDirectory = `${directories.audits ?? AUDIT_ARTIFACT_DIRECTORY}/${audit.audit_id}`;
411
+ const auditJsonPath = await writeJsonArtifact(auditDirectory, "audit-report.json", audit);
412
+ const auditMarkdownPath = await writeTextArtifact(auditDirectory, "audit-report.md", markdown);
413
+ return {
414
+ audit,
415
+ markdown,
416
+ paths: {
417
+ auditJsonPath,
418
+ auditMarkdownPath
419
+ }
420
+ };
421
+ }
422
+ //# sourceMappingURL=audit-report.js.map