@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,66 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { runEngineCommand } from "../engine-degrade.js";
|
|
4
|
+
export function registerEvalCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("eval")
|
|
7
|
+
.description("Run the deterministic recoverability eval over a compaction-input fixture/bundle (or a directory of them). " +
|
|
8
|
+
"Proves byte/hash/source-pointer recoverability ONLY — semantic and commitment preservation are NOT evaluated.")
|
|
9
|
+
.argument("<fixture-or-directory>", "Path to an eval fixture/bundle JSON file, or a directory of them")
|
|
10
|
+
.option("--out <dir>", "Directory where eval report artifacts should be written")
|
|
11
|
+
.action(async (inputPath, options) => {
|
|
12
|
+
await runEngineCommand(async () => {
|
|
13
|
+
// The deterministic recoverability eval IS the proprietary methodology; the whole command is
|
|
14
|
+
// engine-gated (no public structural part). Lazy-load the engine: present in-repo/API, excluded
|
|
15
|
+
// from the public package. When absent, runEngineCommand prints the boundary message and exits
|
|
16
|
+
// cleanly (no stack trace) before any filesystem access.
|
|
17
|
+
const { evaluateFixtureDirectory, evaluateFixtureFile, writeCorpusEvalArtifacts, writeEvalArtifacts } = await import("../../engine/eval-fixtures.js");
|
|
18
|
+
console.log(chalk.cyan("compaction eval"));
|
|
19
|
+
console.log("Deterministic byte/hash/source-pointer recoverability check. " +
|
|
20
|
+
"No model call, no fuzzy matching, no savings claim. " +
|
|
21
|
+
"Semantic preservation and task-critical commitment preservation are NOT evaluated (reported as not_evaluated).");
|
|
22
|
+
const stats = await stat(inputPath);
|
|
23
|
+
if (stats.isDirectory()) {
|
|
24
|
+
const corpus = await evaluateFixtureDirectory(inputPath);
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log(`Corpus: ${corpus.trace_count} trace(s) — ` +
|
|
27
|
+
`recoverability passed=${corpus.recoverability_rollup.passed}, ` +
|
|
28
|
+
`failed=${corpus.recoverability_rollup.failed}, ` +
|
|
29
|
+
`not_computed(fail-closed)=${corpus.recoverability_rollup.not_computed}`);
|
|
30
|
+
console.log(`Recoverability pass rate (over computed): ${corpus.recoverability_pass_rate === null ? "n/a" : `${(corpus.recoverability_pass_rate * 100).toFixed(1)}%`}`);
|
|
31
|
+
console.log(`Real-captured results: ${corpus.real_captured_count}`);
|
|
32
|
+
console.log("Semantic preservation: not_evaluated (never scored). Commitment preservation: not_evaluated (never scored).");
|
|
33
|
+
if (options.out) {
|
|
34
|
+
const paths = await writeCorpusEvalArtifacts(corpus, options.out);
|
|
35
|
+
console.log(chalk.green(`Wrote ${paths.evalReportJsonPath}`));
|
|
36
|
+
console.log(chalk.green(`Wrote ${paths.evalReportMarkdownPath}`));
|
|
37
|
+
}
|
|
38
|
+
// Non-zero exit if any trace failed recoverability (fail-closed signal for CI use).
|
|
39
|
+
if (corpus.recoverability_rollup.failed > 0) {
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const result = await evaluateFixtureFile(inputPath);
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log(`Recoverability: ${result.recoverability.toUpperCase()}`);
|
|
47
|
+
console.log(` ${result.recoverability_reason}`);
|
|
48
|
+
console.log(`Evidence source: ${result.evidence_source} (from ${result.evidence_source_type})`);
|
|
49
|
+
console.log(`Real captured: ${result.real_captured}`);
|
|
50
|
+
console.log(`Semantic preservation: ${result.semantic_preservation} (NOT scored). ` +
|
|
51
|
+
`Commitment preservation: ${result.commitment_preservation} (NOT scored).`);
|
|
52
|
+
for (const check of result.recoverability_checks) {
|
|
53
|
+
console.log(` [${check.status}] ${check.label}`);
|
|
54
|
+
}
|
|
55
|
+
if (options.out) {
|
|
56
|
+
const paths = await writeEvalArtifacts(result, options.out);
|
|
57
|
+
console.log(chalk.green(`Wrote ${paths.evalReportJsonPath}`));
|
|
58
|
+
console.log(chalk.green(`Wrote ${paths.evalReportMarkdownPath}`));
|
|
59
|
+
}
|
|
60
|
+
if (result.recoverability === "failed") {
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=eval.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* `compaction export-session-seed --apply-dir <dir> [--out <file>]`
|
|
4
|
+
*
|
|
5
|
+
* Turns an APPROVED `apply-context` artifacts directory into a pasteable Lane B treatment
|
|
6
|
+
* session-seed prompt (FIXED-PLAN WORKFLOW-EFFICIENCY TREATMENT SETUP). Refusal-by-default:
|
|
7
|
+
* requires a valid approval record (`approved_for_in_workflow_use`), a hash-matching approved
|
|
8
|
+
* context, and a hash-matching retained original — otherwise it refuses, exits non-zero, and writes
|
|
9
|
+
* NO seed. Strictly read-only over the apply artifacts. No provider call, no network, no auto-apply.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerExportSessionSeedCommand(program: Command): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { runEngineCommand } from "../engine-degrade.js";
|
|
3
|
+
/**
|
|
4
|
+
* `compaction export-session-seed --apply-dir <dir> [--out <file>]`
|
|
5
|
+
*
|
|
6
|
+
* Turns an APPROVED `apply-context` artifacts directory into a pasteable Lane B treatment
|
|
7
|
+
* session-seed prompt (FIXED-PLAN WORKFLOW-EFFICIENCY TREATMENT SETUP). Refusal-by-default:
|
|
8
|
+
* requires a valid approval record (`approved_for_in_workflow_use`), a hash-matching approved
|
|
9
|
+
* context, and a hash-matching retained original — otherwise it refuses, exits non-zero, and writes
|
|
10
|
+
* NO seed. Strictly read-only over the apply artifacts. No provider call, no network, no auto-apply.
|
|
11
|
+
*/
|
|
12
|
+
export function registerExportSessionSeedCommand(program) {
|
|
13
|
+
program
|
|
14
|
+
.command("export-session-seed")
|
|
15
|
+
.description("Export a PASTEABLE Lane B treatment session-seed (TREATMENT SETUP, NOT billing-confirmed savings; operator-applied, NOT auto-apply) from an APPROVED apply-context artifacts dir. Refuses unless approved + hashes match. Read-only; no provider call.")
|
|
16
|
+
.requiredOption("--apply-dir <dir>", "The apply-context artifacts directory (must contain an approval record)")
|
|
17
|
+
.option("--out <file>", "Where to write the session seed (default: <apply-dir>/session-seed.md)")
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
await runEngineCommand(async () => {
|
|
20
|
+
// Lazy-load the proprietary engine: present in-repo/API, excluded from the public package.
|
|
21
|
+
// When absent, runEngineCommand prints the boundary message and exits cleanly (no stack trace).
|
|
22
|
+
const { exportSessionSeed } = await import("../../engine/session-seed-export.js");
|
|
23
|
+
console.log(chalk.cyan("compaction export-session-seed (Lane B treatment setup)"));
|
|
24
|
+
console.log("Operator-applied, NOT auto-apply. compaction.dev starts no session and applies no context automatically. No provider call, no network.");
|
|
25
|
+
const result = await exportSessionSeed({
|
|
26
|
+
applyDir: options.applyDir,
|
|
27
|
+
outFile: options.out
|
|
28
|
+
});
|
|
29
|
+
if (result.refused) {
|
|
30
|
+
console.error(chalk.red("Refused — no session seed written:"));
|
|
31
|
+
for (const reason of result.reasons) {
|
|
32
|
+
console.error(chalk.red(`- ${reason}`));
|
|
33
|
+
}
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log(result.consoleSummary);
|
|
38
|
+
console.log(chalk.green(`Wrote ${result.seedPath}`));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=export-session-seed.js.map
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import readline from "node:readline/promises";
|
|
6
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { writeJsonArtifact, writeTextArtifact } from "../../core/artifact-writer.js";
|
|
9
|
+
import { readCompactionRunReports } from "../../core/run-aggregator.js";
|
|
10
|
+
import { buildFeedbackBundle, filterUsageMetadata, formatBundlePreview, renderBundleReadme, UNKNOWN } from "../../core/feedback-bundle.js";
|
|
11
|
+
const BUNDLE_DIR_DEFAULT = path.join(".compaction", "feedback-bundle");
|
|
12
|
+
async function readPackageVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
// dist/cli/commands -> dist -> package root
|
|
16
|
+
const pkgPath = path.resolve(here, "..", "..", "..", "package.json");
|
|
17
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
18
|
+
return typeof pkg.version === "string" ? pkg.version : UNKNOWN;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return UNKNOWN;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Best-effort install-method classification from argv0 — NON-sensitive, no paths leaked. */
|
|
25
|
+
function detectInstallMethod() {
|
|
26
|
+
const argv0 = process.argv[1] ?? "";
|
|
27
|
+
if (argv0.includes(`${path.sep}_npx${path.sep}`) || argv0.includes(`${path.sep}npx${path.sep}`)) {
|
|
28
|
+
return "npx";
|
|
29
|
+
}
|
|
30
|
+
if (argv0.includes(`${path.sep}node_modules${path.sep}.bin${path.sep}`)) {
|
|
31
|
+
return "local-node_modules";
|
|
32
|
+
}
|
|
33
|
+
if (argv0.includes(`${path.sep}lib${path.sep}node_modules${path.sep}`) || argv0.includes(`${path.sep}global${path.sep}`)) {
|
|
34
|
+
return "global-npm";
|
|
35
|
+
}
|
|
36
|
+
return UNKNOWN;
|
|
37
|
+
}
|
|
38
|
+
function parseTriState(value, flag) {
|
|
39
|
+
if (value === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
const normalized = value.trim().toLowerCase();
|
|
42
|
+
if (normalized === "yes" || normalized === "no" || normalized === "unknown") {
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`Invalid value for ${flag}: "${value}". Expected one of: yes, no, unknown.`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Evidence levels this COMMAND accepts, weakest→strongest. `billing_confirmed` is
|
|
49
|
+
* deliberately ABSENT: this flow collects no invoice/billing/export evidence, so it must
|
|
50
|
+
* never be allowed to emit a billing-confirmed label. Supplying it is an explicit error.
|
|
51
|
+
*/
|
|
52
|
+
const ACCEPTED_EVIDENCE = [
|
|
53
|
+
"measured_input_token_reduction",
|
|
54
|
+
"output_token_delta_observed",
|
|
55
|
+
"recoverability_verified",
|
|
56
|
+
"applied_context",
|
|
57
|
+
"workflow_confirmed",
|
|
58
|
+
"usage_confirmed"
|
|
59
|
+
];
|
|
60
|
+
const ACCEPTED_EVIDENCE_SET = new Set([...ACCEPTED_EVIDENCE, "unknown"]);
|
|
61
|
+
function parseEvidenceLevel(value) {
|
|
62
|
+
if (value === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
const normalized = value.trim().toLowerCase();
|
|
65
|
+
if (normalized === "billing_confirmed") {
|
|
66
|
+
throw new Error('Invalid --evidence-level: "billing_confirmed" is not accepted by `feedback`. ' +
|
|
67
|
+
"This command collects NO invoice/billing/export evidence, so it cannot emit a " +
|
|
68
|
+
"billing-confirmed label. Use at most usage_confirmed (only with --usage-metadata).");
|
|
69
|
+
}
|
|
70
|
+
if (!ACCEPTED_EVIDENCE_SET.has(normalized)) {
|
|
71
|
+
throw new Error(`Invalid --evidence-level: "${value}". Allowed: ${[...ACCEPTED_EVIDENCE_SET].join(", ")}.`);
|
|
72
|
+
}
|
|
73
|
+
return normalized;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Cap a tester-supplied evidence level at what the inputs actually support — never emit a
|
|
77
|
+
* label stronger than the evidence. `usage_confirmed` requires real usage metadata; without
|
|
78
|
+
* it the strongest reachable rung is `workflow_confirmed`. Returns the (possibly lowered)
|
|
79
|
+
* level plus a flag when it was capped, so stdout can explain the downgrade honestly.
|
|
80
|
+
*/
|
|
81
|
+
function capEvidenceLevel(level, hasUsageMetadata) {
|
|
82
|
+
if (level === undefined || level === "unknown")
|
|
83
|
+
return { level, capped: false };
|
|
84
|
+
if (level === "usage_confirmed" && !hasUsageMetadata) {
|
|
85
|
+
// No usage metadata supplied → the strongest honest rung is workflow_confirmed.
|
|
86
|
+
return { level: "workflow_confirmed", capped: true };
|
|
87
|
+
}
|
|
88
|
+
return { level, capped: false };
|
|
89
|
+
}
|
|
90
|
+
function parseWorkflowOutcome(value) {
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
const normalized = value.trim().toLowerCase();
|
|
94
|
+
if (["succeeded", "failed", "partial", "unknown"].includes(normalized)) {
|
|
95
|
+
return normalized;
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Invalid --workflow-outcome: "${value}". Expected: succeeded, failed, partial, unknown.`);
|
|
98
|
+
}
|
|
99
|
+
async function readUsageMetadata(filePath) {
|
|
100
|
+
if (!filePath)
|
|
101
|
+
return undefined;
|
|
102
|
+
const raw = await readFile(filePath, "utf8");
|
|
103
|
+
const parsed = JSON.parse(raw);
|
|
104
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
105
|
+
throw new Error("--usage-metadata file must contain a JSON object of metadata fields.");
|
|
106
|
+
}
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
async function readDiagnostics(filePath) {
|
|
110
|
+
if (!filePath)
|
|
111
|
+
return undefined;
|
|
112
|
+
return readFile(filePath, "utf8");
|
|
113
|
+
}
|
|
114
|
+
/** Aggregate INPUT-token signals from local run reports. Output-token delta stays unknown
|
|
115
|
+
* unless a report carries it (reports do not, so it remains unknown — honest by default). */
|
|
116
|
+
async function aggregateSignals(runsDir) {
|
|
117
|
+
const { reports } = await readCompactionRunReports(runsDir);
|
|
118
|
+
let originalInput = 0;
|
|
119
|
+
let compactedInput = 0;
|
|
120
|
+
let inputSaved = 0;
|
|
121
|
+
let costDelta = 0;
|
|
122
|
+
let costObserved = false;
|
|
123
|
+
for (const { report } of reports) {
|
|
124
|
+
originalInput += report.original_input_tokens;
|
|
125
|
+
compactedInput += report.compacted_input_tokens;
|
|
126
|
+
inputSaved += report.tokens_saved;
|
|
127
|
+
if (Number.isFinite(report.saving_per_run)) {
|
|
128
|
+
costDelta += report.saving_per_run;
|
|
129
|
+
costObserved = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
run_count: reports.length,
|
|
134
|
+
total_original_input_tokens: originalInput,
|
|
135
|
+
total_compacted_input_tokens: compactedInput,
|
|
136
|
+
total_input_tokens_saved: inputSaved,
|
|
137
|
+
// Reports carry INPUT-token deltas only; output-token delta is not observable here.
|
|
138
|
+
output_token_delta: UNKNOWN,
|
|
139
|
+
estimated_cost_delta_usd: costObserved ? Math.round((costDelta + Number.EPSILON) * 1e6) / 1e6 : UNKNOWN
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function confirmWrite() {
|
|
143
|
+
if (!input.isTTY) {
|
|
144
|
+
// No interactive terminal and no explicit flag → fail closed.
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const rl = readline.createInterface({ input, output });
|
|
148
|
+
try {
|
|
149
|
+
const answer = await rl.question("Write this feedback bundle locally? It will NOT be uploaded. Type yes to confirm: ");
|
|
150
|
+
return answer.trim().toLowerCase() === "yes";
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
rl.close();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function registerFeedbackCommand(program) {
|
|
157
|
+
program
|
|
158
|
+
.command("feedback")
|
|
159
|
+
.description("Build a REDACTED, privacy-safe feedback bundle (local file only — never uploaded) for the beta learning loop.")
|
|
160
|
+
.option("--redact", "Build the redacted feedback bundle (default behavior; kept for explicitness)")
|
|
161
|
+
.option("--yes", "Confirm writing the bundle without an interactive prompt (explicit gesture)")
|
|
162
|
+
.option("--confirm", "Alias for --yes: explicitly confirm writing the bundle")
|
|
163
|
+
.option("--out <dir>", "Output directory for the bundle", BUNDLE_DIR_DEFAULT)
|
|
164
|
+
.option("--runs <dir>", "Compaction runs directory to aggregate", ".compaction/runs")
|
|
165
|
+
.option("--command-path <name>", "Which compaction command produced the run (e.g. compact)")
|
|
166
|
+
.option("--evidence-level <level>", "Weakest-supported evidence label (billing_confirmed NOT accepted; usage_confirmed needs --usage-metadata)")
|
|
167
|
+
.option("--recoverability <status>", "Recoverability status string (e.g. verified, unverified)")
|
|
168
|
+
.option("--applied-context <yes|no|unknown>", "Was compacted context applied to the workflow?")
|
|
169
|
+
.option("--workflow-outcome <outcome>", "Workflow outcome: succeeded|failed|partial|unknown")
|
|
170
|
+
.option("--missing-context <yes|no|unknown>", "Did the workflow report missing context?")
|
|
171
|
+
.option("--provider <name>", "Provider name, IF you choose to supply it")
|
|
172
|
+
.option("--model <name>", "Model name, IF you choose to supply it")
|
|
173
|
+
.option("--usage-metadata <file>", "Path to a JSON file of provider usage metadata to include")
|
|
174
|
+
.option("--diagnostics-file <file>", "Path to an error/log file to include AFTER best-effort redaction")
|
|
175
|
+
.action(async (options) => {
|
|
176
|
+
console.log(chalk.cyan("compaction feedback"));
|
|
177
|
+
console.log("Local-first, fail-closed. NO network, NO upload, NO telemetry. Redaction is BEST-EFFORT, not perfect.");
|
|
178
|
+
const usageMetadata = await readUsageMetadata(options.usageMetadata);
|
|
179
|
+
// "Usage supplied" for capping = the allowlist keeps at least one non-sensitive
|
|
180
|
+
// field; a file of only unrecognized keys does NOT support a usage_confirmed label.
|
|
181
|
+
// It must carry at least one NUMERIC token count — `model`/`provider` strings
|
|
182
|
+
// alone do not substantiate `usage_confirmed` (Codex P2 on #300).
|
|
183
|
+
const filteredUsage = filterUsageMetadata(usageMetadata);
|
|
184
|
+
const hasUsageMetadata = Boolean(filteredUsage &&
|
|
185
|
+
Object.values(filteredUsage.kept).some((v) => typeof v === "number"));
|
|
186
|
+
const requestedEvidence = parseEvidenceLevel(options.evidenceLevel);
|
|
187
|
+
const { level: cappedEvidence, capped } = capEvidenceLevel(requestedEvidence, hasUsageMetadata);
|
|
188
|
+
if (capped) {
|
|
189
|
+
console.log(chalk.yellow(`Note: --evidence-level "${requestedEvidence}" exceeds the supported evidence ` +
|
|
190
|
+
`(no usage metadata supplied); capped to "${cappedEvidence}". A label is never emitted above the evidence.`));
|
|
191
|
+
}
|
|
192
|
+
const supplied = {
|
|
193
|
+
command_path: options.commandPath,
|
|
194
|
+
evidence_level: cappedEvidence,
|
|
195
|
+
recoverability_status: options.recoverability,
|
|
196
|
+
applied_context: parseTriState(options.appliedContext, "--applied-context"),
|
|
197
|
+
workflow_outcome: parseWorkflowOutcome(options.workflowOutcome),
|
|
198
|
+
missing_context: parseTriState(options.missingContext, "--missing-context"),
|
|
199
|
+
provider: options.provider,
|
|
200
|
+
model: options.model,
|
|
201
|
+
usage_metadata: usageMetadata,
|
|
202
|
+
diagnostic_text: await readDiagnostics(options.diagnosticsFile)
|
|
203
|
+
};
|
|
204
|
+
const environment = {
|
|
205
|
+
cli_version: await readPackageVersion(),
|
|
206
|
+
os: `${os.platform()} ${os.release()}`,
|
|
207
|
+
node_version: process.version,
|
|
208
|
+
package_version: await readPackageVersion(),
|
|
209
|
+
install_method: detectInstallMethod()
|
|
210
|
+
};
|
|
211
|
+
const aggregate = await aggregateSignals(options.runs ?? ".compaction/runs");
|
|
212
|
+
const bundleInput = { environment, aggregate, supplied };
|
|
213
|
+
const bundle = buildFeedbackBundle(bundleInput);
|
|
214
|
+
// PREVIEW EXACTLY what will be written — always, before any write.
|
|
215
|
+
console.log("");
|
|
216
|
+
console.log(formatBundlePreview(bundle));
|
|
217
|
+
console.log("");
|
|
218
|
+
// Explicit-confirm gate, fail-closed.
|
|
219
|
+
const explicit = Boolean(options.yes) || Boolean(options.confirm);
|
|
220
|
+
const confirmed = explicit || (await confirmWrite());
|
|
221
|
+
if (!confirmed) {
|
|
222
|
+
console.log(chalk.yellow("Preview only — nothing written. Re-run with --yes (or --confirm) to write the bundle locally."));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const outDir = options.out ?? BUNDLE_DIR_DEFAULT;
|
|
226
|
+
const jsonPath = await writeJsonArtifact(outDir, "feedback-bundle.json", bundle);
|
|
227
|
+
const readmePath = await writeTextArtifact(outDir, "README.md", renderBundleReadme(bundle));
|
|
228
|
+
console.log(chalk.green(`Wrote ${jsonPath}`));
|
|
229
|
+
console.log(chalk.green(`Wrote ${readmePath}`));
|
|
230
|
+
console.log("This bundle is LOCAL only — it was NOT uploaded. You choose whether to send it. Redaction is best-effort; inspect before sending.");
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=feedback.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { listTraceAdapters } from "../../core/trace-adapters.js";
|
|
3
|
+
import { importTraceFile } from "../../core/trace-intake.js";
|
|
4
|
+
function printSupportedSources() {
|
|
5
|
+
console.log(chalk.cyan("compaction import sources"));
|
|
6
|
+
for (const adapter of listTraceAdapters()) {
|
|
7
|
+
console.log(`- ${adapter.id}: ${adapter.displayName}`);
|
|
8
|
+
}
|
|
9
|
+
console.log("Live provider/runtime integrations remain future work; codex-exec-jsonl reads only local export files.");
|
|
10
|
+
}
|
|
11
|
+
export function registerImportCommand(program) {
|
|
12
|
+
program
|
|
13
|
+
.command("import")
|
|
14
|
+
.argument("[trace-file]", "Path to a local trace JSON, messages JSON, or supported JSONL file")
|
|
15
|
+
.option("--source <source>", "Input source: agent-trace, messages, codex-exec-jsonl, or unknown")
|
|
16
|
+
.option("--out <dir>", "Directory where normalized intake artifacts should be written")
|
|
17
|
+
.option("--operator-export", "Assert this file is your own real `codex exec --json` export (operator provenance). " +
|
|
18
|
+
"Without this flag, imports default to the weakest honest evidence tier (fixture). " +
|
|
19
|
+
"With it, a Codex JSONL import is labeled imported_local — still below real_captured and capped at conditional.")
|
|
20
|
+
.option("--list-sources", "List local trace adapter sources supported by import v0")
|
|
21
|
+
.description("Import a local trace file into the internal AgentTrace format.")
|
|
22
|
+
.action(async (traceFile, options) => {
|
|
23
|
+
if (options.listSources) {
|
|
24
|
+
printSupportedSources();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!traceFile) {
|
|
28
|
+
throw new Error("Missing trace-file. Provide a local JSON file path or run compaction import --list-sources.");
|
|
29
|
+
}
|
|
30
|
+
if (!options.source) {
|
|
31
|
+
throw new Error("Missing required option --source <source>.");
|
|
32
|
+
}
|
|
33
|
+
if (!options.out) {
|
|
34
|
+
throw new Error("Missing required option --out <dir>.");
|
|
35
|
+
}
|
|
36
|
+
const artifacts = await importTraceFile(traceFile, options.source, options.out, {
|
|
37
|
+
operatorExport: options.operatorExport === true
|
|
38
|
+
});
|
|
39
|
+
console.log(chalk.cyan("compaction import"));
|
|
40
|
+
console.log(`Status: ${artifacts.report.status}`);
|
|
41
|
+
console.log(`Source: ${artifacts.report.source}`);
|
|
42
|
+
console.log(`Adapter: ${artifacts.report.adapter_id}`);
|
|
43
|
+
console.log(`Messages: ${artifacts.report.message_count}`);
|
|
44
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.intakeReportJsonPath}`));
|
|
45
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.intakeReportMarkdownPath}`));
|
|
46
|
+
if (artifacts.normalizedTrace) {
|
|
47
|
+
console.log(chalk.green(`Wrote ${artifacts.paths.normalizedTracePath}`));
|
|
48
|
+
console.log("");
|
|
49
|
+
console.log(chalk.bold("Next: analyze the imported trace to see waste attribution:"));
|
|
50
|
+
console.log(` ${artifacts.report.recommended_next_command}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(chalk.red("No normalized trace was generated."));
|
|
54
|
+
console.log("The input did not normalize to an AgentTrace. See intake-report.md for the per-line reasons, " +
|
|
55
|
+
"then re-run with the correct --source (compaction import --list-sources).");
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=import.js.map
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { defaultProjectsDir, discoverClaudeCodeSessions } from "../../core/adapters/claude-code-discovery.js";
|
|
4
|
+
/**
|
|
5
|
+
* `compaction init` — first-run, value-led terminal onboarding.
|
|
6
|
+
*
|
|
7
|
+
* Converts "I installed a CLI" into "I am one command away from seeing my own
|
|
8
|
+
* workflow's context waste." It is guidance plus ONE read-only local detection
|
|
9
|
+
* (does a Claude Code projects directory exist, and how many sessions are in it).
|
|
10
|
+
*
|
|
11
|
+
* Invariants (non-negotiable, hold on every path):
|
|
12
|
+
* - writes nothing
|
|
13
|
+
* - makes no network call
|
|
14
|
+
* - reads no provider credential
|
|
15
|
+
* - needs no repo access
|
|
16
|
+
* A provider credential is ONLY ever used if the operator explicitly runs
|
|
17
|
+
* `capture provider-usage` (or enters operator-run measurement records).
|
|
18
|
+
*
|
|
19
|
+
* Proprietary boundary (per mvp-plan.md §13): output names high-level waste
|
|
20
|
+
* CATEGORIES and policy-FAMILY names plus before/after deltas, risk, safety,
|
|
21
|
+
* approval requirement, artifacts, and the next action — never the proprietary
|
|
22
|
+
* scoring formulas, thresholds, hidden heuristics, future-policy internals, or
|
|
23
|
+
* commercial / moat logic.
|
|
24
|
+
*
|
|
25
|
+
* Presentation note: this is a calm, structured, minimal first-run surface.
|
|
26
|
+
* Hierarchy comes from spacing, dim secondary text, and a few restrained
|
|
27
|
+
* symbols — not from a wall of enumerated text.
|
|
28
|
+
*/
|
|
29
|
+
// Clean one-line wordmark. The hand-drawn ASCII banner read busy at small
|
|
30
|
+
// terminal sizes; a spaced, bold mark with a rule under it reads premium.
|
|
31
|
+
const MARK = chalk.bold("compaction");
|
|
32
|
+
const RULE = chalk.dim("─".repeat(58));
|
|
33
|
+
const HEADLINE = "Context optimization for AI agents";
|
|
34
|
+
const VALUE_PROP = chalk.dim("Reduce avoidable input/output tokens while preserving task-critical\n context. Local-first — nothing is applied without your approval.");
|
|
35
|
+
// The value loop, compressed to one line per step. Each step earns its place.
|
|
36
|
+
const LOOP = [
|
|
37
|
+
["capture", "a real trace from your own agent workflow"],
|
|
38
|
+
["analyze", "where token spend and avoidable context waste are"],
|
|
39
|
+
["recommend", "a candidate policy family + estimated before/after delta"],
|
|
40
|
+
["compact", "the before/after token delta, and which family produced it"],
|
|
41
|
+
["inspect", "safety evidence — source pointers, hashes, recoverable originals"],
|
|
42
|
+
["approve", "emit a compacted context for your next call. No auto-apply."]
|
|
43
|
+
];
|
|
44
|
+
// Real-workflow paths only. compaction operates on the user's OWN agent
|
|
45
|
+
// workflow; there is no demo / synthetic first-value path here. Demo fixtures
|
|
46
|
+
// exist solely for tests, package smoke, and explicitly-labeled docs.
|
|
47
|
+
const PRIMARY = {
|
|
48
|
+
key: "claude-code",
|
|
49
|
+
title: "Claude Code",
|
|
50
|
+
command: "compaction capture claude-code --discover"
|
|
51
|
+
};
|
|
52
|
+
const SECONDARY = [
|
|
53
|
+
{
|
|
54
|
+
key: "codex",
|
|
55
|
+
title: "Codex",
|
|
56
|
+
command: "codex exec --json '<task>' > run.jsonl && compaction import run.jsonl --source codex-exec-jsonl --out ./my-trace"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "openai-agents",
|
|
60
|
+
title: "OpenAI Agents",
|
|
61
|
+
command: "compaction capture openai-agents --out ./my-trace -- <your-command>"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "import",
|
|
65
|
+
title: "Local trace",
|
|
66
|
+
command: "compaction import --list-sources # then: import <file> --source <source> --out ./my-trace"
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
// The follow-up sequence is identical for every path once a trace exists.
|
|
70
|
+
const FOLLOW_UP = [
|
|
71
|
+
"compaction analyze ./my-trace/captured-trace.json",
|
|
72
|
+
"compaction recommend ./my-trace/captured-trace.json",
|
|
73
|
+
"compaction compact ./my-trace/captured-trace.json --out ./my-trace/compacted",
|
|
74
|
+
"compaction apply-context ./my-trace/captured-trace.json --out ./my-trace/apply"
|
|
75
|
+
];
|
|
76
|
+
function header(text) {
|
|
77
|
+
return chalk.bold(text);
|
|
78
|
+
}
|
|
79
|
+
function loopBlock() {
|
|
80
|
+
const width = Math.max(...LOOP.map(([verb]) => verb.length));
|
|
81
|
+
return LOOP.map(([verb, desc]) => ` ${chalk.cyan(verb.padEnd(width))} ${chalk.dim(desc)}`);
|
|
82
|
+
}
|
|
83
|
+
function primaryBlock(sessionCount) {
|
|
84
|
+
const tag = chalk.green(` ✓ ${sessionCount} session(s) found`);
|
|
85
|
+
return [
|
|
86
|
+
`${chalk.bold("▸ " + PRIMARY.title)}${chalk.dim(" · recommended")}${tag}`,
|
|
87
|
+
` ${chalk.cyan(PRIMARY.command)}`
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
function secondaryBlock() {
|
|
91
|
+
const lines = [chalk.dim("Other workflows")];
|
|
92
|
+
for (const p of SECONDARY) {
|
|
93
|
+
lines.push(` ${chalk.dim(p.title)}`);
|
|
94
|
+
lines.push(` ${chalk.cyan(p.command)}`);
|
|
95
|
+
}
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
98
|
+
function emptyStateBlock(projectsDir) {
|
|
99
|
+
// Calm, premium no-sessions state: explain, show how to create one, and give
|
|
100
|
+
// the exact rerun command. The secondary real paths follow below.
|
|
101
|
+
return [
|
|
102
|
+
`${chalk.bold("▸ " + PRIMARY.title)}${chalk.dim(" · recommended")}`,
|
|
103
|
+
chalk.dim(` No sessions found under ${projectsDir}.`),
|
|
104
|
+
chalk.dim(" Start or run a Claude Code session in any project, then re-run:"),
|
|
105
|
+
` ${chalk.cyan("compaction capture claude-code --discover")}`
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
function afterCaptureBlock() {
|
|
109
|
+
return [
|
|
110
|
+
header("Then, on the trace you captured"),
|
|
111
|
+
"",
|
|
112
|
+
...FOLLOW_UP.map((c) => ` ${chalk.cyan(c)}`),
|
|
113
|
+
"",
|
|
114
|
+
chalk.dim(" You'll see: waste pattern CATEGORY, high-level POLICY FAMILY,"),
|
|
115
|
+
chalk.dim(" before/after input/output tokens, the TOKEN-COUNT SOURCE"),
|
|
116
|
+
chalk.dim(" (provider-reported vs estimated — never an estimate as reported),"),
|
|
117
|
+
chalk.dim(" RISK LEVEL, safety + source_recoverability, the APPROVAL REQUIREMENT"),
|
|
118
|
+
chalk.dim(" (--approve-in-workflow-use; no auto-apply), and the next action.")
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
function footerBlock() {
|
|
122
|
+
return [
|
|
123
|
+
chalk.dim("Local-first: no network, no credentials, no repo access for any of the above."),
|
|
124
|
+
chalk.dim("A provider credential is only ever used if YOU explicitly run"),
|
|
125
|
+
chalk.dim("`compaction capture provider-usage` or enter operator-run records.")
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
export function registerInitCommand(program) {
|
|
129
|
+
program
|
|
130
|
+
.command("init")
|
|
131
|
+
.description("First-run onboarding: the value loop, how to capture YOUR first real trace, and the exact next commands. " +
|
|
132
|
+
"Guidance + one read-only local detection only — writes nothing, no network, no credentials.")
|
|
133
|
+
.option("--path <path>", "Focus one input path: claude-code | codex | openai-agents | import")
|
|
134
|
+
.option("--projects-dir <dir>", "Claude Code projects directory to detect against (default: the standard local location)")
|
|
135
|
+
.action(async (options) => {
|
|
136
|
+
// Validate --path early so the error stays a clean one-liner.
|
|
137
|
+
const allKeys = [PRIMARY.key, ...SECONDARY.map((p) => p.key)];
|
|
138
|
+
if (options.path && !allKeys.includes(options.path)) {
|
|
139
|
+
console.error(`unknown --path '${options.path}'. Valid: ${allKeys.join(" | ")}`);
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Read-only local detection (no writes, no network).
|
|
144
|
+
const projectsDir = options.projectsDir ?? defaultProjectsDir();
|
|
145
|
+
let claudeDetected = false;
|
|
146
|
+
let sessionCount = 0;
|
|
147
|
+
if (existsSync(projectsDir)) {
|
|
148
|
+
try {
|
|
149
|
+
const discovery = await discoverClaudeCodeSessions({ projectsDir });
|
|
150
|
+
sessionCount = discovery.sessions.length;
|
|
151
|
+
claudeDetected = sessionCount > 0;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
claudeDetected = false; // detection is best-effort; onboarding never fails on it
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const focus = options.path;
|
|
158
|
+
const lines = [];
|
|
159
|
+
// 1. Mark + value prop.
|
|
160
|
+
lines.push("");
|
|
161
|
+
lines.push(`${MARK} ${chalk.dim("·")} ${HEADLINE}`);
|
|
162
|
+
lines.push(` ${VALUE_PROP}`);
|
|
163
|
+
lines.push("");
|
|
164
|
+
lines.push(RULE);
|
|
165
|
+
lines.push("");
|
|
166
|
+
// 2. The value loop — what the tool actually does, one line per step.
|
|
167
|
+
lines.push(header("How it works"));
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push(...loopBlock());
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push(chalk.dim(" Estimates are labeled as estimates; provider-reported as provider-reported."));
|
|
172
|
+
lines.push(chalk.dim(" Nothing is called a saving until it is measured."));
|
|
173
|
+
lines.push("");
|
|
174
|
+
lines.push(RULE);
|
|
175
|
+
lines.push("");
|
|
176
|
+
// 3. Start here — primary path (Claude Code) or a focused secondary path.
|
|
177
|
+
if (!focus || focus === "claude-code") {
|
|
178
|
+
lines.push(header("Start here"));
|
|
179
|
+
lines.push("");
|
|
180
|
+
if (claudeDetected) {
|
|
181
|
+
lines.push(...primaryBlock(sessionCount));
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
lines.push(...emptyStateBlock(projectsDir));
|
|
185
|
+
}
|
|
186
|
+
lines.push("");
|
|
187
|
+
// 4. Secondary real-workflow paths (compact, secondary styling).
|
|
188
|
+
// Only on the default (unfocused) view.
|
|
189
|
+
if (!focus) {
|
|
190
|
+
lines.push(...secondaryBlock());
|
|
191
|
+
lines.push("");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Focused secondary path.
|
|
196
|
+
const p = SECONDARY.find((s) => s.key === focus);
|
|
197
|
+
lines.push(header("Start here"));
|
|
198
|
+
lines.push("");
|
|
199
|
+
lines.push(` ${chalk.bold(p.title)}`);
|
|
200
|
+
lines.push(` ${chalk.cyan(p.command)}`);
|
|
201
|
+
lines.push("");
|
|
202
|
+
}
|
|
203
|
+
lines.push(RULE);
|
|
204
|
+
lines.push("");
|
|
205
|
+
// 5. The follow-up sequence + what to expect.
|
|
206
|
+
lines.push(...afterCaptureBlock());
|
|
207
|
+
lines.push("");
|
|
208
|
+
lines.push(RULE);
|
|
209
|
+
lines.push("");
|
|
210
|
+
// 6. Local-first footer.
|
|
211
|
+
lines.push(...footerBlock());
|
|
212
|
+
lines.push("");
|
|
213
|
+
console.log(lines.join("\n"));
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=init.js.map
|