@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.
@@ -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":"AAEA,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGjF;;;;;;;;;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,CAyDR;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,GAAE,iBAA+B,GACpC,OAAO,CAAC,MAAM,CAAC,CAajB"}
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 { listRunIds, readStageRecords } from "./run-report.js";
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":"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"}
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,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EACL,UAAU,EACV,gBAAgB,GAEjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAqB9E,MAAM,WAAW,GAAsB;IACrC,GAAG,EAAE,OAAO,CAAC,GAAG;IAChB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;IAClB,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;IAC1C,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;CAC3C,CAAC;AAEF,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;AAC3C,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC,CAAC;AAEzE,SAAS,GAAG,CAAC,EAAY;IACvB,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC;AACnD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAAa,EACb,MAAqB;IAErB,MAAM,KAAK,GAAa,CAAC,wBAAwB,KAAK,EAAE,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC;IAClE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CACR,uEAAuE,CACxE,CAAC;QACF,KAAK,CAAC,IAAI,CACR,qEAAqE,CACtE,CAAC;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,MAAM,oBAAoB,EAAE,EAAE,CAAC,CAAC;IACzD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,gBAAiB,CAAC;QAC9B,MAAM,MAAM,GAAG,CAAC,CAAC,QAAQ;aACtB,GAAG,CACF,CAAC,GAAG,EAAE,EAAE,CACN,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,CAC3B,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CACxD,GAAG,CACP;aACA,IAAI,CAAC,KAAK,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CACR,SAAS,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,KAAK,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS;YACzE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAChC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAiB,CAAC,eAAe,CAAC,CAAC;IACtE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;IACvD,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,MAAM,KAAK,GACT,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;QACpE,KAAK,CAAC,IAAI,CACR,6CAA6C,GAAG,CAAC,MAAM,CACrD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CACrB,sBAAsB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MACpD,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EACxB,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,CACtC,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,iEAAiE;IACjE,MAAM,KAAK,GAAG,wBAAwB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,IAAI,KAAK,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,gFAAgF;IAChF,gEAAgE;IAChE,MAAM,IAAI,GAAG,wBAAwB,CACnC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CACzC,CAAC;IACF,IAAI,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CACR,EAAE,EACF,2BAA2B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,uBAAuB;YAC5E,GAAG,IAAI,CAAC,WAAW,aAAa,IAAI,CAAC,UAAU,wBAAwB,CAC1E,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAA0B,WAAW;IAErC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IACrC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC,GAAG,CACN,uBAAuB,YAAY,gBAAgB;YACjD,oDAAoD,CACvD,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,GAAG,CACN,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CACrE,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -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":"AAIA,OAAO,EAML,KAAK,KAAK,EACX,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;;;GAkBG;AAEH,2EAA2E;AAC3E,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAE1E,0EAA0E;AAC1E,MAAM,MAAM,mBAAmB,GAAG;IAChC,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC;IACzB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,uDAAuD;AACvD,MAAM,MAAM,sBAAsB,GAAG;IACnC,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,kBAAkB,CAAC;IACzB,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAAE,OAAO,EAAE,sBAAsB,EAAE,CAAA;CAAE,CAAC;AAOtE,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErD;AAeD,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAapE;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAkBvE;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,mBAAmB,EAAE,GAC7B,IAAI,CAMN;AA4BD,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,iBAAiB,CAkBhE;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAQ7E;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CACvB,OAAO,EAAE,mBAAmB,EAAE,EAC9B,MAAM,EAAE,mBAAmB,GAC1B,mBAAmB,EAAE,CAEvB;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAC1B,OAAO,EAAE,mBAAmB,EAAE,EAC9B,IAAI,EAAE,MAAM,GACX,mBAAmB,EAAE,CAEvB;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd,CASA;AAWD,6EAA6E;AAC7E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAYF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA+BvE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,iBAAiB,EACtB,GAAG,EAAE,IAAI,GACR;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,sBAAsB,CAAA;CAAE,CA6CjD;AAED,mDAAmD;AACnD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;AAErE,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;IACnB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,sBAAsB,CAAA;KAAE,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IAAE,KAAK,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC;AAEjD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CACtB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,mBAAmB,EAAE,EAC9B,GAAG,EAAE,IAAI,GACR,QAAQ,CAsCV;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,QAAQ,GACb,iBAAiB,CAYnB;AAED,yDAAyD;AACzD,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EACA,cAAc,GACd,iBAAiB,GACjB,gBAAgB,GAChB,oBAAoB,GACpB,YAAY,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,mBAAmB,EAAE,EAC9B,IAAI,EAAE,iBAAiB,EACvB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAC/C,oBAAoB,EAAE,CA0DxB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAUf"}