opencodekit 0.17.13 → 0.18.1

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.
Files changed (44) hide show
  1. package/dist/index.js +4 -6
  2. package/dist/template/.opencode/AGENTS.md +57 -0
  3. package/dist/template/.opencode/agent/scout.md +0 -37
  4. package/dist/template/.opencode/command/resume.md +1 -1
  5. package/dist/template/.opencode/command/status.md +7 -14
  6. package/dist/template/.opencode/dcp.jsonc +81 -81
  7. package/dist/template/.opencode/memory/memory.db +0 -0
  8. package/dist/template/.opencode/memory.db +0 -0
  9. package/dist/template/.opencode/memory.db-shm +0 -0
  10. package/dist/template/.opencode/memory.db-wal +0 -0
  11. package/dist/template/.opencode/opencode.json +199 -23
  12. package/dist/template/.opencode/opencode.json.tui-migration.bak +1380 -0
  13. package/dist/template/.opencode/package.json +1 -1
  14. package/dist/template/.opencode/plugin/README.md +37 -25
  15. package/dist/template/.opencode/plugin/lib/capture.ts +177 -0
  16. package/dist/template/.opencode/plugin/lib/context.ts +194 -0
  17. package/dist/template/.opencode/plugin/lib/curator.ts +234 -0
  18. package/dist/template/.opencode/plugin/lib/db/maintenance.ts +312 -0
  19. package/dist/template/.opencode/plugin/lib/db/observations.ts +299 -0
  20. package/dist/template/.opencode/plugin/lib/db/pipeline.ts +520 -0
  21. package/dist/template/.opencode/plugin/lib/db/schema.ts +356 -0
  22. package/dist/template/.opencode/plugin/lib/db/types.ts +211 -0
  23. package/dist/template/.opencode/plugin/lib/distill.ts +376 -0
  24. package/dist/template/.opencode/plugin/lib/inject.ts +126 -0
  25. package/dist/template/.opencode/plugin/lib/memory-admin-tools.ts +188 -0
  26. package/dist/template/.opencode/plugin/lib/memory-db.ts +54 -936
  27. package/dist/template/.opencode/plugin/lib/memory-helpers.ts +202 -0
  28. package/dist/template/.opencode/plugin/lib/memory-hooks.ts +240 -0
  29. package/dist/template/.opencode/plugin/lib/memory-tools.ts +341 -0
  30. package/dist/template/.opencode/plugin/memory.ts +56 -60
  31. package/dist/template/.opencode/plugin/sessions.ts +372 -93
  32. package/dist/template/.opencode/skill/memory-system/SKILL.md +103 -60
  33. package/dist/template/.opencode/skill/session-management/SKILL.md +22 -35
  34. package/dist/template/.opencode/tui.json +15 -0
  35. package/package.json +1 -1
  36. package/dist/template/.opencode/plugin/compaction.ts +0 -190
  37. package/dist/template/.opencode/tool/action-queue.ts +0 -313
  38. package/dist/template/.opencode/tool/memory-admin.ts +0 -445
  39. package/dist/template/.opencode/tool/memory-get.ts +0 -143
  40. package/dist/template/.opencode/tool/memory-read.ts +0 -45
  41. package/dist/template/.opencode/tool/memory-search.ts +0 -264
  42. package/dist/template/.opencode/tool/memory-timeline.ts +0 -105
  43. package/dist/template/.opencode/tool/memory-update.ts +0 -63
  44. package/dist/template/.opencode/tool/observation.ts +0 -357
@@ -5,7 +5,29 @@ description: Use when persisting learnings, loading previous context, or searchi
5
5
 
6
6
  # Memory System Best Practices
7
7
 
