opencode-clm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/LICENSE +35 -0
- package/NOTICE +41 -0
- package/README.md +294 -0
- package/index.ts +332 -0
- package/package.json +28 -0
- package/skills/clm-context/SKILL.md +166 -0
- package/src/budget.ts +304 -0
- package/src/clm.ts +725 -0
- package/src/compact.ts +73 -0
- package/src/context-document.ts +659 -0
- package/src/continuity.ts +722 -0
- package/src/mirror-guard.ts +95 -0
- package/src/mirror-store.ts +154 -0
- package/src/observation.ts +155 -0
- package/src/opencode.ts +339 -0
- package/src/overflow.ts +212 -0
- package/src/policy.ts +60 -0
- package/src/presentation.ts +127 -0
- package/src/projection.ts +146 -0
- package/src/settings.ts +189 -0
- package/src/state.ts +241 -0
- package/src/steering.ts +54 -0
- package/src/types.ts +102 -0
- package/steering/house-brief.md +43 -0
package/src/compact.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Adapted from pi-clm src/compact.ts (MIT, Copyright 2026 Emanuel Casco).
|
|
2
|
+
/**
|
|
3
|
+
* `/clm-compact [instructions]`: ask the model to compact its own live context now.
|
|
4
|
+
*
|
|
5
|
+
* This is a fixed prompt, not a harness-side edit: the model decides what to keep and
|
|
6
|
+
* edits the mirror with its tools, and the edit is validated and committed before the next
|
|
7
|
+
* request like any other. (OpenCode's own compaction instead summarizes the raw transcript
|
|
8
|
+
* with a separate model call.)
|
|
9
|
+
*
|
|
10
|
+
* A replacement template (the `compactPrompt` setting) may use `{{mirror}}`, `{{current}}`,
|
|
11
|
+
* `{{budget}}` and `{{instructions}}`; unknown placeholders are left as written.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
|
|
16
|
+
import { formatTokens } from "./budget.ts";
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_COMPACT_PROMPT = `Compact your live context now.
|
|
19
|
+
|
|
20
|
+
Your next request is about {{current}} tokens{{budget}}. Your context is mirrored at \`{{mirror}}\`; edit that file, following the Editable context protocol, to remove what you no longer need.
|
|
21
|
+
|
|
22
|
+
Keep what you still need: the task and the user's latest requests, decisions and their reasons, open items, and exact values (ids, paths, numbers) you will use again. Drop what you no longer need: tool output you have already used, superseded drafts and intermediate steps. Prefer one scripted edit of the whole file over many small ones.
|
|
23
|
+
|
|
24
|
+
{{instructions}}
|
|
25
|
+
|
|
26
|
+
When the edit is saved, reply in one line: what you kept, and the new approximate size.`;
|
|
27
|
+
|
|
28
|
+
export interface CompactPromptValues {
|
|
29
|
+
mirror: string;
|
|
30
|
+
/** Calibrated size of the next request, tokens. */
|
|
31
|
+
current: number;
|
|
32
|
+
budget?: number;
|
|
33
|
+
/** Free text after `/clm-compact`. */
|
|
34
|
+
instructions?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildCompactPrompt(template: string, values: CompactPromptValues): string {
|
|
38
|
+
const instructions = values.instructions?.trim() ? `Also: ${values.instructions.trim()}` : "";
|
|
39
|
+
const replacements = new Map<string, string>([
|
|
40
|
+
["mirror", values.mirror],
|
|
41
|
+
["current", formatTokens(values.current)],
|
|
42
|
+
["budget", values.budget === undefined ? "" : ` (budget ${formatTokens(values.budget)})`],
|
|
43
|
+
["instructions", instructions],
|
|
44
|
+
]);
|
|
45
|
+
// Typed instructions are never dropped: a template without the placeholder gets them at the end.
|
|
46
|
+
const withInstructions = instructions && !template.includes("{{instructions}}") ? `${template}\n\n{{instructions}}` : template;
|
|
47
|
+
// An empty instructions slot takes its own blank lines with it; the rest of the template stays as written.
|
|
48
|
+
const emptySlot = "\u0000";
|
|
49
|
+
return withInstructions
|
|
50
|
+
.replace(/\{\{(\w+)\}\}/g, (match, name: string) => (name === "instructions" && !instructions ? emptySlot : replacements.get(name) ?? match))
|
|
51
|
+
.replace(/\n*\u0000\n*/g, (slot) => (slot.includes("\n") ? "\n\n" : ""))
|
|
52
|
+
.trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `CLM_COMPACT_PROMPT`: a template path; unset, `default`, `none` or `off` mean the built-in prompt. */
|
|
56
|
+
export function compactPromptFromEnv(env: Record<string, string | undefined> = process.env): string | undefined {
|
|
57
|
+
const raw = env.CLM_COMPACT_PROMPT?.trim();
|
|
58
|
+
if (!raw || ["default", "none", "off"].includes(raw.toLowerCase())) return undefined;
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The built-in prompt, or the template at `path` (read on every use, so edits apply at once). */
|
|
63
|
+
export function loadCompactPrompt(path: string | undefined): string {
|
|
64
|
+
if (!path) return DEFAULT_COMPACT_PROMPT;
|
|
65
|
+
let text: string;
|
|
66
|
+
try {
|
|
67
|
+
text = readFileSync(path, "utf8").trim();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(`compactPrompt: cannot read ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
|
+
}
|
|
71
|
+
if (!text) throw new Error(`compactPrompt: template is empty: ${path}`);
|
|
72
|
+
return text;
|
|
73
|
+
}
|