@phamvuhoang/otto-core 0.22.0 → 0.24.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/dist/context-compressor.d.ts +147 -0
- package/dist/context-compressor.d.ts.map +1 -0
- package/dist/context-compressor.js +199 -0
- package/dist/context-compressor.js.map +1 -0
- package/dist/context-report-cli.d.ts.map +1 -1
- package/dist/context-report-cli.js +9 -1
- package/dist/context-report-cli.js.map +1 -1
- package/dist/external-skills.d.ts +170 -0
- package/dist/external-skills.d.ts.map +1 -0
- package/dist/external-skills.js +417 -0
- package/dist/external-skills.js.map +1 -0
- package/dist/headroom-adapter.d.ts +58 -0
- package/dist/headroom-adapter.d.ts.map +1 -0
- package/dist/headroom-adapter.js +106 -0
- package/dist/headroom-adapter.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/run-report.d.ts +29 -0
- package/dist/run-report.d.ts.map +1 -1
- package/dist/run-report.js.map +1 -1
- package/dist/skills-cli.d.ts +16 -5
- package/dist/skills-cli.d.ts.map +1 -1
- package/dist/skills-cli.js +134 -7
- package/dist/skills-cli.js.map +1 -1
- package/dist/skills.d.ts +21 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +26 -1
- package/dist/skills.js.map +1 -1
- package/dist/tools-cli.d.ts +55 -0
- package/dist/tools-cli.d.ts.map +1 -0
- package/dist/tools-cli.js +196 -0
- package/dist/tools-cli.js.map +1 -0
- package/dist/tools.d.ts +164 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +228 -0
- package/dist/tools.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { ToolUsage } from "./run-report.js";
|
|
2
|
+
/**
|
|
3
|
+
* Context compression at a governed harness boundary (issue #112 P20). Long
|
|
4
|
+
* unattended runs are constrained by token spend (P7); a compressor such as
|
|
5
|
+
* [Headroom](https://github.com/headroomlabs-ai/headroom) can shrink token-heavy
|
|
6
|
+
* content before it reaches the model. P20 routes selected content through a
|
|
7
|
+
* compressor and **measures** the result, with two hard rules from the roadmap:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Off by default, degrades cleanly.** No compressor unless explicitly
|
|
10
|
+
* enabled (`--context-compressor headroom` / `OTTO_CONTEXT_COMPRESSOR` /
|
|
11
|
+
* `.otto/config.json` `contextCompressor`). A missing/failed compressor falls
|
|
12
|
+
* back to the original content with a warning — never a broken run.
|
|
13
|
+
* 2. **Reversible, never hides evidence.** Every compression stores the original
|
|
14
|
+
* under a durable retrieval handle and records tokens before/after, latency,
|
|
15
|
+
* and the compressor version, so reports/evals/reviewers can still reach the
|
|
16
|
+
* original.
|
|
17
|
+
*
|
|
18
|
+
* The compressor is invoked behind P19's external-tool contract (see
|
|
19
|
+
* `headroom-adapter.ts`); this module is the transport-agnostic core: setting
|
|
20
|
+
* resolution, the `ContextCompressor` interface, the reversible-compress
|
|
21
|
+
* orchestrator, and the savings summary. Pure except for the fs retrieval store,
|
|
22
|
+
* which is injected.
|
|
23
|
+
*
|
|
24
|
+
* **Scope of this slice (P20a spike):** the setting, contract, adapter,
|
|
25
|
+
* reversible mechanism, and inspectability are wired and tested. Applying
|
|
26
|
+
* compression at every live spill/log/memory call site is the P20 production
|
|
27
|
+
* follow-up — until then a run with the compressor off behaves exactly as today.
|
|
28
|
+
*/
|
|
29
|
+
/** Off, or route through the Headroom adapter. */
|
|
30
|
+
export type CompressorMode = "off" | "headroom";
|
|
31
|
+
/**
|
|
32
|
+
* The token-heavy content categories P7 already flags — the targets the roadmap
|
|
33
|
+
* names. Carried on each compression so a report can attribute savings by source.
|
|
34
|
+
*/
|
|
35
|
+
export type CompressionCategory = "issue-body" | "command-log" | "prior-iteration" | "read-artifact" | "memory-projection";
|
|
36
|
+
/** Content handed to the compressor, tagged by source category + a stable key. */
|
|
37
|
+
export type CompressInput = {
|
|
38
|
+
/** A run-unique key (used to name the retrieval artifact). */
|
|
39
|
+
key: string;
|
|
40
|
+
category: CompressionCategory;
|
|
41
|
+
text: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A compressor adapter (Headroom, or a test double). `compress` is async because
|
|
45
|
+
* a real adapter shells out / calls a local MCP server. `ok: false` signals a
|
|
46
|
+
* recoverable failure (the orchestrator then degrades to the original).
|
|
47
|
+
*/
|
|
48
|
+
export type ContextCompressor = {
|
|
49
|
+
name: string;
|
|
50
|
+
version: string;
|
|
51
|
+
/** Whether the underlying tool is installed/reachable right now. */
|
|
52
|
+
isAvailable: () => boolean | Promise<boolean>;
|
|
53
|
+
compress: (input: CompressInput) => Promise<{
|
|
54
|
+
text: string;
|
|
55
|
+
ok: boolean;
|
|
56
|
+
note?: string;
|
|
57
|
+
}>;
|
|
58
|
+
};
|
|
59
|
+
/** The measured outcome of one compression (or a degraded passthrough). */
|
|
60
|
+
export type CompressOutput = {
|
|
61
|
+
/** The text to actually use downstream (compressed, or original if degraded). */
|
|
62
|
+
text: string;
|
|
63
|
+
tokensBefore: number;
|
|
64
|
+
tokensAfter: number;
|
|
65
|
+
tokensSaved: number;
|
|
66
|
+
/** Durable handle to the original; absent when nothing was stored (passthrough). */
|
|
67
|
+
retrievalHandle?: string;
|
|
68
|
+
latencyMs: number;
|
|
69
|
+
compressorVersion: string;
|
|
70
|
+
/** True when the compressor was unavailable/failed and the original was kept. */
|
|
71
|
+
degraded: boolean;
|
|
72
|
+
note?: string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Persist the original content and return a durable retrieval handle. Injected so
|
|
76
|
+
* the orchestrator stays testable; {@link runRetrievalStore} is the fs default.
|
|
77
|
+
*/
|
|
78
|
+
export type RetrievalStore = (key: string, original: string) => string;
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the compressor mode from the flag/env/config precedence chain (flag >
|
|
81
|
+
* env > config > off). An unrecognized value resolves to `off`, so a typo never
|
|
82
|
+
* silently enables compression. Pure.
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolveCompressorMode(opts: {
|
|
85
|
+
flag?: string;
|
|
86
|
+
env?: string;
|
|
87
|
+
config?: string;
|
|
88
|
+
}): CompressorMode;
|
|
89
|
+
/**
|
|
90
|
+
* Read the effective compressor mode for a workspace: `--context-compressor`
|
|
91
|
+
* flag, else `OTTO_CONTEXT_COMPRESSOR`, else `.otto/config.json`
|
|
92
|
+
* `contextCompressor`, else `off`. Missing/malformed config → off (never throws).
|
|
93
|
+
*/
|
|
94
|
+
export declare function readCompressorMode(workspaceDir: string, env?: NodeJS.ProcessEnv, flag?: string): CompressorMode;
|
|
95
|
+
/**
|
|
96
|
+
* Default fs retrieval store: write each original under
|
|
97
|
+
* `.otto/runs/<run-id>/compressed/<key>.orig` and return that **workspace-relative**
|
|
98
|
+
* path as the durable handle, so the run bundle stays portable.
|
|
99
|
+
*/
|
|
100
|
+
export declare function runRetrievalStore(workspaceDir: string, runId: string): RetrievalStore;
|
|
101
|
+
/**
|
|
102
|
+
* Compress one piece of content reversibly and measure it. The single entry
|
|
103
|
+
* point both the live loop and the benchmark use:
|
|
104
|
+
*
|
|
105
|
+
* - off (`compressor` null) → returns the original verbatim, zero savings, no
|
|
106
|
+
* handle, not degraded;
|
|
107
|
+
* - compressor unavailable or `ok: false` → degraded passthrough (original kept,
|
|
108
|
+
* `degraded: true`, a `note`), never throws;
|
|
109
|
+
* - success → stores the original via `store`, returns the compressed text plus
|
|
110
|
+
* the measured tokens-before/after, savings, retrieval handle, and latency.
|
|
111
|
+
*
|
|
112
|
+
* A "compression" that did not actually shrink the token estimate is discarded
|
|
113
|
+
* in favor of the original (no point paying a retrieval indirection for nothing).
|
|
114
|
+
* `now` is injected for deterministic latency in tests.
|
|
115
|
+
*/
|
|
116
|
+
export declare function compressContent(compressor: ContextCompressor | null, input: CompressInput, store: RetrievalStore | null, deps?: {
|
|
117
|
+
now?: () => number;
|
|
118
|
+
}): Promise<CompressOutput>;
|
|
119
|
+
/**
|
|
120
|
+
* Build the {@link ToolUsage} evidence record for a compression, so the loop can
|
|
121
|
+
* attach it to a stage's `toolsUsed[]` (P19). Pure.
|
|
122
|
+
*/
|
|
123
|
+
export declare function compressionToolUsage(output: CompressOutput, category: CompressionCategory, stage?: string): ToolUsage;
|
|
124
|
+
/** Aggregate savings across a run's compressions (for the context report). */
|
|
125
|
+
export type CompressionSummary = {
|
|
126
|
+
invocations: number;
|
|
127
|
+
tokensBefore: number;
|
|
128
|
+
tokensAfter: number;
|
|
129
|
+
tokensSaved: number;
|
|
130
|
+
retrievals: number;
|
|
131
|
+
degraded: number;
|
|
132
|
+
};
|
|
133
|
+
/** Sum a set of compression outcomes into a {@link CompressionSummary}. Pure. */
|
|
134
|
+
export declare function summarizeCompression(outputs: CompressOutput[]): CompressionSummary;
|
|
135
|
+
/**
|
|
136
|
+
* Summarize the compression {@link ToolUsage} records on a run's stage records
|
|
137
|
+
* (name `headroom`) — what `--context-report` reads, since the loop attaches
|
|
138
|
+
* compression evidence as `toolsUsed[]`. Pure.
|
|
139
|
+
*/
|
|
140
|
+
export declare function summarizeToolCompression(usages: ToolUsage[]): {
|
|
141
|
+
invocations: number;
|
|
142
|
+
tokensSaved: number;
|
|
143
|
+
retrievals: number;
|
|
144
|
+
};
|
|
145
|
+
/** One-line human summary of compression savings for the context report. Pure. */
|
|
146
|
+
export declare function formatCompressionSummary(s: CompressionSummary): string;
|
|
147
|
+
//# sourceMappingURL=context-compressor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-compressor.d.ts","sourceRoot":"","sources":["../src/context-compressor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { estimateTokens } from "./context-report.js";
|
|
4
|
+
import { runReportDir } from "./run-report.js";
|
|
5
|
+
const MODES = new Set(["off", "headroom"]);
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the compressor mode from the flag/env/config precedence chain (flag >
|
|
8
|
+
* env > config > off). An unrecognized value resolves to `off`, so a typo never
|
|
9
|
+
* silently enables compression. Pure.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveCompressorMode(opts) {
|
|
12
|
+
for (const raw of [opts.flag, opts.env, opts.config]) {
|
|
13
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
14
|
+
const v = raw.trim().toLowerCase();
|
|
15
|
+
return MODES.has(v) ? v : "off";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return "off";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read the effective compressor mode for a workspace: `--context-compressor`
|
|
22
|
+
* flag, else `OTTO_CONTEXT_COMPRESSOR`, else `.otto/config.json`
|
|
23
|
+
* `contextCompressor`, else `off`. Missing/malformed config → off (never throws).
|
|
24
|
+
*/
|
|
25
|
+
export function readCompressorMode(workspaceDir, env = process.env, flag) {
|
|
26
|
+
let config;
|
|
27
|
+
try {
|
|
28
|
+
const raw = JSON.parse(readFileSync(join(workspaceDir, ".otto", "config.json"), "utf8"));
|
|
29
|
+
if (typeof raw.contextCompressor === "string")
|
|
30
|
+
config = raw.contextCompressor;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// no/invalid config → leave undefined (resolves to off)
|
|
34
|
+
}
|
|
35
|
+
return resolveCompressorMode({
|
|
36
|
+
flag,
|
|
37
|
+
env: env.OTTO_CONTEXT_COMPRESSOR,
|
|
38
|
+
config,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Default fs retrieval store: write each original under
|
|
43
|
+
* `.otto/runs/<run-id>/compressed/<key>.orig` and return that **workspace-relative**
|
|
44
|
+
* path as the durable handle, so the run bundle stays portable.
|
|
45
|
+
*/
|
|
46
|
+
export function runRetrievalStore(workspaceDir, runId) {
|
|
47
|
+
const absDir = join(runReportDir(workspaceDir, runId), "compressed");
|
|
48
|
+
return (key, original) => {
|
|
49
|
+
mkdirSync(absDir, { recursive: true });
|
|
50
|
+
const safe = key.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 80);
|
|
51
|
+
writeFileSync(join(absDir, `${safe}.orig`), original);
|
|
52
|
+
return join(".otto", "runs", runId, "compressed", `${safe}.orig`);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Compress one piece of content reversibly and measure it. The single entry
|
|
57
|
+
* point both the live loop and the benchmark use:
|
|
58
|
+
*
|
|
59
|
+
* - off (`compressor` null) → returns the original verbatim, zero savings, no
|
|
60
|
+
* handle, not degraded;
|
|
61
|
+
* - compressor unavailable or `ok: false` → degraded passthrough (original kept,
|
|
62
|
+
* `degraded: true`, a `note`), never throws;
|
|
63
|
+
* - success → stores the original via `store`, returns the compressed text plus
|
|
64
|
+
* the measured tokens-before/after, savings, retrieval handle, and latency.
|
|
65
|
+
*
|
|
66
|
+
* A "compression" that did not actually shrink the token estimate is discarded
|
|
67
|
+
* in favor of the original (no point paying a retrieval indirection for nothing).
|
|
68
|
+
* `now` is injected for deterministic latency in tests.
|
|
69
|
+
*/
|
|
70
|
+
export async function compressContent(compressor, input, store, deps = {}) {
|
|
71
|
+
const now = deps.now ?? Date.now;
|
|
72
|
+
const tokensBefore = estimateTokens(input.text.length);
|
|
73
|
+
const passthrough = (degraded, note) => ({
|
|
74
|
+
text: input.text,
|
|
75
|
+
tokensBefore,
|
|
76
|
+
tokensAfter: tokensBefore,
|
|
77
|
+
tokensSaved: 0,
|
|
78
|
+
latencyMs: 0,
|
|
79
|
+
compressorVersion: compressor?.version ?? "off",
|
|
80
|
+
degraded,
|
|
81
|
+
...(note ? { note } : {}),
|
|
82
|
+
});
|
|
83
|
+
if (!compressor)
|
|
84
|
+
return passthrough(false);
|
|
85
|
+
const start = now();
|
|
86
|
+
let available;
|
|
87
|
+
try {
|
|
88
|
+
available = await compressor.isAvailable();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
available = false;
|
|
92
|
+
}
|
|
93
|
+
if (!available) {
|
|
94
|
+
return passthrough(true, `compressor "${compressor.name}" unavailable — kept original`);
|
|
95
|
+
}
|
|
96
|
+
let result;
|
|
97
|
+
try {
|
|
98
|
+
result = await compressor.compress(input);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return passthrough(true, `compressor error: ${e.message ?? e}`);
|
|
102
|
+
}
|
|
103
|
+
const latencyMs = Math.max(0, now() - start);
|
|
104
|
+
const tokensAfter = estimateTokens(result.text.length);
|
|
105
|
+
if (!result.ok || tokensAfter >= tokensBefore) {
|
|
106
|
+
return {
|
|
107
|
+
...passthrough(!result.ok, result.note ??
|
|
108
|
+
(result.ok ? "no token reduction — kept original" : undefined)),
|
|
109
|
+
latencyMs,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const retrievalHandle = store ? store(input.key, input.text) : undefined;
|
|
113
|
+
return {
|
|
114
|
+
text: result.text,
|
|
115
|
+
tokensBefore,
|
|
116
|
+
tokensAfter,
|
|
117
|
+
tokensSaved: tokensBefore - tokensAfter,
|
|
118
|
+
...(retrievalHandle ? { retrievalHandle } : {}),
|
|
119
|
+
latencyMs,
|
|
120
|
+
compressorVersion: compressor.version,
|
|
121
|
+
degraded: false,
|
|
122
|
+
...(result.note ? { note: result.note } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build the {@link ToolUsage} evidence record for a compression, so the loop can
|
|
127
|
+
* attach it to a stage's `toolsUsed[]` (P19). Pure.
|
|
128
|
+
*/
|
|
129
|
+
export function compressionToolUsage(output, category, stage) {
|
|
130
|
+
return {
|
|
131
|
+
name: "headroom",
|
|
132
|
+
kind: "command",
|
|
133
|
+
...(stage ? { stage } : {}),
|
|
134
|
+
tokensSaved: output.tokensSaved,
|
|
135
|
+
...(output.retrievalHandle
|
|
136
|
+
? { retrievalHandle: output.retrievalHandle }
|
|
137
|
+
: {}),
|
|
138
|
+
reasons: [
|
|
139
|
+
`compressed ${category}`,
|
|
140
|
+
output.degraded
|
|
141
|
+
? "degraded: kept original"
|
|
142
|
+
: `saved ${output.tokensSaved} tokens`,
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Sum a set of compression outcomes into a {@link CompressionSummary}. Pure. */
|
|
147
|
+
export function summarizeCompression(outputs) {
|
|
148
|
+
const s = {
|
|
149
|
+
invocations: outputs.length,
|
|
150
|
+
tokensBefore: 0,
|
|
151
|
+
tokensAfter: 0,
|
|
152
|
+
tokensSaved: 0,
|
|
153
|
+
retrievals: 0,
|
|
154
|
+
degraded: 0,
|
|
155
|
+
};
|
|
156
|
+
for (const o of outputs) {
|
|
157
|
+
s.tokensBefore += o.tokensBefore;
|
|
158
|
+
s.tokensAfter += o.tokensAfter;
|
|
159
|
+
s.tokensSaved += o.tokensSaved;
|
|
160
|
+
if (o.retrievalHandle)
|
|
161
|
+
s.retrievals += 1;
|
|
162
|
+
if (o.degraded)
|
|
163
|
+
s.degraded += 1;
|
|
164
|
+
}
|
|
165
|
+
return s;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Summarize the compression {@link ToolUsage} records on a run's stage records
|
|
169
|
+
* (name `headroom`) — what `--context-report` reads, since the loop attaches
|
|
170
|
+
* compression evidence as `toolsUsed[]`. Pure.
|
|
171
|
+
*/
|
|
172
|
+
export function summarizeToolCompression(usages) {
|
|
173
|
+
let tokensSaved = 0;
|
|
174
|
+
let retrievals = 0;
|
|
175
|
+
let invocations = 0;
|
|
176
|
+
for (const u of usages) {
|
|
177
|
+
if (u.name !== "headroom")
|
|
178
|
+
continue;
|
|
179
|
+
invocations += 1;
|
|
180
|
+
tokensSaved += u.tokensSaved ?? 0;
|
|
181
|
+
if (u.retrievalHandle)
|
|
182
|
+
retrievals += 1;
|
|
183
|
+
}
|
|
184
|
+
return { invocations, tokensSaved, retrievals };
|
|
185
|
+
}
|
|
186
|
+
/** One-line human summary of compression savings for the context report. Pure. */
|
|
187
|
+
export function formatCompressionSummary(s) {
|
|
188
|
+
if (s.invocations === 0)
|
|
189
|
+
return "Context compression: not used.";
|
|
190
|
+
const pct = s.tokensBefore > 0 ? Math.round((s.tokensSaved / s.tokensBefore) * 100) : 0;
|
|
191
|
+
const parts = [
|
|
192
|
+
`Context compression: ${s.tokensSaved} tokens saved (${pct}%) across ${s.invocations} call(s)`,
|
|
193
|
+
`${s.retrievals} original(s) retained`,
|
|
194
|
+
];
|
|
195
|
+
if (s.degraded > 0)
|
|
196
|
+
parts.push(`${s.degraded} degraded (kept original)`);
|
|
197
|
+
return parts.join("; ") + ".";
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=context-compressor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-compressor.js","sourceRoot":"","sources":["../src/context-compressor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-report-cli.d.ts","sourceRoot":"","sources":["../src/context-report-cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context-report-cli.d.ts","sourceRoot":"","sources":["../src/context-report-cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,iBAAiB,CAAC;AAGzB;;;;;;;;;GASG;AAEH,mFAAmF;AACnF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B,CAAC;AAgBF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,EAAE,GACpB,MAAM,CAuER;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,GAAE,iBAA+B,GACpC,OAAO,CAAC,MAAM,CAAC,CAejB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { summarizeToolCompression } from "./context-compressor.js";
|
|
3
|
+
import { listRunIds, readStageRecords, } from "./run-report.js";
|
|
3
4
|
import { formatCacheEfficiency, summarizeCacheEfficiency } from "./tokens.js";
|
|
4
5
|
const defaultDeps = {
|
|
5
6
|
env: process.env,
|
|
@@ -59,6 +60,13 @@ export function formatContextReportRun(runId, stages) {
|
|
|
59
60
|
if (cache.totalInputTokens > 0) {
|
|
60
61
|
lines.push("", ` ${formatCacheEfficiency(cache)}`);
|
|
61
62
|
}
|
|
63
|
+
// Context compression savings (P20) — drawn from compression tool-usage records
|
|
64
|
+
// on the stage bundle; omitted entirely when no compressor ran.
|
|
65
|
+
const comp = summarizeToolCompression(stages.flatMap((s) => s.toolsUsed ?? []));
|
|
66
|
+
if (comp.invocations > 0) {
|
|
67
|
+
lines.push("", ` Context compression: ~${num.format(comp.tokensSaved)} tokens saved across ` +
|
|
68
|
+
`${comp.invocations} call(s); ${comp.retrievals} original(s) retained.`);
|
|
69
|
+
}
|
|
62
70
|
return lines.join("\n");
|
|
63
71
|
}
|
|
64
72
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-report-cli.js","sourceRoot":"","sources":["../src/context-report-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"context-report-cli.js","sourceRoot":"","sources":["../src/context-report-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { type Skill } from "./skills.js";
|
|
2
|
+
/**
|
|
3
|
+
* External skill source registry (issue #110 P16). Otto can import outside skill
|
|
4
|
+
* packs (Superpowers, PM-Skills, …) into the existing `.otto/skills/<name>/`
|
|
5
|
+
* package shape, but **import is separated from trust and runtime use**: every
|
|
6
|
+
* imported skill lands as `trust: "unverified"` with empty validation, so it
|
|
7
|
+
* stays inert on the loop until a later validation/activation slice (P17/P18).
|
|
8
|
+
*
|
|
9
|
+
* Two files back the registry, both pure fs + JSON with safe defaults (never
|
|
10
|
+
* throw on the read path, mirroring `skills.ts`/`memory.ts`):
|
|
11
|
+
*
|
|
12
|
+
* - `.otto/skills/sources.json` — the configured sources (`{ sources: [...] }`).
|
|
13
|
+
* - `.otto/skills.lock.json` — what was actually resolved and imported:
|
|
14
|
+
* resolved ref, checksum, import timestamp, source type, license, normalized
|
|
15
|
+
* skill name and capabilities, for deterministic diffing and drift audits.
|
|
16
|
+
*
|
|
17
|
+
* This slice supports `local` source directories (the roadmap's "local fixture
|
|
18
|
+
* directories before networked git fetch"). `git`/`archive` resolution layers on
|
|
19
|
+
* top later without changing the lock/normalization shape.
|
|
20
|
+
*/
|
|
21
|
+
/** A source's transport kind. `registry` is reserved for a later slice. */
|
|
22
|
+
export type ExternalSourceType = "git" | "local" | "archive" | "registry";
|
|
23
|
+
/** One configured external skill source (`.otto/skills/sources.json`). */
|
|
24
|
+
export type ExternalSkillSource = {
|
|
25
|
+
/** Registry key, unique within sources.json. */
|
|
26
|
+
name: string;
|
|
27
|
+
type: ExternalSourceType;
|
|
28
|
+
/** URL (git/archive) or filesystem path (local). */
|
|
29
|
+
location: string;
|
|
30
|
+
/** Pinned ref (sha/tag). Absent = unpinned (an audit finding). */
|
|
31
|
+
ref?: string;
|
|
32
|
+
};
|
|
33
|
+
/** One resolved import in `.otto/skills.lock.json`. */
|
|
34
|
+
export type ExternalSkillLockEntry = {
|
|
35
|
+
/** Normalized skill name = `.otto/skills/<skill>` directory. */
|
|
36
|
+
skill: string;
|
|
37
|
+
/** Source name it came from. */
|
|
38
|
+
source: string;
|
|
39
|
+
type: ExternalSourceType;
|
|
40
|
+
/** Package path within the source tree. */
|
|
41
|
+
upstreamPath: string;
|
|
42
|
+
/** Resolved ref the source was pinned to, if any. */
|
|
43
|
+
ref?: string;
|
|
44
|
+
/** Content checksum of the imported package. */
|
|
45
|
+
checksum: string;
|
|
46
|
+
/** Import timestamp (ISO). */
|
|
47
|
+
importedAt: string;
|
|
48
|
+
license?: string;
|
|
49
|
+
capabilities: string[];
|
|
50
|
+
};
|
|
51
|
+
export type ExternalSkillLock = {
|
|
52
|
+
entries: ExternalSkillLockEntry[];
|
|
53
|
+
};
|
|
54
|
+
/** Absolute path to the sources config (`.otto/skills/sources.json`). */
|
|
55
|
+
export declare function sourcesPath(workspaceDir: string): string;
|
|
56
|
+
/** Absolute path to the import lockfile (`.otto/skills.lock.json`). */
|
|
57
|
+
export declare function lockPath(workspaceDir: string): string;
|
|
58
|
+
/** Normalize one untrusted source entry; null when it lacks the required shape. */
|
|
59
|
+
export declare function parseSource(raw: unknown): ExternalSkillSource | null;
|
|
60
|
+
/** Read configured sources; absent/malformed → `[]` (never throws). */
|
|
61
|
+
export declare function readSources(workspaceDir: string): ExternalSkillSource[];
|
|
62
|
+
/** Write the sources config, sorted by name for deterministic diffs. */
|
|
63
|
+
export declare function writeSources(workspaceDir: string, sources: ExternalSkillSource[]): void;
|
|
64
|
+
/** Read the import lock; absent/malformed → `{ entries: [] }` (never throws). */
|
|
65
|
+
export declare function readLock(workspaceDir: string): ExternalSkillLock;
|
|
66
|
+
/** Write the lock, entries sorted by skill name for deterministic diffs. */
|
|
67
|
+
export declare function writeLock(workspaceDir: string, lock: ExternalSkillLock): void;
|
|
68
|
+
/** Add (or replace by name) a source. Pure — returns a new array. */
|
|
69
|
+
export declare function addSource(sources: ExternalSkillSource[], source: ExternalSkillSource): ExternalSkillSource[];
|
|
70
|
+
/** Remove a source by name. Pure — returns a new array. */
|
|
71
|
+
export declare function removeSource(sources: ExternalSkillSource[], name: string): ExternalSkillSource[];
|
|
72
|
+
/**
|
|
73
|
+
* Parse a leading `--- … ---` frontmatter block into flat string fields plus the
|
|
74
|
+
* remaining body. Minimal `key: value` parsing (no nested YAML) — enough for the
|
|
75
|
+
* `name`/`description`/`license`/`capabilities` fields skill packs declare.
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseFrontmatter(text: string): {
|
|
78
|
+
fields: Record<string, string>;
|
|
79
|
+
body: string;
|
|
80
|
+
};
|
|
81
|
+
/** A SKILL.md package located inside a source tree, before normalization. */
|
|
82
|
+
export type DiscoveredPackage = {
|
|
83
|
+
/** Normalized skill name (also the `.otto/skills/<name>` dir). */
|
|
84
|
+
name: string;
|
|
85
|
+
/** Package path within the source tree (POSIX-style, for the lock). */
|
|
86
|
+
upstreamPath: string;
|
|
87
|
+
/** Raw SKILL.md content. */
|
|
88
|
+
raw: string;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Find every `SKILL.md` package under a local source directory (recursively,
|
|
92
|
+
* depth-bounded). Covers the `skills/<name>/SKILL.md` Superpowers/PM-Skills shape
|
|
93
|
+
* and `.codex-plugin`/`.claude-plugin` bundles that nest the same file. Each
|
|
94
|
+
* package's skill name comes from its SKILL.md `name:` frontmatter, else its
|
|
95
|
+
* containing directory name, slugified. Returns packages sorted by name. Absent/
|
|
96
|
+
* unreadable dir → `[]` (never throws).
|
|
97
|
+
*/
|
|
98
|
+
export declare function discoverPackages(sourceDir: string): DiscoveredPackage[];
|
|
99
|
+
/**
|
|
100
|
+
* Normalize a discovered package + its source into an unverified, inert
|
|
101
|
+
* {@link Skill} plus the resolved {@link ExternalSkillLockEntry}. The skill body
|
|
102
|
+
* is the SKILL.md content minus frontmatter; capabilities come from a
|
|
103
|
+
* `capabilities:` frontmatter field when present (else empty); trust is always
|
|
104
|
+
* `unverified` and validation empty so the loop cannot apply it. `now` is the
|
|
105
|
+
* import timestamp (injected for deterministic tests).
|
|
106
|
+
*/
|
|
107
|
+
export declare function normalizePackage(source: ExternalSkillSource, pkg: DiscoveredPackage, now: Date): {
|
|
108
|
+
skill: Skill;
|
|
109
|
+
entry: ExternalSkillLockEntry;
|
|
110
|
+
};
|
|
111
|
+
/** What a `sync` would do to one skill package. */
|
|
112
|
+
export type SyncAction = "add" | "update" | "unchanged" | "conflict";
|
|
113
|
+
export type SyncPlanItem = {
|
|
114
|
+
skill: string;
|
|
115
|
+
source: string;
|
|
116
|
+
action: SyncAction;
|
|
117
|
+
/** Set for `conflict`: the other source already claiming this name. */
|
|
118
|
+
conflictWith?: string;
|
|
119
|
+
/** Resolved skill + lock entry (carried so apply needn't re-normalize). */
|
|
120
|
+
resolved: {
|
|
121
|
+
skill: Skill;
|
|
122
|
+
entry: ExternalSkillLockEntry;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
export type SyncPlan = {
|
|
126
|
+
items: SyncPlanItem[];
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Compute a deterministic sync plan over the given local sources without writing
|
|
130
|
+
* anything — the engine behind both `sync --dry-run` and `sync`. For each source
|
|
131
|
+
* (in name order) every discovered package is classified:
|
|
132
|
+
*
|
|
133
|
+
* - `conflict` — a different source earlier in the plan already claims the name;
|
|
134
|
+
* - `add` — the skill is not present on disk;
|
|
135
|
+
* - `update` — present, but the lock's recorded checksum differs (drift);
|
|
136
|
+
* - `unchanged` — present and the checksum matches the lock.
|
|
137
|
+
*
|
|
138
|
+
* Only `local` sources resolve in this slice; other types are skipped (a later
|
|
139
|
+
* slice adds git/archive fetch ahead of this same planner).
|
|
140
|
+
*/
|
|
141
|
+
export declare function planSync(workspaceDir: string, sources: ExternalSkillSource[], now: Date): SyncPlan;
|
|
142
|
+
/**
|
|
143
|
+
* Apply a sync plan: write each `add`/`update` skill package and refresh the lock
|
|
144
|
+
* to exactly the set of resolved (non-conflict) packages. `conflict` items are
|
|
145
|
+
* skipped (logged by the caller). Returns the persisted lock. Side-effecting
|
|
146
|
+
* counterpart to {@link planSync}; the dry-run path calls `planSync` only.
|
|
147
|
+
*/
|
|
148
|
+
export declare function applySync(workspaceDir: string, plan: SyncPlan): ExternalSkillLock;
|
|
149
|
+
/** One governance problem with the external registry. */
|
|
150
|
+
export type ExternalAuditFinding = {
|
|
151
|
+
kind: "unpinned-ref" | "missing-license" | "duplicate-name" | "unsupported-format" | "stale-copy";
|
|
152
|
+
subject: string;
|
|
153
|
+
detail: string;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Audit the external registry (`otto-skills audit --external`). Surfaces:
|
|
157
|
+
* unpinned source refs, lock entries with no license, duplicate skill names
|
|
158
|
+
* across sources, sources whose type this slice cannot resolve, and stale
|
|
159
|
+
* imported copies (on-disk skill checksum drifted from the lock). Pure over the
|
|
160
|
+
* given sources/lock + a checksum probe; deterministic, sorted by kind+subject.
|
|
161
|
+
*/
|
|
162
|
+
export declare function auditExternal(sources: ExternalSkillSource[], lock: ExternalSkillLock, onDiskChecksum: (skill: string) => string | null): ExternalAuditFinding[];
|
|
163
|
+
/**
|
|
164
|
+
* Recompute the checksum of an imported skill's on-disk body (`instructions.md`)
|
|
165
|
+
* so {@link auditExternal} can compare it to the lock and flag a `stale-copy`
|
|
166
|
+
* when the package was hand-edited after import. Returns null when the skill or
|
|
167
|
+
* its body is absent. The hash domain matches {@link normalizePackage}.
|
|
168
|
+
*/
|
|
169
|
+
export declare function importedChecksum(workspaceDir: string, skill: string): string | null;
|
|
170
|
+
//# sourceMappingURL=external-skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-skills.d.ts","sourceRoot":"","sources":["../src/external-skills.ts"],"names":[],"mappings":"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"}
|