8
- Persistent context that survives across sessions. Uses **SQLite + FTS5** for searchable observations.
8
+ Persistent context that survives across sessions. Uses **4-tier automated knowledge pipeline** backed by SQLite + FTS5.
9
+
10
+ ## Architecture
11
+
12
+ ```
13
+ message.part.updated → capture.ts → temporal_messages
14
+ ↓ (session.idle, 10+ messages)
15
+ distill.ts → distillations (TF-IDF + key sentences)
16
+ ↓ (session.idle)
17
+ curator.ts → observations (pattern-matched)
18
+
19
+ system.transform ← inject.ts ← FTS5 search → scored + packed → system prompt
20
+ messages.transform ← context.ts → token budget enforcement
21
+ ```
22
+
23
+ ### 4 Tiers
24
+
25
+ | Tier | Storage | Populated By | Purpose |
26
+ | ----------------- | ------------- | ------------------- | ------------------------------- |
27
+ | temporal_messages | SQLite | Automatic (capture) | Raw message text, 180-day TTL |
28
+ | distillations | SQLite + FTS5 | Automatic (idle) | TF-IDF compressed sessions |
29
+ | observations | SQLite + FTS5 | Manual + curator | Decisions, bugs, patterns, etc. |
30
+ | memory_files | SQLite | Manual | Static docs, handoffs, research |
9
31
 
10
32
  ## Core Principle
11
33
 
@@ -23,11 +45,11 @@ Always search memory first.
23
45
 
24
46
  ```typescript
25
47
  // Search for relevant past work
26
- memory_search({ query: "<task keywords>", limit: 5 });
27
- memory_search({ query: "bugfix <component>", type: "observations" });
48
+ memory - search({ query: "<task keywords>", limit: 5 });
49
+ memory - search({ query: "bugfix <component>", type: "observations" });
28
50
 
29
51
  // Check recent handoffs
30
- memory_search({ query: "handoff", type: "handoffs", limit: 3 });
52
+ memory - search({ query: "handoff", type: "handoffs", limit: 3 });
31
53
  ```
32
54
 
33
55
  **Why:** Past you already solved this. Don't rediscover.
@@ -38,14 +60,14 @@ Don't fetch full content until you know you need it.
38
60
 
39
61
  ```typescript
40
62
  // 1. Search returns compact index (50-100 tokens per result)
41
- const results = memory_search({ query: "auth patterns" });
63
+ const results = memory - search({ query: "auth patterns" });
42
64
  // Returns: [{id: 42, title: "Auth bug fixed", ...}]
43
65
 
44
66
  // 2. Fetch full details ONLY for relevant IDs
45
- memory_get({ ids: "42,45" });
67
+ memory - get({ ids: "42,45" });
46
68
 
47
69
  // 3. See what led to this decision
48
- memory_timeline({ anchor_id: 42, depth_before: 3 });
70
+ memory - timeline({ anchor_id: 42, depth_before: 3 });
49
71
  ```
50
72
 
51
73
  **Why:** Prevents context bloat. High signal, low noise.
@@ -56,12 +78,13 @@ Create observations for anything non-obvious. Don't wait until the end.
56
78
 
57
79
  ```typescript
58
80
  observation({
59
- type: "pattern", // decision | bugfix | pattern | discovery | warning
81
+ type: "pattern", // decision | bugfix | pattern | discovery | warning | learning
60
82
  title: "Brief description",
61
83
  narrative: "Context and reasoning...",
62
84
  facts: "key, facts, here",
63
85
  concepts: "searchable, keywords",
64
86
  files_modified: "src/file.ts",
87
+ source: "manual", // manual (default) | curator | imported
65
88
  });
66
89
  ```
67
90
 
@@ -79,9 +102,10 @@ observation({
79
102
  Document completion state for future you.
80
103
 
81
104
  ```typescript
82
- memory_update({
83
- file: "handoffs/YYYY-MM-DD-task",
84
- content: `## Completed
105
+ memory -
106
+ update({
107
+ file: "handoffs/YYYY-MM-DD-task",
108
+ content: `## Completed
85
109
  - X
86
110
 
87
111
  ## Blockers
@@ -89,8 +113,8 @@ memory_update({
89
113
 
90
114
  ## Next
91
115
  - Z`,
92
- mode: "append",
93
- });
116
+ mode: "append",
117
+ });
94
118
  ```
95
119
 
96
120
  ---
@@ -99,18 +123,18 @@ memory_update({
99
123
 
100
124
  ### memory-search (Start Here)
101
125
 
102
- Fast FTS5 full-text search. Returns **compact index** for progressive disclosure.
126
+ Fast FTS5 full-text search with porter stemming. Returns **compact index** for progressive disclosure.
103
127
 
104
128
  ```typescript
