@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,65 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
// PUBLIC constant from the policy-types module (Phase 1a) — no engine runtime edge.
|
|
4
|
+
import { COMPACTION_POLICY_NAME } from "../../core/policy-types.js";
|
|
5
|
+
import { parseTraceFile } from "../../core/trace-parser.js";
|
|
6
|
+
import { runEngineCommand } from "../engine-degrade.js";
|
|
7
|
+
async function readRecommendationMode(traceId, isRecommendationMode) {
|
|
8
|
+
const recommendationPath = `.compaction/recommendations/${traceId}/recommendation.json`;
|
|
9
|
+
try {
|
|
10
|
+
const recommendation = JSON.parse(await readFile(recommendationPath, "utf8"));
|
|
11
|
+
return typeof recommendation.recommended_mode === "string" && isRecommendationMode(recommendation.recommended_mode)
|
|
12
|
+
? recommendation.recommended_mode
|
|
13
|
+
: undefined;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function registerApplyCommand(program) {
|
|
23
|
+
program
|
|
24
|
+
.command("apply")
|
|
25
|
+
.argument("<trace-file>", "Path to a local agent trace JSON file")
|
|
26
|
+
.requiredOption("--policy <name>", "Reviewed deterministic local policy to apply")
|
|
27
|
+
.option("--require-safety-pass", "Refuse apply unless the deterministic local safety report passes")
|
|
28
|
+
.description("Apply a reviewed local deterministic compaction policy and write apply artifacts.")
|
|
29
|
+
.action(async (traceFile, options) => {
|
|
30
|
+
await runEngineCommand(async () => {
|
|
31
|
+
// Lazy-load the proprietary engine: present in-repo/API, excluded from the public package.
|
|
32
|
+
// When absent, runEngineCommand prints the boundary message and exits cleanly (no stack trace).
|
|
33
|
+
const { isRecommendationMode } = await import("../../engine/recommendation.js");
|
|
34
|
+
const { writeApplyArtifacts } = await import("../../engine/apply-mode.js");
|
|
35
|
+
const trace = await parseTraceFile(traceFile);
|
|
36
|
+
const recommendationMode = await readRecommendationMode(trace.id, isRecommendationMode);
|
|
37
|
+
const artifacts = await writeApplyArtifacts({
|
|
38
|
+
trace,
|
|
39
|
+
policy: {
|
|
40
|
+
name: options.policy,
|
|
41
|
+
isLocal: options.policy === COMPACTION_POLICY_NAME,
|
|
42
|
+
isDeterministic: options.policy === COMPACTION_POLICY_NAME
|
|
43
|
+
},
|
|
44
|
+
requireSafetyPass: options.requireSafetyPass === true,
|
|
45
|
+
generatedAt: trace.generatedAt,
|
|
46
|
+
recommendationMode
|
|
47
|
+
});
|
|
48
|
+
console.log(chalk.cyan("compaction apply"));
|
|
49
|
+
console.log(`Trace id: ${artifacts.report.trace_id}`);
|
|
50
|
+
console.log(`Policy name: ${artifacts.report.policy_name}`);
|
|
51
|
+
console.log(`Safety status: ${artifacts.report.safety_status}`);
|
|
52
|
+
console.log(`Risk level: ${artifacts.report.risk_level}`);
|
|
53
|
+
console.log(`Applied: ${artifacts.report.applied}`);
|
|
54
|
+
console.log(`Refusal reason: ${artifacts.report.refusal_reason ?? "None"}`);
|
|
55
|
+
console.log(`Required next step: ${artifacts.report.required_next_step}`);
|
|
56
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.appliedTracePath}`));
|
|
57
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.applyReportJsonPath}`));
|
|
58
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.applyReportMarkdownPath}`));
|
|
59
|
+
if (!artifacts.report.applied) {
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { runEngineCommand } from "../engine-degrade.js";
|
|
3
|
+
export function registerApproveCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("approve")
|
|
6
|
+
.description("Approve and locally apply a reviewed optimization recommendation to a captured trace copy.")
|
|
7
|
+
.argument("<optimization-id>", "Optimization ID produced by compaction optimize openai-agents")
|
|
8
|
+
.option("--require-safety-pass", "Refuse approval unless safety status is pass")
|
|
9
|
+
.option("--approve-skill-injection-policy", "EXPLICITLY also authorize the approval-required repeated_skill_injection_to_state_capsule policy to compact byte-identical same-skill role:user skill injections. Approval-gated; default off; no auto-apply.")
|
|
10
|
+
.option("--pre-apply", "READ-ONLY pre-apply view: show before/after input tokens, estimated cost, recoverability, evidence tier, and approval_readiness (not_ready/conditional/ready) WITHOUT applying anything or emitting an approved trace. No auto-apply.")
|
|
11
|
+
.action(async (optimizationId, options) => {
|
|
12
|
+
await runEngineCommand(async () => {
|
|
13
|
+
// Lazy-load the proprietary engine: present in-repo/API, excluded from the public package.
|
|
14
|
+
// When absent, runEngineCommand prints the boundary message and exits cleanly (no stack trace).
|
|
15
|
+
const { approveOptimization } = await import("../../engine/optimization-approval.js");
|
|
16
|
+
const { buildPreApplyApprovalView } = await import("../../engine/pre-apply-approval-view.js");
|
|
17
|
+
if (options.preApply) {
|
|
18
|
+
console.log(chalk.cyan("compaction approve --pre-apply"));
|
|
19
|
+
console.log("Read-only pre-apply view. NOTHING is applied; no approved trace is emitted. No provider APIs, live workflow mutation, production proxying, auto mode, model routing, or hosted dashboard is performed.");
|
|
20
|
+
const view = await buildPreApplyApprovalView({
|
|
21
|
+
optimizationId,
|
|
22
|
+
requireSafetyPass: Boolean(options.requireSafetyPass),
|
|
23
|
+
includeSkillInjectionPolicy: Boolean(options.approveSkillInjectionPolicy)
|
|
24
|
+
});
|
|
25
|
+
console.log(view.terminalSummary);
|
|
26
|
+
console.log(chalk.green(`Wrote ${view.paths.preApplyReportJsonPath}`));
|
|
27
|
+
console.log(chalk.green(`Wrote ${view.paths.preApplyReportMarkdownPath}`));
|
|
28
|
+
console.log(chalk.green(`Evidence directory: ${view.outputDirectory}`));
|
|
29
|
+
// Read-only: never apply. Exit non-zero only when apply would be blocked, so scripts can gate.
|
|
30
|
+
if (view.report.approval_readiness === "not_ready") {
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.log(chalk.cyan("compaction approve"));
|
|
36
|
+
console.log("Running explicit local approval only. No provider APIs, live workflow mutation, production proxying, auto mode, model routing, or hosted dashboard is performed.");
|
|
37
|
+
const artifacts = await approveOptimization({
|
|
38
|
+
optimizationId,
|
|
39
|
+
requireSafetyPass: Boolean(options.requireSafetyPass),
|
|
40
|
+
approveSkillInjectionPolicy: Boolean(options.approveSkillInjectionPolicy)
|
|
41
|
+
});
|
|
42
|
+
console.log(artifacts.terminalSummary);
|
|
43
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.approvalReportJsonPath}`));
|
|
44
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.approvalReportMarkdownPath}`));
|
|
45
|
+
if (artifacts.paths.approvedTracePath) {
|
|
46
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.approvedTracePath}`));
|
|
47
|
+
}
|
|
48
|
+
console.log(chalk.green(`Evidence directory: ${artifacts.outputDirectory}`));
|
|
49
|
+
if (!artifacts.report.approved) {
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=approve.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
// FREE: the default `audit` package path uses only public core (engine refs in audit-report are
|
|
3
|
+
// type-only). The `--traces` / `--adapter` pilot paths run the proprietary engine and are lazy-loaded
|
|
4
|
+
// in their branches below, degrading honestly when the engine build is excluded from the package.
|
|
5
|
+
import { writeAuditArtifacts } from "../../core/audit-report.js";
|
|
6
|
+
import { runEngineCommand } from "../engine-degrade.js";
|
|
7
|
+
function formatCurrency(value) {
|
|
8
|
+
return `$${value.toFixed(6)}`;
|
|
9
|
+
}
|
|
10
|
+
const supportedPilotSources = ["agent-trace", "messages", "unknown"];
|
|
11
|
+
function parsePilotSource(source) {
|
|
12
|
+
if (supportedPilotSources.includes(source)) {
|
|
13
|
+
return source;
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`Unsupported source "${source}". Supported sources: ${supportedPilotSources.join(", ")}.`);
|
|
16
|
+
}
|
|
17
|
+
export function registerAuditCommand(program) {
|
|
18
|
+
program
|
|
19
|
+
.command("audit")
|
|
20
|
+
.description("Package local compaction artifacts or run a local trace audit pilot folder.")
|
|
21
|
+
.option("--traces <dir>", "Directory of local trace files to audit")
|
|
22
|
+
.option("--adapter <adapter-id>", "Adapter registry id for the adapter audit workflow")
|
|
23
|
+
.option("--source <source>", "Pilot input source: agent-trace, messages, or unknown", "unknown")
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
if (options.traces && options.adapter) {
|
|
26
|
+
await runEngineCommand(async () => {
|
|
27
|
+
// Engine-backed adapter audit (compaction + recommendation over each trace). Lazy-load;
|
|
28
|
+
// degrades honestly when the engine build is excluded from the package.
|
|
29
|
+
const { runAdapterAudit } = await import("../../core/adapter-audit.js");
|
|
30
|
+
const result = await runAdapterAudit(options.traces, options.adapter);
|
|
31
|
+
console.log(chalk.cyan("compaction audit"));
|
|
32
|
+
console.log(result.consoleSummary);
|
|
33
|
+
console.log(chalk.green(`Wrote ${result.paths.adapterAuditSummaryJsonPath}`));
|
|
34
|
+
console.log(chalk.green(`Wrote ${result.paths.adapterAuditSummaryMarkdownPath}`));
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (options.traces) {
|
|
39
|
+
await runEngineCommand(async () => {
|
|
40
|
+
// Engine-backed local trace audit pilot (compaction + recommendation over a folder of traces).
|
|
41
|
+
// Lazy-load; degrades honestly when the engine build is excluded from the package.
|
|
42
|
+
const { runLocalTraceAuditPilot } = await import("../../core/pilot-audit.js");
|
|
43
|
+
const result = await runLocalTraceAuditPilot(options.traces, parsePilotSource(options.source ?? "unknown"));
|
|
44
|
+
console.log(chalk.cyan("compaction audit"));
|
|
45
|
+
console.log(result.consoleSummary);
|
|
46
|
+
console.log(chalk.green(`Wrote ${result.paths.pilotSummaryJsonPath}`));
|
|
47
|
+
console.log(chalk.green(`Wrote ${result.paths.pilotSummaryMarkdownPath}`));
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const artifacts = await writeAuditArtifacts();
|
|
52
|
+
console.log(chalk.cyan("compaction audit"));
|
|
53
|
+
console.log(`Audit id: ${artifacts.audit.audit_id}`);
|
|
54
|
+
console.log(`Runs summarized: ${artifacts.audit.total_runs}`);
|
|
55
|
+
console.log(`Savings: ${artifacts.audit.total_tokens_saved} input tokens saved, ${formatCurrency(artifacts.audit.total_saving_per_run)} estimated saving per run, summed over ${artifacts.audit.total_runs} recorded run${artifacts.audit.total_runs === 1 ? "" : "s"}.`);
|
|
56
|
+
console.log(`Recommendation artifacts: ${artifacts.audit.recommendation_summary.total_recommendations}`);
|
|
57
|
+
console.log(`Apply artifacts: ${artifacts.audit.apply_summary?.total_apply_reports ?? 0}`);
|
|
58
|
+
console.log("Local-only audit package; no provider calls or hosted services are used.");
|
|
59
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.auditJsonPath}`));
|
|
60
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.auditMarkdownPath}`));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { writeJsonArtifact, writeTextArtifact } from "../../core/artifact-writer.js";
|
|
5
|
+
import { addRun, computeReport, initMeasurementRecord, BILLING_DELTA_CRITERION, BILLING_DELTA_RECORD_ROOT } from "../../core/billing-delta/billing-delta-record.js";
|
|
6
|
+
const PRIVACY_NOTE = "Operator-run, local-first. compaction.dev makes NO provider call, reads NO credential, and has NO network access. " +
|
|
7
|
+
"All billing figures are operator-entered (read from YOUR provider console/billing export) and are NOT provider-verified by this tool. " +
|
|
8
|
+
"Aggregate cost only — no prompt/completion content is stored.";
|
|
9
|
+
function recordPath(outDir) {
|
|
10
|
+
return path.join(outDir, "measurement-record.json");
|
|
11
|
+
}
|
|
12
|
+
function defaultOutputDir(workflowId) {
|
|
13
|
+
return path.join(BILLING_DELTA_RECORD_ROOT, workflowId);
|
|
14
|
+
}
|
|
15
|
+
async function loadRecord(outDir) {
|
|
16
|
+
const raw = await readFile(recordPath(outDir), "utf8");
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
async function readOptionalFile(filePath) {
|
|
20
|
+
if (!filePath)
|
|
21
|
+
return undefined;
|
|
22
|
+
return readFile(filePath, "utf8");
|
|
23
|
+
}
|
|
24
|
+
function resolveOutDir(out, workflow) {
|
|
25
|
+
if (out)
|
|
26
|
+
return out;
|
|
27
|
+
if (workflow)
|
|
28
|
+
return defaultOutputDir(workflow);
|
|
29
|
+
throw new Error("either --out <dir> or --workflow <id> is required to locate the measurement record");
|
|
30
|
+
}
|
|
31
|
+
// Console display only. Raw full-precision values stay in the JSON/markdown artifacts.
|
|
32
|
+
function roundForConsole(value) {
|
|
33
|
+
if (value === null || value === undefined)
|
|
34
|
+
return "n/a";
|
|
35
|
+
return String(Number(value.toFixed(4)));
|
|
36
|
+
}
|
|
37
|
+
function buildReportMarkdown(report) {
|
|
38
|
+
const lines = [
|
|
39
|
+
"# Billing-Delta Measurement Report (operator-run A/B)",
|
|
40
|
+
"",
|
|
41
|
+
`**Measurement:** ${report.measurement_id} (workflow: ${report.workflow_id})`,
|
|
42
|
+
`**Generated at:** ${report.generated_at}`,
|
|
43
|
+
`**Verdict:** ${report.verdict}`,
|
|
44
|
+
"",
|
|
45
|
+
"> Operator-entered billing figures, not provider-verified by this tool. The tool made no provider call.",
|
|
46
|
+
"> This is NOT 'billing-confirmed savings' (Tier 3); it does not flip ValueProof.billing_confirmed_savings.claimed.",
|
|
47
|
+
"",
|
|
48
|
+
"## Fixed conditions",
|
|
49
|
+
"",
|
|
50
|
+
`- Model: ${report.fixed_conditions.model}`,
|
|
51
|
+
`- Model version: ${report.fixed_conditions.model_version}`,
|
|
52
|
+
`- Input: ${report.fixed_conditions.input_description}`,
|
|
53
|
+
`- Window: ${report.fixed_conditions.window}`,
|
|
54
|
+
"",
|
|
55
|
+
"## Arms (aggregate billed cost only)",
|
|
56
|
+
"",
|
|
57
|
+
`- Control: n=${report.control.n}, mean=${report.control.mean ?? "n/a"}, min=${report.control.min ?? "n/a"}, max=${report.control.max ?? "n/a"}, sample_sd=${report.control.sample_sd ?? "n/a"}`,
|
|
58
|
+
`- Treatment: n=${report.treatment.n}, mean=${report.treatment.mean ?? "n/a"}, min=${report.treatment.min ?? "n/a"}, max=${report.treatment.max ?? "n/a"}, sample_sd=${report.treatment.sample_sd ?? "n/a"}`,
|
|
59
|
+
"",
|
|
60
|
+
"## Delta of means (control - treatment; positive = treatment cheaper)",
|
|
61
|
+
"",
|
|
62
|
+
`- mean_delta: ${report.mean_delta ?? "n/a"} ${report.currency ?? ""}`,
|
|
63
|
+
`- standard_error: ${report.delta_standard_error ?? "n/a"}`,
|
|
64
|
+
`- interval: ${report.delta_interval ? `[${report.delta_interval.lower}, ${report.delta_interval.upper}]` : "n/a"}`,
|
|
65
|
+
`- interval_excludes_zero: ${report.delta_interval_excludes_zero}`,
|
|
66
|
+
"",
|
|
67
|
+
"## Provider-reported token delta (token-level; never a billing claim)",
|
|
68
|
+
"",
|
|
69
|
+
`> ${report.token_delta.claim_label}`,
|
|
70
|
+
"",
|
|
71
|
+
`- status: ${report.token_delta.status}`,
|
|
72
|
+
`- Control: n_with_tokens=${report.token_delta.control.n_with_tokens}, mean_input=${roundForConsole(report.token_delta.control.mean_input_tokens)}, input_deterministic=${report.token_delta.control.input_deterministic}, output min/max=${roundForConsole(report.token_delta.control.output_min)}/${roundForConsole(report.token_delta.control.output_max)}`,
|
|
73
|
+
`- Treatment: n_with_tokens=${report.token_delta.treatment.n_with_tokens}, mean_input=${roundForConsole(report.token_delta.treatment.mean_input_tokens)}, input_deterministic=${report.token_delta.treatment.input_deterministic}, output min/max=${roundForConsole(report.token_delta.treatment.output_min)}/${roundForConsole(report.token_delta.treatment.output_max)}`,
|
|
74
|
+
`- input_token_delta: ${roundForConsole(report.token_delta.input_token_delta)}${report.token_delta.input_token_delta_pct !== null ? ` (${(report.token_delta.input_token_delta_pct * 100).toFixed(2)}% of control input)` : ""}`,
|
|
75
|
+
`- output_token_delta: ${roundForConsole(report.token_delta.output_token_delta)}`,
|
|
76
|
+
`- estimated_cost_delta: ${report.token_delta.estimated_cost_delta ? `${report.token_delta.estimated_cost_delta.amount_usd.toFixed(4)} USD (${report.token_delta.estimated_cost_delta.cost_source} — an estimate, never billed)` : "n/a"}`,
|
|
77
|
+
`- task_quality: control=[${report.token_delta.task_quality.control.join(", ") || "none"}] treatment=[${report.token_delta.task_quality.treatment.join(", ") || "none"}] any_failed=${report.token_delta.task_quality.any_failed}`,
|
|
78
|
+
...report.token_delta.notes.map((n) => `- note: ${n}`),
|
|
79
|
+
"",
|
|
80
|
+
"## Criterion",
|
|
81
|
+
"",
|
|
82
|
+
`- ${report.criterion}`,
|
|
83
|
+
"",
|
|
84
|
+
"## Verdict notes",
|
|
85
|
+
"",
|
|
86
|
+
...report.verdict_notes.map((n) => `- ${n}`),
|
|
87
|
+
"",
|
|
88
|
+
"## Claim scope (this workflow / these conditions / this N ONLY)",
|
|
89
|
+
"",
|
|
90
|
+
`- ${report.claim_scope}`,
|
|
91
|
+
`- not_a_general_claim: ${report.not_a_general_claim}`,
|
|
92
|
+
"",
|
|
93
|
+
"## Confounders recorded",
|
|
94
|
+
"",
|
|
95
|
+
...(report.confounders.length > 0 ? report.confounders.map((c) => `- ${c}`) : ["- (none recorded)"]),
|
|
96
|
+
"",
|
|
97
|
+
"## Treatment provenance links",
|
|
98
|
+
"",
|
|
99
|
+
...(report.treatment_provenance_links.length > 0
|
|
100
|
+
? report.treatment_provenance_links.map((l) => `- ${l.apply_approval_record_path} (sha256: ${l.apply_approval_record_sha256})`)
|
|
101
|
+
: ["- (none)"]),
|
|
102
|
+
"",
|
|
103
|
+
"## Caveats",
|
|
104
|
+
"",
|
|
105
|
+
...report.caveats.map((c) => `- ${c}`),
|
|
106
|
+
""
|
|
107
|
+
];
|
|
108
|
+
return lines.join("\n");
|
|
109
|
+
}
|
|
110
|
+
export function registerBillingDeltaCommand(program) {
|
|
111
|
+
const billingDelta = program
|
|
112
|
+
.command("billing-delta")
|
|
113
|
+
.description("Operator-run, local-first billing-delta measurement record (A/B protocol). The OPERATOR runs both arms " +
|
|
114
|
+
"and enters billed costs from their own provider console. compaction.dev makes no provider call, reads no " +
|
|
115
|
+
"credential, and emits no billing-confirmed savings claim. Produces a measured, caveated, scoped delta only.");
|
|
116
|
+
billingDelta
|
|
117
|
+
.command("init")
|
|
118
|
+
.description("Create a measurement record: workflow id/description, fixed conditions (model+version, input, window), optional apply-approval link.")
|
|
119
|
+
.requiredOption("--workflow <id>", "Workflow id (also the default output dir under .compaction/billing-delta/<id>/)")
|
|
120
|
+
.requiredOption("--description <text>", "Plain-language workflow description")
|
|
121
|
+
.requiredOption("--model <name>", "Model identifier held fixed across both arms")
|
|
122
|
+
.option("--model-version <version>", "Pinned model version where the provider exposes it (default: unknown)")
|
|
123
|
+
.requiredOption("--input <text>", "Description of the fixed input/task (a description, NOT the content)")
|
|
124
|
+
.requiredOption("--window <text>", "Time/pricing window the arms are run in")
|
|
125
|
+
.option("--apply-approval-record <path>", "Optional path to the apply-approval-record JSON from `apply-context` (treatment-context provenance). Recorded by path + hash.")
|
|
126
|
+
.option("--out <dir>", "Output directory for the measurement record (default: .compaction/billing-delta/<workflow>/)")
|
|
127
|
+
.action(async (options) => {
|
|
128
|
+
console.log(chalk.cyan("compaction billing-delta init"));
|
|
129
|
+
console.log(PRIVACY_NOTE);
|
|
130
|
+
const outDir = options.out ?? defaultOutputDir(options.workflow);
|
|
131
|
+
const applyApprovalRecordContent = await readOptionalFile(options.applyApprovalRecord);
|
|
132
|
+
const record = initMeasurementRecord({
|
|
133
|
+
workflowId: options.workflow,
|
|
134
|
+
workflowDescription: options.description,
|
|
135
|
+
model: options.model,
|
|
136
|
+
modelVersion: options.modelVersion,
|
|
137
|
+
inputDescription: options.input,
|
|
138
|
+
window: options.window,
|
|
139
|
+
generatedAt: new Date().toISOString(),
|
|
140
|
+
applyApprovalRecordPath: options.applyApprovalRecord,
|
|
141
|
+
applyApprovalRecordContent
|
|
142
|
+
});
|
|
143
|
+
const written = await writeJsonArtifact(outDir, "measurement-record.json", record);
|
|
144
|
+
console.log(chalk.green(`Wrote ${written}`));
|
|
145
|
+
console.log(`Measurement id: ${record.measurement_id}`);
|
|
146
|
+
console.log("Next: compaction billing-delta add-run --arm control|treatment --cost <n> --currency <c> --timestamp <iso> --out " + outDir);
|
|
147
|
+
});
|
|
148
|
+
billingDelta
|
|
149
|
+
.command("add-run")
|
|
150
|
+
.description("Append a run: arm (control|treatment), operator-entered billed cost (+currency), optional provider-reported usage, timestamp, notes/confounders, optional treatment apply-approval link.")
|
|
151
|
+
.requiredOption("--arm <arm>", "control | treatment")
|
|
152
|
+
.requiredOption("--cost <number>", "Operator-entered billed cost read from YOUR provider console/billing export")
|
|
153
|
+
.requiredOption("--currency <code>", "Currency code (e.g. USD)")
|
|
154
|
+
.requiredOption("--timestamp <iso>", "Run timestamp (ISO 8601)")
|
|
155
|
+
.option("--notes <text>", "Notes / confounders (cache state, retries, version drift). Aggregate, non-content.")
|
|
156
|
+
.option("--input-tokens <n>", "Optional provider-reported input tokens (aggregate)")
|
|
157
|
+
.option("--output-tokens <n>", "Optional provider-reported output tokens (aggregate)")
|
|
158
|
+
.option("--total-tokens <n>", "Optional provider-reported total tokens (aggregate)")
|
|
159
|
+
.option("--apply-approval-record <path>", "For treatment runs: optional path to the apply-approval-record JSON (the compacted-context provenance). Recorded by path + hash.")
|
|
160
|
+
.option("--out <dir>", "Directory of the measurement record")
|
|
161
|
+
.option("--workflow <id>", "Workflow id (used to locate the record under .compaction/billing-delta/<id>/ if --out is omitted)")
|
|
162
|
+
.action(async (options) => {
|
|
163
|
+
console.log(chalk.cyan("compaction billing-delta add-run"));
|
|
164
|
+
console.log(PRIVACY_NOTE);
|
|
165
|
+
if (options.arm !== "control" && options.arm !== "treatment") {
|
|
166
|
+
console.error("error: --arm must be 'control' or 'treatment'.");
|
|
167
|
+
process.exitCode = 1;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const cost = Number(options.cost);
|
|
171
|
+
if (!Number.isFinite(cost)) {
|
|
172
|
+
console.error("error: --cost must be a number.");
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const outDir = resolveOutDir(options.out, options.workflow);
|
|
177
|
+
const record = await loadRecord(outDir);
|
|
178
|
+
const usage = options.inputTokens || options.outputTokens || options.totalTokens
|
|
179
|
+
? {
|
|
180
|
+
...(options.inputTokens ? { input_tokens: Number(options.inputTokens) } : {}),
|
|
181
|
+
...(options.outputTokens ? { output_tokens: Number(options.outputTokens) } : {}),
|
|
182
|
+
...(options.totalTokens ? { total_tokens: Number(options.totalTokens) } : {})
|
|
183
|
+
}
|
|
184
|
+
: undefined;
|
|
185
|
+
const treatmentApprovalRecordContent = await readOptionalFile(options.applyApprovalRecord);
|
|
186
|
+
const updated = addRun(record, {
|
|
187
|
+
arm: options.arm,
|
|
188
|
+
billedCost: cost,
|
|
189
|
+
currency: options.currency,
|
|
190
|
+
runTimestamp: options.timestamp,
|
|
191
|
+
notes: options.notes,
|
|
192
|
+
providerReportedUsage: usage,
|
|
193
|
+
treatmentApprovalRecordPath: options.applyApprovalRecord,
|
|
194
|
+
treatmentApprovalRecordContent
|
|
195
|
+
});
|
|
196
|
+
const written = await writeJsonArtifact(outDir, "measurement-record.json", updated);
|
|
197
|
+
const added = updated.runs[updated.runs.length - 1];
|
|
198
|
+
console.log(chalk.green(`Recorded ${added.arm} run ${added.run_id} (operator_entered=true). Wrote ${written}`));
|
|
199
|
+
const nControl = updated.runs.filter((r) => r.arm === "control").length;
|
|
200
|
+
const nTreatment = updated.runs.filter((r) => r.arm === "treatment").length;
|
|
201
|
+
console.log(`Runs so far: control=${nControl}, treatment=${nTreatment}`);
|
|
202
|
+
});
|
|
203
|
+
billingDelta
|
|
204
|
+
.command("report")
|
|
205
|
+
.description("Compute per-arm N/mean/min/max/sample-SD, the delta of means, and a caveated verdict (insufficient_data | inconclusive | measured_caveated_delta).")
|
|
206
|
+
.option("--out <dir>", "Directory of the measurement record")
|
|
207
|
+
.option("--workflow <id>", "Workflow id (used to locate the record if --out is omitted)")
|
|
208
|
+
.action(async (options) => {
|
|
209
|
+
console.log(chalk.cyan("compaction billing-delta report"));
|
|
210
|
+
console.log(PRIVACY_NOTE);
|
|
211
|
+
const outDir = resolveOutDir(options.out, options.workflow);
|
|
212
|
+
const record = await loadRecord(outDir);
|
|
213
|
+
const report = computeReport(record, new Date().toISOString());
|
|
214
|
+
const writtenJson = await writeJsonArtifact(outDir, "billing-delta-report.json", report);
|
|
215
|
+
const writtenMd = await writeTextArtifact(outDir, "billing-delta-report.md", buildReportMarkdown(report));
|
|
216
|
+
console.log(`Verdict: ${chalk.bold(report.verdict)}`);
|
|
217
|
+
console.log(`Criterion: ${BILLING_DELTA_CRITERION}`);
|
|
218
|
+
console.log(`Control: n=${report.control.n}, mean=${roundForConsole(report.control.mean)}, sample_sd=${roundForConsole(report.control.sample_sd)}`);
|
|
219
|
+
console.log(`Treatment: n=${report.treatment.n}, mean=${roundForConsole(report.treatment.mean)}, sample_sd=${roundForConsole(report.treatment.sample_sd)}`);
|
|
220
|
+
console.log(`Delta of means (control - treatment): ${roundForConsole(report.mean_delta)} ${report.currency ?? ""}`);
|
|
221
|
+
if (report.delta_interval) {
|
|
222
|
+
console.log(`Interval: [${roundForConsole(report.delta_interval.lower)}, ${roundForConsole(report.delta_interval.upper)}] (excludes zero: ${report.delta_interval_excludes_zero})`);
|
|
223
|
+
}
|
|
224
|
+
for (const note of report.verdict_notes) {
|
|
225
|
+
console.log(chalk.yellow(`- ${note}`));
|
|
226
|
+
}
|
|
227
|
+
const td = report.token_delta;
|
|
228
|
+
console.log(`Token delta (${td.status}): ${td.claim_label}`);
|
|
229
|
+
if (td.status === "computed") {
|
|
230
|
+
console.log(` input_token_delta: ${roundForConsole(td.input_token_delta)}` +
|
|
231
|
+
(td.input_token_delta_pct !== null ? ` (${(td.input_token_delta_pct * 100).toFixed(2)}% of control input)` : "") +
|
|
232
|
+
`; output_token_delta: ${roundForConsole(td.output_token_delta)}`);
|
|
233
|
+
console.log(` estimated_cost_delta: ${td.estimated_cost_delta ? `${td.estimated_cost_delta.amount_usd.toFixed(4)} USD (price_table_estimate — an estimate, never billed)` : "n/a"}; ` +
|
|
234
|
+
`task any_failed=${td.task_quality.any_failed}`);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
for (const n of td.notes)
|
|
238
|
+
console.log(chalk.yellow(` ${n}`));
|
|
239
|
+
}
|
|
240
|
+
console.log("Claim scope: " + report.claim_scope);
|
|
241
|
+
console.log("not_a_general_claim: " + report.not_a_general_claim + "; billing_confirmed: " + report.billing_confirmed);
|
|
242
|
+
console.log(chalk.green(`Wrote ${writtenJson}`));
|
|
243
|
+
console.log(chalk.green(`Wrote ${writtenMd}`));
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=billing-delta.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface DiscoverClaudeCodeOptions {
|
|
2
|
+
projectsDir?: string;
|
|
3
|
+
}
|
|
4
|
+
interface CaptureClaudeCodeOptions {
|
|
5
|
+
session: string;
|
|
6
|
+
out?: string;
|
|
7
|
+
maxToolResultChars?: string;
|
|
8
|
+
includeSubagents?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function captureClaudeCodeCommand(options: CaptureClaudeCodeOptions): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Discover local Claude Code sessions (metadata only) and print, for each, the
|
|
13
|
+
* ready-to-run `capture claude-code --session <path>` command (guided capture).
|
|
14
|
+
*
|
|
15
|
+
* Local and read-only: scans the projects directory only, makes no network call
|
|
16
|
+
* and no upload, and never prints session message content — metadata only.
|
|
17
|
+
*/
|
|
18
|
+
export declare function discoverClaudeCodeCommand(options: DiscoverClaudeCodeOptions): Promise<void>;
|
|
19
|
+
export {};
|