@compaction/cli 0.1.2 → 0.2.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/README.md +11 -1
- package/dist/cli/commands/compact.js +15 -0
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +130 -0
- package/dist/cli/commands/import.js +3 -1
- package/dist/cli/commands/init.js +184 -157
- package/dist/cli/commands/run.js +13 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/onboarding/InitTui.d.ts +52 -0
- package/dist/cli/onboarding/InitTui.js +153 -0
- package/dist/cli/onboarding/model.d.ts +39 -0
- package/dist/cli/onboarding/model.js +77 -0
- package/dist/cli/onboarding/should-use-tui.d.ts +25 -0
- package/dist/cli/onboarding/should-use-tui.js +35 -0
- package/dist/cli/onboarding/wordmark.d.ts +28 -0
- package/dist/cli/onboarding/wordmark.js +72 -0
- package/dist/core/aggregate-format.d.ts +3 -1
- package/dist/core/aggregate-format.js +18 -0
- package/dist/core/context-store-eval-cases.d.ts +6 -0
- package/dist/core/context-store-eval-cases.js +331 -0
- package/dist/core/context-store-eval.d.ts +140 -0
- package/dist/core/context-store-eval.js +138 -0
- package/dist/core/context-store-fs.d.ts +73 -0
- package/dist/core/context-store-fs.js +157 -0
- package/dist/core/context-store-sufficiency.d.ts +98 -0
- package/dist/core/context-store-sufficiency.js +135 -0
- package/dist/core/context-store.d.ts +155 -0
- package/dist/core/context-store.js +228 -0
- package/dist/core/report-generator.d.ts +32 -1
- package/dist/core/report-generator.js +67 -0
- package/dist/core/run-aggregator.d.ts +64 -0
- package/dist/core/run-aggregator.js +132 -3
- package/dist/core/session-aggregate.d.ts +43 -0
- package/dist/core/session-aggregate.js +89 -7
- package/dist/core/trace-adapters.d.ts +8 -0
- package/dist/core/trace-adapters.js +4 -0
- package/dist/core/types.d.ts +55 -0
- package/package.json +5 -1
package/dist/cli/commands/run.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { parseRunCommand, executeLocalCommand, persistLocalCommandRun } from "../../core/command-runner.js";
|
|
3
|
-
import { writeJsonArtifact } from "../../core/artifact-writer.js";
|
|
3
|
+
import { writeJsonArtifact, writeTextArtifact } from "../../core/artifact-writer.js";
|
|
4
|
+
import { formatCompactionMarkdownReport, withSavingsEvidence, withTraceFingerprint } from "../../core/report-generator.js";
|
|
4
5
|
import { localCommandRunToAgentTrace } from "../../core/run-trace-converter.js";
|
|
5
6
|
import { runEngineCommand } from "../engine-degrade.js";
|
|
6
7
|
export function registerRunCommand(program) {
|
|
@@ -23,6 +24,17 @@ export function registerRunCommand(program) {
|
|
|
23
24
|
const trace = localCommandRunToAgentTrace(run);
|
|
24
25
|
const tracePath = await writeJsonArtifact(persistedRun.outputDirectory, "trace.json", trace);
|
|
25
26
|
const artifacts = await writeCompactionArtifacts(trace, persistedRun.outputDirectory, run.runId);
|
|
27
|
+
// Attribution/integrity + per-run evidence label (V0.2 savings-evidence): re-persist
|
|
28
|
+
// report.json with the trace fingerprint (attribution/dedup) and the composed
|
|
29
|
+
// `savings_evidence` record (per-run label). `run` figures are local estimates (chars/4) —
|
|
30
|
+
// `local_estimate` rung 1. Additive; no existing report field/label changes.
|
|
31
|
+
const evidenceReport = withSavingsEvidence(withTraceFingerprint(artifacts.report, trace), {
|
|
32
|
+
costSource: "local_estimate"
|
|
33
|
+
});
|
|
34
|
+
await writeJsonArtifact(persistedRun.outputDirectory, "report.json", evidenceReport);
|
|
35
|
+
// Re-render report.md from the ENRICHED report so the per-run savings_evidence record
|
|
36
|
+
// appears in the human-readable artifact too (engine rendered it from the base report).
|
|
37
|
+
await writeTextArtifact(persistedRun.outputDirectory, "report.md", formatCompactionMarkdownReport(evidenceReport, artifacts.policy));
|
|
26
38
|
console.log(`Exit code: ${run.exitCode ?? "none"}`);
|
|
27
39
|
console.log(`Signal: ${run.signal ?? "none"}`);
|
|
28
40
|
console.log(`Duration: ${run.durationMs} ms`);
|
package/dist/cli/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { registerBillingDeltaCommand } from "./commands/billing-delta.js";
|
|
|
11
11
|
import { registerInitCommand } from "./commands/init.js";
|
|
12
12
|
import { registerCaptureCommand } from "./commands/capture.js";
|
|
13
13
|
import { registerCompactCommand } from "./commands/compact.js";
|
|
14
|
+
import { registerContextCommand } from "./commands/context.js";
|
|
14
15
|
import { registerEvalCommand } from "./commands/eval.js";
|
|
15
16
|
import { registerExportSessionSeedCommand } from "./commands/export-session-seed.js";
|
|
16
17
|
import { registerFeedbackCommand } from "./commands/feedback.js";
|
|
@@ -27,7 +28,7 @@ const program = new Command();
|
|
|
27
28
|
program
|
|
28
29
|
.name("compaction")
|
|
29
30
|
.description("CLI-first trace compaction tools for AI agent workflows.")
|
|
30
|
-
.version("0.
|
|
31
|
+
.version("0.2.0");
|
|
31
32
|
registerAnalyzeCommand(program);
|
|
32
33
|
registerAdapterCommand(program);
|
|
33
34
|
registerAggregateCommand(program);
|
|
@@ -39,6 +40,7 @@ registerBillingDeltaCommand(program);
|
|
|
39
40
|
registerInitCommand(program);
|
|
40
41
|
registerCaptureCommand(program);
|
|
41
42
|
registerCompactCommand(program);
|
|
43
|
+
registerContextCommand(program);
|
|
42
44
|
registerEvalCommand(program);
|
|
43
45
|
registerExportSessionSeedCommand(program);
|
|
44
46
|
registerFeedbackCommand(program);
|
|
@@ -62,6 +64,7 @@ const PUBLIC_COMMANDS = new Set([
|
|
|
62
64
|
"capture",
|
|
63
65
|
"import",
|
|
64
66
|
"analyze",
|
|
67
|
+
"context",
|
|
65
68
|
"spend",
|
|
66
69
|
"summary",
|
|
67
70
|
"aggregate",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type WorkflowCard } from "./model.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interactive first-run TUI for `compaction init` (Ink/React) — a minimal,
|
|
5
|
+
* premium, left-aligned, dark-terminal-first workflow chooser.
|
|
6
|
+
*
|
|
7
|
+
* Progressive enhancement gated behind a real TTY (see `decideInteractiveTui`):
|
|
8
|
+
* it NEVER runs in pipes/CI/NO_COLOR/dumb terminals or the test runner — those
|
|
9
|
+
* get the static screen — so it may freely rely on color and Unicode.
|
|
10
|
+
*
|
|
11
|
+
* The big COMPACTION wordmark is the visual hero; everything below is quiet and
|
|
12
|
+
* compact. Palette is restrained Compaction-blue only (no teal/cyan). The full
|
|
13
|
+
* UI is short and rendered into the alternate screen buffer (see `runInitTui`)
|
|
14
|
+
* so resizing repaints in place and never duplicates the wordmark.
|
|
15
|
+
*
|
|
16
|
+
* Invariants (identical to the static path): writes nothing, no network call,
|
|
17
|
+
* no credential read, features ONLY the free local-first workflows. It is a
|
|
18
|
+
* chooser, not an agent: no text input, no model call, nothing uploaded.
|
|
19
|
+
*/
|
|
20
|
+
export interface InitTuiDetection {
|
|
21
|
+
detected: boolean;
|
|
22
|
+
sessionCount: number;
|
|
23
|
+
projectsDir: string;
|
|
24
|
+
}
|
|
25
|
+
export type InitTuiResult = {
|
|
26
|
+
action: "select";
|
|
27
|
+
card: WorkflowCard;
|
|
28
|
+
} | {
|
|
29
|
+
action: "quit";
|
|
30
|
+
};
|
|
31
|
+
interface AppProps {
|
|
32
|
+
version: string;
|
|
33
|
+
detection: InitTuiDetection;
|
|
34
|
+
onDone: (result: InitTuiResult) => void;
|
|
35
|
+
}
|
|
36
|
+
export declare function App({ version, detection, onDone }: AppProps): React.ReactElement;
|
|
37
|
+
/**
|
|
38
|
+
* Render the TUI and resolve with the user's choice. Resolves to `{action:"quit"}`
|
|
39
|
+
* if the user exits without selecting. The caller prints the chosen command to
|
|
40
|
+
* the scrollback after this resolves.
|
|
41
|
+
*
|
|
42
|
+
* Renders into the terminal's ALTERNATE SCREEN BUFFER (like vim/less/fzf): no
|
|
43
|
+
* scrollback, so a reflow can never push a stale frame up into history. Resize
|
|
44
|
+
* redraw is handled INSIDE the component (reactive terminal size → re-render),
|
|
45
|
+
* so Ink owns the clear/repaint and the screen stays visible across resizes. On
|
|
46
|
+
* exit the screen is restored and the selected next-command prints normally.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runInitTui(props: {
|
|
49
|
+
version: string;
|
|
50
|
+
detection: InitTuiDetection;
|
|
51
|
+
}): Promise<InitTuiResult>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { Box, Text, useApp, useInput, useStdout, render } from "ink";
|
|
4
|
+
import { VALUE_PROMISE, STATUS_PILLS, WORKFLOW_CARDS, FREE_FLOW, PRIMARY } from "./model.js";
|
|
5
|
+
import { renderWordmark, fillGradient } from "./wordmark.js";
|
|
6
|
+
// Restrained Compaction-blue palette only.
|
|
7
|
+
const BLUE = "#3231cd"; // brand accent / wordmark fill (bottom of gradient)
|
|
8
|
+
const OUTLINE = "#232185"; // one darker brand-blue tone for the framed-outline strokes
|
|
9
|
+
const DIM = "gray";
|
|
10
|
+
const FRAME_MAX = 96;
|
|
11
|
+
function Wordmark({ version, maxWidth }) {
|
|
12
|
+
const mark = useMemo(() => renderWordmark(maxWidth), [maxWidth]);
|
|
13
|
+
// Blue-only vertical gradient for the solid fill; the framed-outline strokes
|
|
14
|
+
// are one darker brand-blue tone (inline 3D depth, no contrasting colour).
|
|
15
|
+
const grad = useMemo(() => fillGradient(mark.height), [mark.height]);
|
|
16
|
+
return (_jsxs(Box, { flexDirection: "column", children: [mark.rows.map((row, r) => (_jsx(Text, { children: row.map((seg, i) => {
|
|
17
|
+
if (seg.layer === "fill") {
|
|
18
|
+
return (_jsx(Text, { color: grad[r], bold: true, children: seg.text }, i));
|
|
19
|
+
}
|
|
20
|
+
if (seg.layer === "outline") {
|
|
21
|
+
return (_jsx(Text, { color: OUTLINE, children: seg.text }, i));
|
|
22
|
+
}
|
|
23
|
+
return _jsx(Text, { children: seg.text }, i);
|
|
24
|
+
}) }, r))), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: BLUE, bold: true, children: ["v", version] }), _jsx(Text, { color: DIM, children: " the Compaction CLI" })] })] }));
|
|
25
|
+
}
|
|
26
|
+
function StatusRow() {
|
|
27
|
+
return (_jsx(Box, { marginTop: 1, children: STATUS_PILLS.map((pill, i) => (_jsxs(Box, { children: [_jsx(Text, { color: BLUE, children: "\u25CF" }), _jsx(Text, { color: DIM, children: ` ${pill}` }), i < STATUS_PILLS.length - 1 ? _jsx(Text, { color: DIM, children: " " }) : null] }, pill))) }));
|
|
28
|
+
}
|
|
29
|
+
function badge(card, det, dim) {
|
|
30
|
+
if (card.key !== PRIMARY.key)
|
|
31
|
+
return null;
|
|
32
|
+
if (det.detected) {
|
|
33
|
+
return _jsx(Text, { color: dim ? DIM : BLUE, children: ` ✓ ${det.sessionCount} session(s) found` });
|
|
34
|
+
}
|
|
35
|
+
return _jsx(Text, { color: DIM, children: " · no sessions found yet" });
|
|
36
|
+
}
|
|
37
|
+
// The single highlighted card for the focused workflow. It hugs its content
|
|
38
|
+
// (alignSelf flex-start) so it reads as a card, not a full-width bar.
|
|
39
|
+
function ActiveCard({ card, det }) {
|
|
40
|
+
const recommended = card.key === PRIMARY.key;
|
|
41
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: BLUE, bold: true, children: recommended ? "▌ RECOMMENDED" : "▌ WORKFLOW" }), _jsxs(Box, { alignSelf: "flex-start", borderStyle: "round", borderColor: BLUE, paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "white", bold: true, children: card.title }), recommended ? _jsx(Text, { color: BLUE, children: " ★" }) : null, badge(card, det, false)] }), _jsx(Text, { color: BLUE, children: card.command })] })] }));
|
|
42
|
+
}
|
|
43
|
+
// Quiet, compact, borderless row for an unfocused workflow.
|
|
44
|
+
function QuietRow({ card, det }) {
|
|
45
|
+
const recommended = card.key === PRIMARY.key;
|
|
46
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: DIM, children: " ○ " }), _jsx(Text, { color: DIM, children: card.title }), recommended ? _jsx(Text, { color: DIM, children: " ★ recommended" }) : null, badge(card, det, true)] }));
|
|
47
|
+
}
|
|
48
|
+
// Below this width the full chooser cannot lay out cleanly; show a compact
|
|
49
|
+
// recovery message instead. It re-renders into the full UI when widened again.
|
|
50
|
+
const NARROW_MIN = 44;
|
|
51
|
+
// Reactive terminal size: re-render the tree on `resize` (debounced) so the
|
|
52
|
+
// layout recomputes against the new width and Ink redraws cleanly. Reading the
|
|
53
|
+
// size into React state (rather than poking the terminal with escape codes) is
|
|
54
|
+
// what keeps the TUI visible across resizes. Guards columns/rows === 0/undefined.
|
|
55
|
+
function useTerminalSize() {
|
|
56
|
+
const { stdout } = useStdout();
|
|
57
|
+
const read = () => {
|
|
58
|
+
const c = stdout && typeof stdout.columns === "number" && stdout.columns > 0 ? stdout.columns : FRAME_MAX;
|
|
59
|
+
const r = stdout && typeof stdout.rows === "number" && stdout.rows > 0 ? stdout.rows : 24;
|
|
60
|
+
return { cols: c, rows: r };
|
|
61
|
+
};
|
|
62
|
+
const [size, setSize] = useState(read);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!stdout || typeof stdout.on !== "function")
|
|
65
|
+
return;
|
|
66
|
+
let t = null;
|
|
67
|
+
const onResize = () => {
|
|
68
|
+
if (t)
|
|
69
|
+
clearTimeout(t);
|
|
70
|
+
t = setTimeout(() => setSize(read()), 24); // debounce bursts of resize events
|
|
71
|
+
};
|
|
72
|
+
stdout.on("resize", onResize);
|
|
73
|
+
return () => {
|
|
74
|
+
if (t)
|
|
75
|
+
clearTimeout(t);
|
|
76
|
+
if (typeof stdout.off === "function")
|
|
77
|
+
stdout.off("resize", onResize);
|
|
78
|
+
};
|
|
79
|
+
// read() closes over `stdout`; re-subscribe only if the stream changes.
|
|
80
|
+
}, [stdout]);
|
|
81
|
+
return size;
|
|
82
|
+
}
|
|
83
|
+
export function App({ version, detection, onDone }) {
|
|
84
|
+
const [index, setIndex] = useState(0);
|
|
85
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
86
|
+
const [showCommands, setShowCommands] = useState(false);
|
|
87
|
+
const { exit } = useApp();
|
|
88
|
+
const { cols } = useTerminalSize();
|
|
89
|
+
const maxWidth = Math.min(cols, FRAME_MAX) - 2;
|
|
90
|
+
useInput((input, key) => {
|
|
91
|
+
if (key.upArrow || input === "k") {
|
|
92
|
+
setIndex((i) => (i - 1 + WORKFLOW_CARDS.length) % WORKFLOW_CARDS.length);
|
|
93
|
+
}
|
|
94
|
+
else if (key.downArrow || input === "j") {
|
|
95
|
+
setIndex((i) => (i + 1) % WORKFLOW_CARDS.length);
|
|
96
|
+
}
|
|
97
|
+
else if (key.return) {
|
|
98
|
+
onDone({ action: "select", card: WORKFLOW_CARDS[index] });
|
|
99
|
+
exit();
|
|
100
|
+
}
|
|
101
|
+
else if (input === "q" || key.escape) {
|
|
102
|
+
onDone({ action: "quit" });
|
|
103
|
+
exit();
|
|
104
|
+
}
|
|
105
|
+
else if (input === "?") {
|
|
106
|
+
setShowHelp((h) => !h);
|
|
107
|
+
}
|
|
108
|
+
else if (input === "/") {
|
|
109
|
+
setShowCommands((c) => !c);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Too-narrow recovery: a clean compact message that re-expands into the full
|
|
113
|
+
// chooser when the terminal is widened again. q / Esc still quits (useInput
|
|
114
|
+
// above runs on every path).
|
|
115
|
+
if (cols < NARROW_MIN) {
|
|
116
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, children: [_jsx(Text, { color: BLUE, bold: true, children: "COMPACTION" }), _jsx(Text, { color: DIM, children: "Terminal too narrow \u2014 widen the window to use the chooser," }), _jsx(Text, { color: DIM, children: "or run `compaction init --static`. (q / Esc to quit)" })] }));
|
|
117
|
+
}
|
|
118
|
+
const freeFlow = FREE_FLOW.map((f) => f.command.replace("compaction ", "")).join(" · ");
|
|
119
|
+
// Left-aligned, compact, no centering — the wordmark anchors the top-left.
|
|
120
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, children: [_jsx(Wordmark, { version: version, maxWidth: maxWidth }), _jsx(StatusRow, {}), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "white", children: VALUE_PROMISE }) }), _jsx(Box, { flexDirection: "column", children: WORKFLOW_CARDS.map((card, i) => i === index ? (_jsx(ActiveCard, { card: card, det: detection }, card.key)) : (_jsx(QuietRow, { card: card, det: detection }, card.key))) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: DIM, children: "Free local flow " }), _jsx(Text, { color: DIM, children: freeFlow })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: BLUE, bold: true, children: "Choose a workflow, press Enter" }), _jsx(Text, { color: DIM, children: " / commands ? help " }), _jsx(Text, { color: BLUE, children: "\u25CD local mode" })] }), _jsx(Text, { color: DIM, children: "↑/↓ · j/k move · Enter select · q / Esc quit" }), showCommands ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: DIM, children: "Free local flow, once you have a trace:" }), FREE_FLOW.map((f) => (_jsxs(Text, { children: [_jsx(Text, { color: BLUE, children: ` ${f.command}` }), _jsx(Text, { color: DIM, children: ` — ${f.desc}` })] }, f.command)))] })) : null, showHelp ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: DIM, children: `Enter prints the exact next command for ${WORKFLOW_CARDS[index].title} — copy & run` }), _jsx(Text, { color: DIM, children: "it. The CLI never runs it for you. Local-first: no network, no credentials, no upload." }), _jsx(Text, { color: DIM, children: "Nothing is called a saving until it is measured." })] })) : null] }));
|
|
121
|
+
}
|
|
122
|
+
const ALT_SCREEN_ENTER = "[?1049h[H[2J";
|
|
123
|
+
const ALT_SCREEN_LEAVE = "[?1049l";
|
|
124
|
+
/**
|
|
125
|
+
* Render the TUI and resolve with the user's choice. Resolves to `{action:"quit"}`
|
|
126
|
+
* if the user exits without selecting. The caller prints the chosen command to
|
|
127
|
+
* the scrollback after this resolves.
|
|
128
|
+
*
|
|
129
|
+
* Renders into the terminal's ALTERNATE SCREEN BUFFER (like vim/less/fzf): no
|
|
130
|
+
* scrollback, so a reflow can never push a stale frame up into history. Resize
|
|
131
|
+
* redraw is handled INSIDE the component (reactive terminal size → re-render),
|
|
132
|
+
* so Ink owns the clear/repaint and the screen stays visible across resizes. On
|
|
133
|
+
* exit the screen is restored and the selected next-command prints normally.
|
|
134
|
+
*/
|
|
135
|
+
export async function runInitTui(props) {
|
|
136
|
+
const out = process.stdout;
|
|
137
|
+
const useAlt = Boolean(out.isTTY);
|
|
138
|
+
if (useAlt)
|
|
139
|
+
out.write(ALT_SCREEN_ENTER);
|
|
140
|
+
let result = { action: "quit" };
|
|
141
|
+
try {
|
|
142
|
+
const app = render(_jsx(App, { version: props.version, detection: props.detection, onDone: (r) => {
|
|
143
|
+
result = r;
|
|
144
|
+
} }));
|
|
145
|
+
await app.waitUntilExit();
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
if (useAlt)
|
|
149
|
+
out.write(ALT_SCREEN_LEAVE);
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=InitTui.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared onboarding model — the single source of truth for both the static
|
|
3
|
+
* `compaction init` fallback screen and the interactive Ink TUI.
|
|
4
|
+
*
|
|
5
|
+
* This module holds DATA ONLY (no chalk, no React) so it is trivially testable
|
|
6
|
+
* and importable from both the plain renderer and the .tsx component. The two
|
|
7
|
+
* surfaces must always feature the same workflows, the same free follow-ups,
|
|
8
|
+
* and the same honest claim labels — keeping them here guarantees that.
|
|
9
|
+
*
|
|
10
|
+
* Free-CLI scope: onboarding features ONLY the free, local-first commands
|
|
11
|
+
* (capture / import / analyze / spend / summary / feedback). The optimization
|
|
12
|
+
* & verification engine commands (recommend / compact / inspect / approve /
|
|
13
|
+
* apply-context) are the opt-in Compaction API — NOT in the free CLI — and so
|
|
14
|
+
* are deliberately ABSENT from this model on every surface.
|
|
15
|
+
*/
|
|
16
|
+
export declare const BRAND_HEX = "#3231cd";
|
|
17
|
+
export declare const VALUE_PROMISE = "Find and reduce avoidable context spend in real agent workflows, locally.";
|
|
18
|
+
export declare const STATUS_PILLS: readonly ["local-first", "no telemetry", "no upload by default"];
|
|
19
|
+
export interface WorkflowCard {
|
|
20
|
+
key: string;
|
|
21
|
+
title: string;
|
|
22
|
+
/** The exact, copy-pasteable next command for this workflow. */
|
|
23
|
+
command: string;
|
|
24
|
+
/** One-line description of what running this workflow gets you. */
|
|
25
|
+
blurb: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const PRIMARY: WorkflowCard;
|
|
28
|
+
export declare const SECONDARY: WorkflowCard[];
|
|
29
|
+
/** All workflow cards, primary first — the order shown in the TUI chooser. */
|
|
30
|
+
export declare const WORKFLOW_CARDS: WorkflowCard[];
|
|
31
|
+
/** Lookup a card by key (used by `--path` focus and TUI selection). */
|
|
32
|
+
export declare function findWorkflow(key: string): WorkflowCard | undefined;
|
|
33
|
+
export declare const ALL_WORKFLOW_KEYS: string[];
|
|
34
|
+
export declare const FREE_FLOW: Array<{
|
|
35
|
+
command: string;
|
|
36
|
+
desc: string;
|
|
37
|
+
}>;
|
|
38
|
+
export declare const HONESTY_LINES: readonly ["Token counts are provider-reported when the run carries usage metadata,", "otherwise a local estimate (chars/4) — labeled as such. Cost is a", "price-table estimate; nothing is called a saving until it is measured.", "Optimization & verification are the opt-in Compaction API — not in the", "free CLI and not a hosted service yet. The free CLI measures and reports."];
|
|
39
|
+
export declare const FOOTER_LINES: readonly ["Local-first: no network, no credentials, no repo access for any of the above.", "A provider credential is only ever used if YOU explicitly run", "`compaction capture provider-usage` or enter operator-run records."];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared onboarding model — the single source of truth for both the static
|
|
3
|
+
* `compaction init` fallback screen and the interactive Ink TUI.
|
|
4
|
+
*
|
|
5
|
+
* This module holds DATA ONLY (no chalk, no React) so it is trivially testable
|
|
6
|
+
* and importable from both the plain renderer and the .tsx component. The two
|
|
7
|
+
* surfaces must always feature the same workflows, the same free follow-ups,
|
|
8
|
+
* and the same honest claim labels — keeping them here guarantees that.
|
|
9
|
+
*
|
|
10
|
+
* Free-CLI scope: onboarding features ONLY the free, local-first commands
|
|
11
|
+
* (capture / import / analyze / spend / summary / feedback). The optimization
|
|
12
|
+
* & verification engine commands (recommend / compact / inspect / approve /
|
|
13
|
+
* apply-context) are the opt-in Compaction API — NOT in the free CLI — and so
|
|
14
|
+
* are deliberately ABSENT from this model on every surface.
|
|
15
|
+
*/
|
|
16
|
+
// Brand accent. Used by both surfaces; chalk/ink down-sample or no-op it on
|
|
17
|
+
// terminals without truecolor, so the text is never lost.
|
|
18
|
+
export const BRAND_HEX = "#3231cd";
|
|
19
|
+
export const VALUE_PROMISE = "Find and reduce avoidable context spend in real agent workflows, locally.";
|
|
20
|
+
// The three short status pills shown under the wordmark on both surfaces.
|
|
21
|
+
export const STATUS_PILLS = ["local-first", "no telemetry", "no upload by default"];
|
|
22
|
+
// Real-workflow paths only. compaction operates on the user's OWN agent
|
|
23
|
+
// workflow; there is no demo / synthetic first-value path here.
|
|
24
|
+
export const PRIMARY = {
|
|
25
|
+
key: "claude-code",
|
|
26
|
+
title: "Claude Code",
|
|
27
|
+
command: "compaction capture claude-code --discover",
|
|
28
|
+
blurb: "Capture a local Claude Code session (recommended first run)."
|
|
29
|
+
};
|
|
30
|
+
export const SECONDARY = [
|
|
31
|
+
{
|
|
32
|
+
key: "openai-agents",
|
|
33
|
+
title: "OpenAI Agents SDK",
|
|
34
|
+
command: "compaction capture openai-agents --out ./my-trace -- <your-command>",
|
|
35
|
+
blurb: "Capture an OpenAI Agents SDK run."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "codex",
|
|
39
|
+
title: "Codex / local import",
|
|
40
|
+
command: "codex exec --json '<task>' > run.jsonl && compaction import run.jsonl --source codex-exec-jsonl --out ./my-trace",
|
|
41
|
+
blurb: "Import a Codex exec JSONL export."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: "import",
|
|
45
|
+
title: "Local trace import",
|
|
46
|
+
command: "compaction import --list-sources # then: import <file> --source <source> --out ./my-trace",
|
|
47
|
+
blurb: "Import any local trace / JSONL you already have."
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
/** All workflow cards, primary first — the order shown in the TUI chooser. */
|
|
51
|
+
export const WORKFLOW_CARDS = [PRIMARY, ...SECONDARY];
|
|
52
|
+
/** Lookup a card by key (used by `--path` focus and TUI selection). */
|
|
53
|
+
export function findWorkflow(key) {
|
|
54
|
+
return WORKFLOW_CARDS.find((c) => c.key === key);
|
|
55
|
+
}
|
|
56
|
+
export const ALL_WORKFLOW_KEYS = WORKFLOW_CARDS.map((c) => c.key);
|
|
57
|
+
// The follow-up sequence once a trace exists — FREE commands only.
|
|
58
|
+
export const FREE_FLOW = [
|
|
59
|
+
{ command: "compaction analyze", desc: "input/output tokens + estimated spend, with honest labels" },
|
|
60
|
+
{ command: "compaction spend", desc: "where the context spend came from (attribution)" },
|
|
61
|
+
{ command: "compaction summary", desc: "roll up your local runs" },
|
|
62
|
+
{ command: "compaction feedback --redact", desc: "build a redacted, local-only share bundle (never uploaded)" }
|
|
63
|
+
];
|
|
64
|
+
// Honest claim labels, shown verbatim on both surfaces.
|
|
65
|
+
export const HONESTY_LINES = [
|
|
66
|
+
"Token counts are provider-reported when the run carries usage metadata,",
|
|
67
|
+
"otherwise a local estimate (chars/4) — labeled as such. Cost is a",
|
|
68
|
+
"price-table estimate; nothing is called a saving until it is measured.",
|
|
69
|
+
"Optimization & verification are the opt-in Compaction API — not in the",
|
|
70
|
+
"free CLI and not a hosted service yet. The free CLI measures and reports."
|
|
71
|
+
];
|
|
72
|
+
export const FOOTER_LINES = [
|
|
73
|
+
"Local-first: no network, no credentials, no repo access for any of the above.",
|
|
74
|
+
"A provider credential is only ever used if YOU explicitly run",
|
|
75
|
+
"`compaction capture provider-usage` or enter operator-run records."
|
|
76
|
+
];
|
|
77
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether `compaction init` should launch the interactive Ink TUI or
|
|
3
|
+
* fall back to the plain static screen.
|
|
4
|
+
*
|
|
5
|
+
* The TUI is a progressive enhancement: it renders only in a real interactive
|
|
6
|
+
* terminal. Everywhere else — pipes, CI, NO_COLOR, dumb terminals, the test
|
|
7
|
+
* runner, or when the operator opts out — we serve the byte-stable static
|
|
8
|
+
* screen so scripts, snapshots, and smoke tests never see a control sequence.
|
|
9
|
+
*
|
|
10
|
+
* Pure function over an explicit environment snapshot so it is trivially unit
|
|
11
|
+
* testable without touching the real process.
|
|
12
|
+
*/
|
|
13
|
+
export interface TuiEnv {
|
|
14
|
+
stdoutIsTTY: boolean;
|
|
15
|
+
stdinIsTTY: boolean;
|
|
16
|
+
env: NodeJS.ProcessEnv;
|
|
17
|
+
}
|
|
18
|
+
export interface TuiDecision {
|
|
19
|
+
useTui: boolean;
|
|
20
|
+
/** Machine-readable reason the TUI was declined (for tests / --help intuition). */
|
|
21
|
+
reason: "ok" | "not-a-tty" | "no-color" | "ci" | "dumb-term" | "opted-out" | "test-env";
|
|
22
|
+
}
|
|
23
|
+
export declare function decideInteractiveTui(e: TuiEnv): TuiDecision;
|
|
24
|
+
/** Convenience wrapper over the live process. */
|
|
25
|
+
export declare function decideInteractiveTuiFromProcess(): TuiDecision;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function decideInteractiveTui(e) {
|
|
2
|
+
// Raw-mode keyboard navigation requires BOTH a TTY stdin and stdout.
|
|
3
|
+
if (!e.stdoutIsTTY || !e.stdinIsTTY)
|
|
4
|
+
return { useTui: false, reason: "not-a-tty" };
|
|
5
|
+
// Honor NO_COLOR (no-color.org): present, regardless of value → plain output.
|
|
6
|
+
if (e.env.NO_COLOR != null)
|
|
7
|
+
return { useTui: false, reason: "no-color" };
|
|
8
|
+
// CI systems are non-interactive even when a pseudo-TTY is attached.
|
|
9
|
+
if (e.env.CI != null && e.env.CI !== "" && e.env.CI !== "false" && e.env.CI !== "0") {
|
|
10
|
+
return { useTui: false, reason: "ci" };
|
|
11
|
+
}
|
|
12
|
+
// Terminals that cannot render cursor movement / styling.
|
|
13
|
+
if (e.env.TERM === "dumb")
|
|
14
|
+
return { useTui: false, reason: "dumb-term" };
|
|
15
|
+
// Test runner — keep first-value output deterministic for snapshots.
|
|
16
|
+
if (e.env.VITEST != null || e.env.NODE_ENV === "test") {
|
|
17
|
+
return { useTui: false, reason: "test-env" };
|
|
18
|
+
}
|
|
19
|
+
// Explicit operator escape hatch. Accept any truthy value (1/true/yes/…),
|
|
20
|
+
// matching the lenient CI convention; "0"/"false"/"" do not opt out.
|
|
21
|
+
const optOut = e.env.COMPACTION_NO_TUI;
|
|
22
|
+
if (optOut != null && optOut !== "" && optOut !== "0" && optOut !== "false") {
|
|
23
|
+
return { useTui: false, reason: "opted-out" };
|
|
24
|
+
}
|
|
25
|
+
return { useTui: true, reason: "ok" };
|
|
26
|
+
}
|
|
27
|
+
/** Convenience wrapper over the live process. */
|
|
28
|
+
export function decideInteractiveTuiFromProcess() {
|
|
29
|
+
return decideInteractiveTui({
|
|
30
|
+
stdoutIsTTY: Boolean(process.stdout.isTTY),
|
|
31
|
+
stdinIsTTY: Boolean(process.stdin.isTTY),
|
|
32
|
+
env: process.env
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=should-use-tui.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic, dependency-free framed-outline block wordmark for the
|
|
3
|
+
* COMPACTION TUI hero.
|
|
4
|
+
*
|
|
5
|
+
* This restores the earlier "framed-outline / inline-outline" 3D treatment: the
|
|
6
|
+
* exact glyph art of figlet's "ANSI Shadow" font for the fixed word COMPACTION,
|
|
7
|
+
* BAKED IN as a constant (no figlet dependency — lean, deterministic, testable).
|
|
8
|
+
* Each glyph is a solid '█' fill framed by box-drawing outline strokes
|
|
9
|
+
* (╗ ╔ ═ ╝ ║ ╚) that give the inline 3D depth — NOT a separate offset shadow,
|
|
10
|
+
* NOT a dotted/ghost backdrop.
|
|
11
|
+
*
|
|
12
|
+
* The component paints the FILL with a restrained 2–3 shade Compaction-blue
|
|
13
|
+
* vertical gradient and the OUTLINE strokes with one darker brand-blue tone.
|
|
14
|
+
*/
|
|
15
|
+
export type Layer = "fill" | "outline" | "empty";
|
|
16
|
+
export interface Segment {
|
|
17
|
+
text: string;
|
|
18
|
+
layer: Layer;
|
|
19
|
+
}
|
|
20
|
+
export interface Wordmark {
|
|
21
|
+
rows: Segment[][];
|
|
22
|
+
/** "block" (framed-outline glyphs) or "text" (spaced fallback for narrow terminals). */
|
|
23
|
+
kind: "block" | "text";
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function renderWordmark(maxWidth: number): Wordmark;
|
|
28
|
+
export declare function fillGradient(rowCount: number): string[];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic, dependency-free framed-outline block wordmark for the
|
|
3
|
+
* COMPACTION TUI hero.
|
|
4
|
+
*
|
|
5
|
+
* This restores the earlier "framed-outline / inline-outline" 3D treatment: the
|
|
6
|
+
* exact glyph art of figlet's "ANSI Shadow" font for the fixed word COMPACTION,
|
|
7
|
+
* BAKED IN as a constant (no figlet dependency — lean, deterministic, testable).
|
|
8
|
+
* Each glyph is a solid '█' fill framed by box-drawing outline strokes
|
|
9
|
+
* (╗ ╔ ═ ╝ ║ ╚) that give the inline 3D depth — NOT a separate offset shadow,
|
|
10
|
+
* NOT a dotted/ghost backdrop.
|
|
11
|
+
*
|
|
12
|
+
* The component paints the FILL with a restrained 2–3 shade Compaction-blue
|
|
13
|
+
* vertical gradient and the OUTLINE strokes with one darker brand-blue tone.
|
|
14
|
+
*/
|
|
15
|
+
// Baked "ANSI Shadow" rendering of COMPACTION (figlet). Do not hand-edit the
|
|
16
|
+
// box-drawing characters; regenerate from figlet if the word ever changes.
|
|
17
|
+
const LINES = [
|
|
18
|
+
" ██████╗ ██████╗ ███╗ ███╗██████╗ █████╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗",
|
|
19
|
+
"██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║",
|
|
20
|
+
"██║ ██║ ██║██╔████╔██║██████╔╝███████║██║ ██║ ██║██║ ██║██╔██╗ ██║",
|
|
21
|
+
"██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██╔══██║██║ ██║ ██║██║ ██║██║╚██╗██║",
|
|
22
|
+
"╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ██║ ██║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║",
|
|
23
|
+
" ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝"
|
|
24
|
+
];
|
|
25
|
+
const FILL = "█";
|
|
26
|
+
const BLOCK_W = 83;
|
|
27
|
+
const SPACED = "C O M P A C T I O N";
|
|
28
|
+
// Classify each cell: solid fill, outline frame stroke, or empty.
|
|
29
|
+
function buildGrid() {
|
|
30
|
+
return LINES.map((line) => {
|
|
31
|
+
const padded = line.padEnd(BLOCK_W, " ");
|
|
32
|
+
const segs = [];
|
|
33
|
+
let cur = null;
|
|
34
|
+
for (const ch of padded) {
|
|
35
|
+
const layer = ch === " " ? "empty" : ch === FILL ? "fill" : "outline";
|
|
36
|
+
if (cur && cur.layer === layer)
|
|
37
|
+
cur.text += ch;
|
|
38
|
+
else {
|
|
39
|
+
cur = { layer, text: ch };
|
|
40
|
+
segs.push(cur);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return segs;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const BLOCK = buildGrid();
|
|
47
|
+
export function renderWordmark(maxWidth) {
|
|
48
|
+
if (maxWidth >= BLOCK_W) {
|
|
49
|
+
return { rows: BLOCK, kind: "block", width: BLOCK_W, height: BLOCK.length };
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
rows: [[{ text: SPACED, layer: "fill" }]],
|
|
53
|
+
kind: "text",
|
|
54
|
+
width: SPACED.length,
|
|
55
|
+
height: 1
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Restrained blue-only vertical gradient for the FILL — at most three
|
|
59
|
+
// blue/indigo shades, no teal/cyan. One hex per row, sampled to the row count.
|
|
60
|
+
const BLUE_RAMP = ["#6d6bff", "#4f46e5", "#3231cd"];
|
|
61
|
+
export function fillGradient(rowCount) {
|
|
62
|
+
if (rowCount <= 1)
|
|
63
|
+
return [BLUE_RAMP[0]];
|
|
64
|
+
const out = [];
|
|
65
|
+
for (let i = 0; i < rowCount; i++) {
|
|
66
|
+
const t = i / (rowCount - 1);
|
|
67
|
+
const idx = Math.min(BLUE_RAMP.length - 1, Math.round(t * (BLUE_RAMP.length - 1)));
|
|
68
|
+
out.push(BLUE_RAMP[idx]);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=wordmark.js.map
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* copies raw trace text, prompts, completions, tool output, file paths, run ids, or report
|
|
9
9
|
* paths into it. Like the feedback bundle, it is a LOCAL FILE ONLY: no network, no upload.
|
|
10
10
|
*/
|
|
11
|
-
import type { AggregateReport, AggregateTotals, VerificationCounts } from "./session-aggregate.js";
|
|
11
|
+
import type { AggregateReport, AggregateTotals, DistinctnessSummary, VerificationCounts } from "./session-aggregate.js";
|
|
12
12
|
import { AGGREGATE_EVIDENCE_LABELS } from "./session-aggregate.js";
|
|
13
13
|
import type { RunLabels } from "./run-labels.js";
|
|
14
14
|
export declare const SHARE_BUNDLE_VERSION = "1.0.0";
|
|
@@ -41,6 +41,8 @@ export interface AggregateShareBundle {
|
|
|
41
41
|
session_count: number;
|
|
42
42
|
totals: AggregateTotals;
|
|
43
43
|
verification: VerificationCounts;
|
|
44
|
+
/** Duplicate-safety summary (distinct N of M by content fingerprint) — counts only, no run ids. */
|
|
45
|
+
distinctness: DistinctnessSummary;
|
|
44
46
|
/** Provider/runtime breakdown — short label keys + numbers only (no run ids). */
|
|
45
47
|
by_provider: ShareBundleBreakdownEntry[];
|
|
46
48
|
by_project: ShareBundleBreakdownEntry[];
|
|
@@ -32,6 +32,18 @@ function totalsLines(totals) {
|
|
|
32
32
|
`estimated savings: ${usd(totals.total_estimated_savings_usd)} (estimated, summed over ${totals.compactions} recorded run(s); NOT billing-confirmed, NOT realized, NOT extrapolated)`
|
|
33
33
|
];
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* One honest distinctness line: "distinct runs N of M (K duplicates detected)" plus the
|
|
37
|
+
* fingerprint coverage. This is a count of distinct captured runs by content fingerprint — NOT a
|
|
38
|
+
* billing/provider/semantic claim. The headline savings are computed over the distinct set.
|
|
39
|
+
*/
|
|
40
|
+
function distinctnessLine(d) {
|
|
41
|
+
const noFp = d.runs_without_fingerprint > 0
|
|
42
|
+
? `; ${d.runs_without_fingerprint} run(s) without a fingerprint counted as distinct (cannot de-duplicate)`
|
|
43
|
+
: "";
|
|
44
|
+
return (`distinct runs ${d.distinct_runs} of ${d.total_runs} (${d.duplicate_runs} duplicate(s) detected by content fingerprint, ` +
|
|
45
|
+
`excluded from the headline so savings are not double-counted)${noFp}`);
|
|
46
|
+
}
|
|
35
47
|
function verificationLines(v) {
|
|
36
48
|
return [
|
|
37
49
|
`readiness: ready ${v.ready}, conditional ${v.conditional}, not_ready ${v.not_ready}, not_evaluated ${v.not_evaluated}`,
|
|
@@ -54,6 +66,7 @@ export function formatAggregateReport(report) {
|
|
|
54
66
|
lines.push(`Sessions: ${report.session_count}`);
|
|
55
67
|
lines.push("");
|
|
56
68
|
lines.push("Cross-session totals");
|
|
69
|
+
lines.push(`- ${distinctnessLine(report.distinctness)}`);
|
|
57
70
|
for (const line of totalsLines(report.totals))
|
|
58
71
|
lines.push(`- ${line}`);
|
|
59
72
|
lines.push("");
|
|
@@ -79,6 +92,7 @@ export function formatAggregateReport(report) {
|
|
|
79
92
|
labelBits.push(`labels: ${session.labels.user_labels.join("/")}`);
|
|
80
93
|
if (labelBits.length)
|
|
81
94
|
lines.push(` - ${labelBits.join("; ")}`);
|
|
95
|
+
lines.push(` - ${distinctnessLine(session.distinctness)}`);
|
|
82
96
|
for (const line of totalsLines(session.totals))
|
|
83
97
|
lines.push(` - ${line}`);
|
|
84
98
|
for (const line of verificationLines(session.verification))
|
|
@@ -141,6 +155,7 @@ export function formatAggregateMarkdown(report) {
|
|
|
141
155
|
lines.push("");
|
|
142
156
|
lines.push("## Cross-session totals");
|
|
143
157
|
lines.push("");
|
|
158
|
+
lines.push(`- Distinct runs: ${distinctnessLine(report.distinctness)}`);
|
|
144
159
|
lines.push(`- Runs (compactions): ${t.compactions}`);
|
|
145
160
|
lines.push(`- Token-evidence split: provider-reported ${t.runs_provider_reported}, local-estimate ${t.runs_local_estimate}, unknown ${t.runs_unknown_token_evidence}`);
|
|
146
161
|
lines.push(`- Total input tokens before: ${t.total_input_tokens_before}`);
|
|
@@ -219,6 +234,7 @@ export function formatAggregateMarkdown(report) {
|
|
|
219
234
|
}
|
|
220
235
|
const SHARE_INCLUDES = [
|
|
221
236
|
"aggregate token totals (input before/after/saved; output where recorded)",
|
|
237
|
+
"distinct-run count (distinct N of M by content fingerprint; duplicate-run count) — counts only",
|
|
222
238
|
"token-evidence split (provider-reported vs local-estimate run counts)",
|
|
223
239
|
"estimated cost + estimated savings totals (price-table estimate; NOT billing-confirmed)",
|
|
224
240
|
"compaction count + session count",
|
|
@@ -271,6 +287,7 @@ export function buildAggregateShareBundle(report, generatedAt = new Date().toISO
|
|
|
271
287
|
session_count: report.session_count,
|
|
272
288
|
totals: report.totals,
|
|
273
289
|
verification: report.verification,
|
|
290
|
+
distinctness: report.distinctness,
|
|
274
291
|
by_provider: breakdownEntries(report.by_provider),
|
|
275
292
|
by_project: breakdownEntries(report.by_project),
|
|
276
293
|
by_workflow: breakdownEntries(report.by_workflow),
|
|
@@ -299,6 +316,7 @@ export function formatShareBundleMarkdown(bundle) {
|
|
|
299
316
|
lines.push("");
|
|
300
317
|
lines.push("## Aggregate value (estimated, summed over recorded runs)");
|
|
301
318
|
lines.push("");
|
|
319
|
+
lines.push(`- Distinct runs: ${distinctnessLine(bundle.distinctness)}`);
|
|
302
320
|
lines.push(`- Compactions: ${t.compactions}`);
|
|
303
321
|
lines.push(`- Token-evidence split: provider-reported ${t.runs_provider_reported}, local-estimate ${t.runs_local_estimate}, unknown ${t.runs_unknown_token_evidence}`);
|
|
304
322
|
lines.push(`- Input tokens saved: ${t.total_input_tokens_saved}`);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ContextStoreEvalFixture } from "./context-store-eval.js";
|
|
2
|
+
export declare const CONTEXT_STORE_EVAL_FIXTURES: ContextStoreEvalFixture[];
|
|
3
|
+
export declare const CONTEXT_STORE_EVAL_RICHER_FIXTURES: ContextStoreEvalFixture[];
|
|
4
|
+
export declare const CONTEXT_STORE_EVAL_EDGE_FIXTURES: ContextStoreEvalFixture[];
|
|
5
|
+
/** Every fixture (healthy + richer + edge), for the internal sufficiency report. */
|
|
6
|
+
export declare const CONTEXT_STORE_ALL_EVAL_FIXTURES: ContextStoreEvalFixture[];
|