105
- memory_search({ query: "authentication" });
106
- memory_search({ query: "bugfix", type: "observations", limit: 5 });
107
- memory_search({ query: "session", type: "handoffs" });
108
- memory_search({ query: "patterns", type: "all" }); // Search everything
129
+ memory - search({ query: "authentication" });
130
+ memory - search({ query: "bugfix", type: "observations", limit: 5 });
131
+ memory - search({ query: "session", type: "handoffs" });
132
+ memory - search({ query: "patterns", type: "all" }); // Search everything
109
133
  ```
110
134
 
111
135
  **Search modes:**
112
136
 
113
- - `observations` (default): Search SQLite with FTS5 ranking
137
+ - `observations` (default): Search SQLite with FTS5 BM25 ranking
114
138
  - `handoffs`, `research`, `templates`: Search specific directories
115
139
  - `beads`: Search .beads/artifacts
116
140
  - `all`: Search everything
@@ -120,8 +144,8 @@ memory_search({ query: "patterns", type: "all" }); // Search everything
120
144
  Fetch full observation details after identifying relevant IDs:
121
145
 
122
146
  ```typescript
123
- memory_get({ ids: "42" }); // Single observation
124
- memory_get({ ids: "1,5,10" }); // Multiple observations
147
+ memory - get({ ids: "42" }); // Single observation
148
+ memory - get({ ids: "1,5,10" }); // Multiple observations
125
149
  ```
126
150
 
127
151
  ### memory-timeline (Chronological Context)
@@ -129,7 +153,7 @@ memory_get({ ids: "1,5,10" }); // Multiple observations
129
153
  See what happened before/after a specific observation:
130
154
 
131
155
  ```typescript
132
- memory_timeline({ anchor_id: 42, depth_before: 5, depth_after: 5 });
156
+ memory - timeline({ anchor_id: 42, depth_before: 5, depth_after: 5 });
133
157
  ```
134
158
 
135
159
  ### memory-read (Files)
@@ -137,9 +161,9 @@ memory_timeline({ anchor_id: 42, depth_before: 5, depth_after: 5 });
137
161
  Load project files, handoffs, or templates:
138
162
 
139
163
  ```typescript
140
- memory_read({ file: "project/gotchas" });
141
- memory_read({ file: "handoffs/2024-01-20-phase-1" });
142
- memory_read({ file: "research/auth-patterns" });
164
+ memory - read({ file: "project/gotchas" });
165
+ memory - read({ file: "handoffs/2024-01-20-phase-1" });
166
+ memory - read({ file: "research/auth-patterns" });
143
167
  ```
144
168
 
145
169
  ### memory-update (Files)
@@ -147,13 +171,40 @@ memory_read({ file: "research/auth-patterns" });
147
171
  Save to project files or handoffs:
148
172
 
149
173
  ```typescript
150
- memory_update({
151
- file: "project/gotchas",
152
- content: "### New Gotcha\n\nDescription...",
153
- mode: "append", // or "replace"
154
- });
174
+ memory -
175
+ update({
176
+ file: "project/gotchas",
177
+ content: "### New Gotcha\n\nDescription...",
178
+ mode: "append", // or "replace"
179
+ });
180
+ ```
181
+
182
+ ### memory-admin (Maintenance)
183
+
184
+ ```typescript
185
+ // Check current status (schema, FTS5, counts, DB size)
186
+ memory - admin({ operation: "status" });
187
+
188
+ // Full maintenance (archive >90 days, checkpoint WAL, vacuum)
189
+ memory - admin({ operation: "full" });
190
+
191
+ // Preview what would be archived
192
+ memory - admin({ operation: "archive", older_than_days: 60, dry_run: true });
193
+
194
+ // Capture pipeline stats (temporal messages, distillations, compression)
195
+ memory - admin({ operation: "capture-stats" });
196
+
197
+ // Force distillation for current session
198
+ memory - admin({ operation: "distill-now" });
199
+
200
+ // Force curator run (extract observations from distillations)
201
+ memory - admin({ operation: "curate-now" });
155
202
  ```
156
203
 
204
+ **Automatic:** On session idle — distillation, curation, FTS5 optimize, WAL checkpoint.
205
+
206
+ **Manual:** Run `memory-admin({ operation: "status" })` to check health.
207
+
157
208
  ---
158
209
 
159
210
  ## What Goes Where
@@ -161,24 +212,34 @@ memory_update({
161
212
  ### SQLite (observations)
162
213
 
163
214
  - Events: decisions, bugfixes, patterns discovered
164
- - Searchable via FTS5
215
+ - Searchable via FTS5 with porter stemming
216
+ - Created manually via `observation()` or automatically by curator
165
217
  - Use `observation()` to create
166
218
 
219
+ ### SQLite (distillations — automatic)
220
+
221
+ - Compressed session summaries with TF-IDF terms
222
+ - Created automatically when 10+ messages accumulate
223
+ - Searchable via FTS5
224
+ - Used for relevance-scored LTM injection
225
+
167
226
  ### Markdown Files
168
227
 
169
228
  - Static knowledge: user preferences, tech stack
170
229
  - Handoffs: session summaries
171
230
  - Research: deep-dive documents
172
- - Use `memory_read()` / `memory_update()`
173
-
174
- | Location | Content | Tool |
175
- | -------------------------- | -------------------------- | --------------------------------- |
176
- | `project/user.md` | User identity, preferences | `memory_read()` |
177
- | `project/tech-stack.md` | Frameworks, constraints | `memory_read()` |
178
- | `project/gotchas.md` | Footguns, warnings | `memory_update({mode: "append"})` |
179
- | `handoffs/YYYY-MM-DD-*.md` | Session summaries | `memory_update()` |
180
- | `research/*.md` | Deep-dive analysis | `memory_update()` |
181
- | SQLite | Observations, events | `observation()` |
231
+ - Use `memory-read()` / `memory-update()`
232
+
233
+ | Location | Content | Tool |
234
+ | -------------------------- | -------------------------- | ----------------------------------- |
235
+ | `project/user.md` | User identity, preferences | `memory-read()` |
236
+ | `project/tech-stack.md` | Frameworks, constraints | `memory-read()` |
237
+ | `project/gotchas.md` | Footguns, warnings | `memory-update({ mode: "append" })` |
238
+ | `handoffs/YYYY-MM-DD-*.md` | Session summaries | `memory-update()` |
239
+ | `research/*.md` | Deep-dive analysis | `memory-update()` |
240
+ | SQLite observations | Events, decisions | `observation()` |
241
+ | SQLite distillations | Session summaries | Automatic (idle) or `distill-now` |
242
+ | SQLite temporal_messages | Raw captured text | Automatic (message events) |
182
243
 
183
244
  ---
184
245
 
@@ -195,6 +256,7 @@ observation({
195
256
  files_read: "src/auth.ts, src/middleware.ts",
196
257
  files_modified: "src/auth.ts",
197
258
  bead_id: "br-abc123", // Link to task (optional)
259
+ source: "manual", // manual (default), curator, imported
198
260
  });
199
261
  ```
200
262
 
@@ -213,25 +275,6 @@ observation({
213
275
 
214
276
  ---
215
277
 
216
- ## Maintenance
217
-
218
- ```typescript
219
- // Check current status
220
- memory_admin({ operation: "status" });
221
-
222
- // Full maintenance (archive >90 days, checkpoint WAL, vacuum)
223
- memory_admin({ operation: "full" });
224
-
225
- // Preview what would be archived
226
- memory_admin({ operation: "archive", older_than_days: 60, dry_run: true });
227
- ```
228
-
229
- **Automatic:** Runs at session end (FTS5 optimize, WAL checkpoint if >1MB)
230
-
231
- **Manual:** Run monthly or when storage grows
232
-
233
- ---
234
-
235
278
  ## Philosophy
236
279
 
237
280
  **Memory is not a dumping ground. It's curated signal.**
@@ -19,48 +19,35 @@ The environment monitors context usage and warns at these thresholds:
19
19
 
20
20
  ## Session Tools
21
21
 
22
- ### list_sessions
22
+ ### find_sessions
23
23
 
24
- Discover available sessions before reading:
24
+ Search and discover sessions by keyword. Returns ranked results with match counts and snippets.
25
25
 
26
26
  ```typescript
27
- list_sessions({ limit: 10, project: "current" }); // Current project
28
- list_sessions({ since: "today" }); // Today's sessions
29
- list_sessions({ project: "all", since: "yesterday" }); // Cross-project
27
+ find_sessions({ query: "auth bug", limit: 5 }); // Search by keyword
28
+ find_sessions({ query: "refactor" }); // Default limit
30
29
  ```
31
30
 
32
- ### read_session
31
+ **Parameters:**
33
32
 
34
- Pull context from previous session:
33
+ - `query` (required): Search keywords — multi-word queries use AND matching
34
+ - `limit` (optional): Max results to return
35
35
 
36
- ```typescript
37
- read_session("last"); // Most recent
38
- read_session("2 ago", { project: "current" }); // 2nd most recent
39
- read_session("today"); // Today's first session
40
- read_session("ses_abc123", { focus: "file changes" }); // Specific aspect
41
- ```
36
+ **Returns:** Ranked sessions with match count, snippets, and suggested next steps.
42
37
 
43
- ### search_session
38
+ ### read_session
44
39
 
45
- Full-text search across sessions:
40
+ Read messages from a specific session. Supports optional focus filtering.
46
41
 
47
42
  ```typescript
48
- search_session({ query: "auth bug" }); // Search all sessions
49
- search_session({ query: "OAuth", session_id: "ses_abc" }); // Specific session
50
- search_session({ query: "error", limit: 10 }); // Limit results
43
+ read_session({ session_id: "ses_abc123" }); // Full session
44
+ read_session({ session_id: "ses_abc123", focus: "auth" }); // Filter to relevant messages
51
45
  ```
52
46
 
53
- Use to find past discussions, decisions, or work on a topic before starting new work.
54
-
55
- ### summarize_session
56
-
57
- Generate AI summary of a session:
58
-
59
- ```typescript
60
- summarize_session("ses_abc123"); // Trigger AI summarization
61
- ```
47
+ **Parameters:**
62
48
 
63
- Use before `read_session` to get a quick overview of what happened in a past session without loading full context.
49
+ - `session_id` (required): Session ID from find_sessions results
50
+ - `focus` (optional): Keyword to filter messages within the session
64
51
 
65
52
  ## When to Start New Session
66
53
 
@@ -74,11 +61,11 @@ Use before `read_session` to get a quick overview of what happened in a past ses
74
61
  ```
75
62
  Session 1: Implement feature X (80k tokens)
76
63
  ↓ close, update memory
77
- Session 2: list_sessions() → read_session("last") → Refactor (60k tokens)
64
+ Session 2: find_sessions({ query: "feature X" }) → read_session(...) → Refactor (60k tokens)
78
65
 
79
- Session 3: read_session("previous") → Add tests (90k tokens)
66
+ Session 3: find_sessions({ query: "feature X" }) → Add tests (90k tokens)
80
67
 
81
- Session 4: read_session refs → Final review (100k tokens)
68
+ Session 4: read_session(...) → Final review (100k tokens)
82
69
  ```
83
70
 
84
71
  **Result**: 4 fresh contexts vs 1 degraded 330k context. Better performance, lower cost.
@@ -87,10 +74,10 @@ Session 4: read_session refs → Final review (100k tokens)
87
74
 
88
75
  Use all available sources:
89
76
 
90
- 1. `read_session("last")` - Previous session work
91
- 2. Git state - `git diff`, `git log` - Code changes
92
- 3. Memory files - `.opencode/memory/*` - Persistent context
93
- 4. Beads - `br show <id>` - Task specs
77
+ 1. `find_sessions` + `read_session` Previous session work
78
+ 2. Git state `git diff`, `git log` Code changes
79
+ 3. Memory files `.opencode/memory/*` Persistent context
80
+ 4. Beads `br show <id>` Task specs
94
81
 
95
82
  **Don't**: Carry everything forward. Extract what's needed, discard the rest.
96
83
 
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://opencode.ai/tui.json",
3
+ "keybinds": {
4
+ "command_list": ";",
5
+ "leader": "`",
6
+ "session_child_cycle": "ctrl+alt+right",
7
+ "session_child_cycle_reverse": "ctrl+alt+left",
8
+ "session_compact": "ctrl+k"
9
+ },
10
+ "scroll_speed": 3,
11
+ "scroll_acceleration": {
12
+ "enabled": true
13
+ },
14
+ "diff_style": "auto"
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.17.13",
3
+ "version": "0.18.1",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "keywords": ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
6
6
  "license": "MIT",
@@ -1,190 +0,0 @@
1
- /**
2
- * Session Continuity Plugin (compaction-time context injection)
3
- *
4
- * Purpose:
5
- * - Provide compact, bounded state needed to resume work after compaction.
6
- * - Keep injected prompt guidance minimal and deterministic.
7
- *
8
- * Non-goals:
9
- * - Replace DCP policy/rules management.
10
- * - Inject large free-form manuals into the prompt.
11
- */
12
-
13
- import { Database } from "bun:sqlite";
14
- import { readFile, readdir, stat } from "node:fs/promises";
15
- import path from "node:path";
16
- import type { Plugin } from "@opencode-ai/plugin";
17
-
18
- const MAX_SESSION_CONTEXT_CHARS = 3000;
19
- const MAX_PROJECT_FILES = 3;
20
- const MAX_PROJECT_FILE_CHARS = 900;
21
- const MAX_HANDOFF_CHARS = 2500;
22
- const MAX_BEADS = 8;
23
- const MAX_COMBINED_CONTEXT_CHARS = 9000;
24
-
25
- interface BeadRow {
26
- id: string;
27
- title: string;
28
- }
29
-
30
- function truncate(text: string, maxChars: number): string {
31
- if (text.length <= maxChars) return text;
32
- return `${text.slice(0, maxChars)}\n...[truncated]`;
33
- }
34
-
35
- async function safeReadFile(filePath: string): Promise<string> {
36
- try {
37
- return await readFile(filePath, "utf-8");
38
- } catch {
39
- return "";
40
- }
41
- }
42
-
43
- function renderSection(title: string, body: string): string {
44
- if (!body.trim()) return "";
45
- return `## ${title}\n${body.trim()}`;
46
- }
47
-
48
- async function readProjectMemoryContext(memoryDir: string): Promise<string> {
49
- const projectDir = path.join(memoryDir, "project");
50
- let names: string[] = [];
51
- try {
52
- names = (await readdir(projectDir))
53
- .filter((name) => name.endsWith(".md"))
54
- .sort()
55
- .slice(0, MAX_PROJECT_FILES);
56
- } catch {
57
- return "";
58
- }
59
-
60
- const chunks: string[] = [];
61
- for (const name of names) {
62
- const fullPath = path.join(projectDir, name);
63
- const content = (await safeReadFile(fullPath)).trim();
64
- if (!content) continue;
65
- chunks.push(
66
- `### ${name.replace(/\.md$/, "")}\n${truncate(content, MAX_PROJECT_FILE_CHARS)}`,
67
- );
68
- }
69
-
70
- return chunks.join("\n\n");
71
- }
72
-
73
- async function readLatestHandoff(handoffDir: string): Promise<string> {
74
- let names: string[] = [];
75
- try {
76
- names = (await readdir(handoffDir)).filter((name) => name.endsWith(".md"));
77
- } catch {
78
- return "";
79
- }
80
-
81
- if (names.length === 0) return "";
82
-
83
- const withMtime = await Promise.all(
84
- names.map(async (name) => {
85
- const fullPath = path.join(handoffDir, name);
86
- try {
87
- const info = await stat(fullPath);
88
- return { name, fullPath, mtimeMs: info.mtimeMs };
89
- } catch {
90
- return { name, fullPath, mtimeMs: 0 };
91
- }
92
- }),
93
- );
94
-
95
- withMtime.sort((a, b) => b.mtimeMs - a.mtimeMs);
96
- const latest = withMtime[0];
97
- const content = (await safeReadFile(latest.fullPath)).trim();
98
- if (!content) return "";
99
-
100
- return `Source: ${latest.name}\n${truncate(content, MAX_HANDOFF_CHARS)}`;
101
- }
102
-
103
- /**
104
- * Read in-progress beads directly from SQLite database.
105
- * Cross-platform alternative to shell command execution.
106
- */
107
- function readInProgressBeads(directory: string): string {
108
- const dbPath = path.join(directory, ".beads", "beads.db");
109
- let db: Database | undefined;
110
-
111
- try {
112
- db = new Database(dbPath, { readonly: true });
113
-
114
- const rows = db
115
- .query<BeadRow, [number]>(
116
- "SELECT id, title FROM issues WHERE status = 'in_progress' ORDER BY updated_at DESC LIMIT ?",
117
- )
118
- .all(MAX_BEADS);
119
-
120
- if (rows.length === 0) return "";
121
-
122
- return rows.map((row) => `- ${row.id}: ${row.title}`).join("\n");
123
- } catch {
124
- // Database may not exist, be locked, or have different schema
125
- // Return empty string to allow graceful degradation
126
- return "";
127
- } finally {
128
- db?.close();
129
- }
130
- }
131
-
132
- export const CompactionPlugin: Plugin = async ({ directory }) => {
133
- const memoryDir = path.join(directory, ".opencode", "memory");
134
- const handoffDir = path.join(memoryDir, "handoffs");
135
-
136
- return {
137
- "experimental.session.compacting": async (_input, output) => {
138
- const sessionContext = truncate(
139
- (await safeReadFile(path.join(memoryDir, "session-context.md"))).trim(),
140
- MAX_SESSION_CONTEXT_CHARS,
141
- );
142
-
143
- const [projectContext, handoffContext] = await Promise.all([
144
- readProjectMemoryContext(memoryDir),
145
- readLatestHandoff(handoffDir),
146
- ]);
147
-
148
- // Synchronous SQLite query - no async/await needed
149
- const beadsContext = readInProgressBeads(directory);
150
-
151
- const combined = [
152
- renderSection("Session Continuity", sessionContext),
153
- renderSection("Active Beads", beadsContext),
154
- renderSection("Previous Handoff", handoffContext),
155
- renderSection("Project Memory", projectContext),
156
- ]
157
- .filter(Boolean)
158
- .join("\n\n");
159
-
160
- if (combined) {
161
- output.context.push(
162
- `## Session Context\n${truncate(combined, MAX_COMBINED_CONTEXT_CHARS)}\n`,
163
- );
164
- }
165
-
166
- output.prompt = `${output.prompt}
167
-
168
- <compaction_task>
169
- Summarize conversation state for reliable continuation after compaction.
170
- </compaction_task>
171
-
172
- <compaction_rules>
173
- - Preserve exact IDs, file paths, and unresolved constraints.
174
- - Distinguish completed work from current in-progress work.
175
- - Keep summary concise and execution-focused.
176
- - If critical context is missing, state uncertainty explicitly.
177
- </compaction_rules>
178
-
179
- <compaction_output>
180
- Include:
181
- - What was done
182
- - What is being worked on now
183
- - Files currently in play
184
- - Next actions
185
- - Persistent user constraints/preferences
186
- </compaction_output>
187
- `;
188
- },
189
- };
190
- };