opencodekit 0.16.15 → 0.16.18

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 (85) hide show
  1. package/README.md +77 -242
  2. package/dist/index.js +19 -6
  3. package/dist/template/.opencode/AGENTS.md +72 -236
  4. package/dist/template/.opencode/README.md +49 -482
  5. package/dist/template/.opencode/agent/build.md +71 -345
  6. package/dist/template/.opencode/agent/explore.md +47 -139
  7. package/dist/template/.opencode/agent/general.md +61 -172
  8. package/dist/template/.opencode/agent/looker.md +65 -161
  9. package/dist/template/.opencode/agent/painter.md +46 -200
  10. package/dist/template/.opencode/agent/plan.md +37 -220
  11. package/dist/template/.opencode/agent/review.md +72 -153
  12. package/dist/template/.opencode/agent/scout.md +44 -486
  13. package/dist/template/.opencode/agent/vision.md +63 -178
  14. package/dist/template/.opencode/command/create.md +75 -307
  15. package/dist/template/.opencode/command/design.md +53 -589
  16. package/dist/template/.opencode/command/handoff.md +76 -180
  17. package/dist/template/.opencode/command/init.md +45 -211
  18. package/dist/template/.opencode/command/plan.md +62 -514
  19. package/dist/template/.opencode/command/pr.md +56 -226
  20. package/dist/template/.opencode/command/research.md +55 -266
  21. package/dist/template/.opencode/command/resume.md +33 -138
  22. package/dist/template/.opencode/command/review-codebase.md +54 -202
  23. package/dist/template/.opencode/command/ship.md +78 -127
  24. package/dist/template/.opencode/command/start.md +47 -577
  25. package/dist/template/.opencode/command/status.md +60 -353
  26. package/dist/template/.opencode/command/ui-review.md +52 -298
  27. package/dist/template/.opencode/command/verify.md +36 -250
  28. package/dist/template/.opencode/memory.db-shm +0 -0
  29. package/dist/template/.opencode/memory.db-wal +0 -0
  30. package/dist/template/.opencode/opencode.json +133 -35
  31. package/dist/template/.opencode/plugin/README.md +40 -166
  32. package/dist/template/.opencode/plugin/compaction.ts +162 -131
  33. package/dist/template/.opencode/plugin/lib/memory-db.ts +112 -0
  34. package/dist/template/.opencode/plugin/swarm-enforcer.ts +182 -27
  35. package/dist/template/.opencode/skill/augment-context-engine/SKILL.md +112 -0
  36. package/dist/template/.opencode/skill/augment-context-engine/mcp.json +6 -0
  37. package/dist/template/.opencode/skill/core-data-expert/SKILL.md +82 -0
  38. package/dist/template/.opencode/skill/core-data-expert/references/batch-operations.md +543 -0
  39. package/dist/template/.opencode/skill/core-data-expert/references/cloudkit-integration.md +259 -0
  40. package/dist/template/.opencode/skill/core-data-expert/references/concurrency.md +522 -0
  41. package/dist/template/.opencode/skill/core-data-expert/references/fetch-requests.md +643 -0
  42. package/dist/template/.opencode/skill/core-data-expert/references/glossary.md +233 -0
  43. package/dist/template/.opencode/skill/core-data-expert/references/migration.md +393 -0
  44. package/dist/template/.opencode/skill/core-data-expert/references/model-configuration.md +597 -0
  45. package/dist/template/.opencode/skill/core-data-expert/references/performance.md +300 -0
  46. package/dist/template/.opencode/skill/core-data-expert/references/persistent-history.md +553 -0
  47. package/dist/template/.opencode/skill/core-data-expert/references/project-audit.md +60 -0
  48. package/dist/template/.opencode/skill/core-data-expert/references/saving.md +574 -0
  49. package/dist/template/.opencode/skill/core-data-expert/references/stack-setup.md +625 -0
  50. package/dist/template/.opencode/skill/core-data-expert/references/testing.md +300 -0
  51. package/dist/template/.opencode/skill/core-data-expert/references/threading.md +589 -0
  52. package/dist/template/.opencode/skill/swift-concurrency/SKILL.md +246 -0
  53. package/dist/template/.opencode/skill/swift-concurrency/references/actors.md +640 -0
  54. package/dist/template/.opencode/skill/swift-concurrency/references/async-algorithms.md +822 -0
  55. package/dist/template/.opencode/skill/swift-concurrency/references/async-await-basics.md +249 -0
  56. package/dist/template/.opencode/skill/swift-concurrency/references/async-sequences.md +670 -0
  57. package/dist/template/.opencode/skill/swift-concurrency/references/core-data.md +533 -0
  58. package/dist/template/.opencode/skill/swift-concurrency/references/glossary.md +128 -0
  59. package/dist/template/.opencode/skill/swift-concurrency/references/linting.md +142 -0
  60. package/dist/template/.opencode/skill/swift-concurrency/references/memory-management.md +542 -0
  61. package/dist/template/.opencode/skill/swift-concurrency/references/migration.md +1076 -0
  62. package/dist/template/.opencode/skill/swift-concurrency/references/performance.md +574 -0
  63. package/dist/template/.opencode/skill/swift-concurrency/references/sendable.md +578 -0
  64. package/dist/template/.opencode/skill/swift-concurrency/references/tasks.md +604 -0
  65. package/dist/template/.opencode/skill/swift-concurrency/references/testing.md +565 -0
  66. package/dist/template/.opencode/skill/swift-concurrency/references/threading.md +452 -0
  67. package/dist/template/.opencode/skill/swiftui-expert-skill/SKILL.md +290 -0
  68. package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-advanced.md +351 -0
  69. package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-basics.md +284 -0
  70. package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-transitions.md +326 -0
  71. package/dist/template/.opencode/skill/swiftui-expert-skill/references/image-optimization.md +286 -0
  72. package/dist/template/.opencode/skill/swiftui-expert-skill/references/layout-best-practices.md +312 -0
  73. package/dist/template/.opencode/skill/swiftui-expert-skill/references/liquid-glass.md +377 -0
  74. package/dist/template/.opencode/skill/swiftui-expert-skill/references/list-patterns.md +153 -0
  75. package/dist/template/.opencode/skill/swiftui-expert-skill/references/modern-apis.md +400 -0
  76. package/dist/template/.opencode/skill/swiftui-expert-skill/references/performance-patterns.md +377 -0
  77. package/dist/template/.opencode/skill/swiftui-expert-skill/references/scroll-patterns.md +305 -0
  78. package/dist/template/.opencode/skill/swiftui-expert-skill/references/sheet-navigation-patterns.md +292 -0
  79. package/dist/template/.opencode/skill/swiftui-expert-skill/references/state-management.md +447 -0
  80. package/dist/template/.opencode/skill/swiftui-expert-skill/references/text-formatting.md +285 -0
  81. package/dist/template/.opencode/skill/swiftui-expert-skill/references/view-structure.md +276 -0
  82. package/dist/template/.opencode/tool/action-queue.ts +308 -0
  83. package/dist/template/.opencode/tool/swarm.ts +65 -40
  84. package/package.json +16 -3
  85. package/dist/template/.opencode/.agents/skills/context7/SKILL.md +0 -88
@@ -1,184 +1,58 @@
1
1
  # OpenCode Plugins
2
2
 
3
- TypeScript plugins for extending OpenCode functionality following official best practices.
3
+ Plugins in this directory extend OpenCode with project-specific behavior and tools.
4
4
 
5
- ## Directory Structure
5
+ ## Current Plugin Files
6
6
 
7
- ```
7
+ ```text
8
8
  plugin/
9
+ ├── memory.ts # Memory DB maintenance + observation toasts
10
+ ├── sessions.ts # Session tools (list/read/search/summarize)
11
+ ├── compaction.ts # Compaction-time context recovery injection
12
+ ├── swarm-enforcer.ts # Beads workflow enforcement and reminders
13
+ ├── skill-mcp.ts # Skill-scoped MCP bridge (skill_mcp tools)
14
+ ├── copilot-auth.ts # GitHub Copilot provider/auth integration
9
15
  ├── lib/
10
- └── notify.ts # Shared notification utilities
11
- ├── injector.ts # AGENTS.md hierarchy walker
12
- ├── compactor.ts # Context usage warnings
13
- ├── enforcer.ts # TODO completion enforcement
14
- ├── notification.ts # Session completion alerts
15
- ├── sessions.ts # Session management tools
16
- ├── truncator.ts # Output size monitoring
17
- └── README.md
18
- ```
19
-
20
- ## Installed Plugins
21
-
22
- ### injector.ts
23
-
24
- **AGENTS.md hierarchy walker** - solves OpenCode's limitation where findUp only finds the first AGENTS.md match.
25
-
26
- - Hooks into `tool.execute.after` for `read` tool
27
- - Walks up from file directory to project root
28
- - Collects ALL AGENTS.md files in the path
29
- - Injects in order: root → specific (T-shaped context loading)
30
- - Caches per session to avoid duplicate injections
31
-
32
- **Example:** When reading `src/components/Button.tsx`:
33
-
34
- ```
35
- Injects:
36
- 1. /project/AGENTS.md (root context)
37
- 2. /project/src/AGENTS.md (src context)
38
- 3. /project/src/components/AGENTS.md (component context)
39
- ```
40
-
41
- ### sessions.ts
42
-
43
- **Session management and context transfer** - enables short, focused sessions.
44
-
45
- **Tools:**
46
-
47
- - `list_sessions(project?, since?, limit?)` - Discover available sessions
48
- - `read_session(session_reference, project?, focus?)` - Load context from previous sessions
49
- - `summarize_session(session_id)` - Generate AI summary of a session
50
-
51
- **Workflow pattern:**
52
-
53
- ```
54
- Session 1: Implementation (80k) → close
55
- Session 2: read_session("last") → Refactor (60k) → close
56
- Session 3: read_session("previous") → Tests (90k) → close
16
+ ├── memory-db.ts # SQLite + FTS5 memory backend
17
+ │ └── notify.ts # Shared notification helpers
18
+ └── sdk/ # Copilot SDK adaptation code
57
19
  ```
58
20
 
59
- ### compactor.ts
60
-
61
- **Context usage warnings** - notifies at token thresholds before hitting limits.
62
-
63
- - Warns at 70% (info), 85% (warn), 95% (critical)
64
- - Sends native notifications at 85%+
65
- - Tracks per-session to avoid duplicate warnings
66
-
67
- ### enforcer.ts
68
-
69
- **TODO completion enforcement** - forces continuation when session idles with incomplete work.
70
-
71
- - Tracks TODOs per session via `todo.updated` events
72
- - On `session.idle`, checks for incomplete high-priority or in-progress TODOs
73
- - **ENFORCES** continuation by calling `client.session.promptAsync()` (not just notification)
74
- - Injects prompt: "Continue working on incomplete TODOs: [list]"
75
- - 5-minute cooldown to prevent spam
76
- - Falls back to OS notification if prompt injection fails
77
-
78
- **Behavior:**
79
-
80
- - High-priority or in-progress TODOs → Inject continuation prompt
81
- - Low-priority pending TODOs → OS notification only (no forced continuation)
21
+ ## Plugin Responsibilities
82
22
 
83
- ### swarm-enforcer.ts
23
+ - `memory.ts`
24
+ - Optimizes FTS5 index on idle sessions
25
+ - Checkpoints WAL when needed
26
+ - Shows toast feedback for observation saves and session errors
84
27
 
85
- **Swarm protocol guardrails (Beads-as-board)** - nudges agents to follow the Beads-first workflow.
28
+ - `sessions.ts`
29
+ - Provides custom tools: `list_sessions`, `read_session`, `search_session`, `summarize_session`
86
30
 
87
- - Treats `.beads/` as the single source of truth for the swarm task board
88
- - If user expresses implementation intent and no task is `in_progress`, injects a nudge to use `br ready/show/update`
89
- - If there are `in_progress` tasks but `.beads/artifacts/<id>/spec.md` is missing, nudges to create it before implementation
90
- - On `file.edited`, warns if code is being changed without a claimed task or with missing `spec.md`
91
- - On `session.idle`, reminds to `br close` + `br sync` when work is done
31
+ - `compaction.ts`
32
+ - Injects session continuity context during compaction
33
+ - Pulls memory/project/handoff context and recovery instructions
92
34
 
93
- ### notification.ts
35
+ - `swarm-enforcer.ts`
36
+ - Injects bead state and stage labels into system context
37
+ - Warns when implementation starts without a properly started bead
38
+ - Reminds to close/sync in-progress work on session idle
94
39
 
95
- **Session completion alerts** - sends native notifications when AI finishes.
40
+ - `skill-mcp.ts`
41
+ - Loads MCP configs from skills
42
+ - Exposes `skill_mcp`, `skill_mcp_status`, `skill_mcp_disconnect`
43
+ - Supports tool filtering with `includeTools`
96
44
 
97
- - Extracts session summary from messages
98
- - Cross-platform (macOS, Linux, WSL, Windows)
99
- - Uses shared `lib/notify.ts` utilities
100
-
101
- ### truncator.ts
102
-
103
- **Output size monitoring** - logs warnings for large outputs under context pressure.
104
-
105
- - Monitors `tool.execute.after` events
106
- - Warns when outputs exceed thresholds based on context usage
107
- - Note: Actual truncation requires OpenCode core changes; this is observation-only
108
-
109
- ## Shared Library
110
-
111
- ### lib/notify.ts
112
-
113
- Shared utilities used by multiple plugins:
114
-
115
- ```typescript
116
- import { notify, THRESHOLDS, getContextPercentage } from "./lib/notify";
117
-
118
- // Send cross-platform notification using $ shell API
119
- await notify($, "Title", "Message");
120
-
121
- // Context thresholds
122
- THRESHOLDS.MODERATE; // 70%
123
- THRESHOLDS.URGENT; // 85%
124
- THRESHOLDS.CRITICAL; // 95%
125
- ```
126
-
127
- ## Best Practices Applied
128
-
129
- ### Use `$` Shell API (not `exec`)
130
-
131
- ```typescript
132
- // ✅ Correct - uses Bun shell from plugin context
133
- export const MyPlugin: Plugin = async ({ $ }) => {
134
- await $`osascript -e 'display notification "Done!"'`;
135
- };
136
-
137
- // ❌ Wrong - manual exec with escaping
138
- import { exec } from "child_process";
139
- exec(`osascript -e '...'`, () => {});
140
- ```
141
-
142
- ### Share Common Code
143
-
144
- ```typescript
145
- // ✅ Correct - import from shared lib
146
- import { notify } from "./lib/notify";
147
-
148
- // ❌ Wrong - copy-paste same code in every plugin
149
- function notify() {
150
- /* duplicated */
151
- }
152
- ```
153
-
154
- ### Proper Plugin Structure
155
-
156
- ```typescript
157
- import type { Plugin } from "@opencode-ai/plugin";
158
-
159
- export const MyPlugin: Plugin = async ({ client, $ }) => {
160
- return {
161
- event: async ({ event }) => {
162
- /* ... */
163
- },
164
- "tool.execute.after": async (input, output) => {
165
- /* ... */
166
- },
167
- };
168
- };
169
- ```
45
+ - `copilot-auth.ts`
46
+ - Handles GitHub Copilot OAuth/device flow
47
+ - Adds model/provider request shaping for compatible reasoning behavior
170
48
 
171
- ## Available Hooks
49
+ ## Notes
172
50
 
173
- | Hook | Purpose |
174
- | --------------------- | --------------------------------------------------------------- |
175
- | `event` | Listen to OpenCode events (session.idle, session.updated, etc.) |
176
- | `tool.execute.before` | Hook before tool execution |
177
- | `tool.execute.after` | Hook after tool execution (observation only) |
178
- | `tool` | Add custom tools |
51
+ - `notification.ts.bak` is a backup file and not part of the active plugin set.
52
+ - Keep plugin documentation aligned with actual files in this directory.
53
+ - Prefer shared helpers in `lib/` over duplicated utilities across plugins.
179
54
 
180
- ## Resources
55
+ ## References
181
56
 
182
- - [OpenCode Plugin Documentation](https://opencode.ai/docs/plugins/)
183
- - [OpenCode Custom Tools](https://opencode.ai/docs/custom-tools/)
184
- - [Community Examples](https://github.com/sst/opencode/discussions)
57
+ - OpenCode plugin docs: https://opencode.ai/docs/plugins/
58
+ - OpenCode custom tools docs: https://opencode.ai/docs/custom-tools/
@@ -1,152 +1,183 @@
1
1
  /**
2
- * Session Continuity Plugin (Compaction-Time Context Injection)
2
+ * Session Continuity Plugin (compaction-time context injection)
3
3
  *
4
- * Complements DCP plugin (which handles context management rules via
5
- * experimental.chat.system.transform). This plugin fires ONLY during
6
- * compaction events (experimental.session.compacting) and handles:
4
+ * Purpose:
5
+ * - Provide compact, bounded state needed to resume work after compaction.
6
+ * - Keep injected prompt guidance minimal and deterministic.
7
7
  *
8
- * 1. Load session-context.md (CONTINUITY.md pattern) — highest priority
9
- * 2. Load project memory files (user prefs, tech stack, gotchas)
10
- * 3. Inject beads in-progress state (active task IDs)
11
- * 4. Load most recent handoff file for session resumption
12
- * 5. Append post-compaction recovery protocol
13
- *
14
- * Division of responsibility:
15
- * - DCP plugin: context budget zones, distill/compress/prune rules,
16
- * prunable-tools list, nudge system (always-on via system.transform)
17
- * - This plugin: session state preservation, post-compaction recovery,
18
- * memory persistence reminders (compaction-time only)
8
+ * Non-goals:
9
+ * - Replace DCP policy/rules management.
10
+ * - Inject large free-form manuals into the prompt.
19
11
  */
20
12
 
13
+ import { execFile } from "node:child_process";
14
+ import { readFile, readdir, stat } from "node:fs/promises";
15
+ import path from "node:path";
16
+ import { promisify } from "node:util";
21
17
  import type { Plugin } from "@opencode-ai/plugin";
22
18
 
23
- export const CompactionPlugin: Plugin = async ({ $, directory }) => {
24
- const MEMORY_DIR = `${directory}/.opencode/memory`;
25
- const HANDOFF_DIR = `${MEMORY_DIR}/handoffs`;
26
-
27
- return {
28
- "experimental.session.compacting": async (input, output) => {
29
- // 1. Load session context (CONTINUITY.md pattern) - HIGHEST PRIORITY
30
- let sessionContext = "";
31
- try {
32
- const sessionFile = `${MEMORY_DIR}/session-context.md`;
33
- const content = await $`cat ${sessionFile} 2>/dev/null`.text();
34
- if (content.trim()) {
35
- sessionContext = `\n## Session Continuity (Compaction-Safe)\n${content}`;
36
- }
37
- } catch {
38
- // No session context yet
39
- }
40
-
41
- // 2. Load project memory files
42
- let memoryContext = "";
43
- try {
44
- const memoryFiles =
45
- await $`find ${MEMORY_DIR}/project -name '*.md' -type f 2>/dev/null | head -10`.quiet();
46
- if (memoryFiles.stdout) {
47
- const files = memoryFiles.stdout.toString().trim().split("\n");
48
- for (const file of files) {
49
- const content = await $`cat ${file}`.text();
50
- if (content.trim() && !content.includes("<!-- ")) {
51
- const name = file.split("/").pop()?.replace(".md", "");
52
- memoryContext += `\n## Project ${name}\n${content}`;
53
- }
54
- }
55
- }
56
- } catch {
57
- // Memory dir doesn't exist or other error
58
- }
59
-
60
- // 3. Inject beads in-progress state
61
- let beadsContext = "";
62
- try {
63
- const result =
64
- await $`cd ${directory} && br list --status in_progress --json 2>/dev/null`.quiet();
65
- if (result.stdout) {
66
- const inProgress = JSON.parse(result.stdout.toString());
67
- if (inProgress.length > 0) {
68
- beadsContext = `\n## Active Beads (In Progress)\n${inProgress.map((b: { id: string; title: string }) => `- ${b.id}: ${b.title}`).join("\n")}`;
69
- }
70
- }
71
- } catch {
72
- // Beads not available, skip
73
- }
74
-
75
- // 4. Load most recent handoff file (session continuity)
76
- let handoffContext = "";
19
+ const execFileAsync = promisify(execFile);
20
+
21
+ const MAX_SESSION_CONTEXT_CHARS = 3000;
22
+ const MAX_PROJECT_FILES = 3;
23
+ const MAX_PROJECT_FILE_CHARS = 900;
24
+ const MAX_HANDOFF_CHARS = 2500;
25
+ const MAX_BEADS = 8;
26
+ const MAX_COMBINED_CONTEXT_CHARS = 9000;
27
+
28
+ function truncate(text: string, maxChars: number): string {
29
+ if (text.length <= maxChars) return text;
30
+ return `${text.slice(0, maxChars)}\n...[truncated]`;
31
+ }
32
+
33
+ async function safeReadFile(filePath: string): Promise<string> {
34
+ try {
35
+ return await readFile(filePath, "utf-8");
36
+ } catch {
37
+ return "";
38
+ }
39
+ }
40
+
41
+ function renderSection(title: string, body: string): string {
42
+ if (!body.trim()) return "";
43
+ return `## ${title}\n${body.trim()}`;
44
+ }
45
+
46
+ async function readProjectMemoryContext(memoryDir: string): Promise<string> {
47
+ const projectDir = path.join(memoryDir, "project");
48
+ let names: string[] = [];
49
+ try {
50
+ names = (await readdir(projectDir))
51
+ .filter((name) => name.endsWith(".md"))
52
+ .sort()
53
+ .slice(0, MAX_PROJECT_FILES);
54
+ } catch {
55
+ return "";
56
+ }
57
+
58
+ const chunks: string[] = [];
59
+ for (const name of names) {
60
+ const fullPath = path.join(projectDir, name);
61
+ const content = (await safeReadFile(fullPath)).trim();
62
+ if (!content) continue;
63
+ chunks.push(
64
+ `### ${name.replace(/\.md$/, "")}\n${truncate(content, MAX_PROJECT_FILE_CHARS)}`,
65
+ );
66
+ }
67
+
68
+ return chunks.join("\n\n");
69
+ }
70
+
71
+ async function readLatestHandoff(handoffDir: string): Promise<string> {
72
+ let names: string[] = [];
73
+ try {
74
+ names = (await readdir(handoffDir)).filter((name) => name.endsWith(".md"));
75
+ } catch {
76
+ return "";
77
+ }
78
+
79
+ if (names.length === 0) return "";
80
+
81
+ const withMtime = await Promise.all(
82
+ names.map(async (name) => {
83
+ const fullPath = path.join(handoffDir, name);
77
84
  try {
78
- const result =
79
- await $`ls -t ${HANDOFF_DIR}/*.md 2>/dev/null | head -1`.quiet();
80
- if (result.stdout) {
81
- const handoffPath = result.stdout.toString().trim();
82
- if (handoffPath) {
83
- const handoffContent = await $`cat ${handoffPath}`.text();
84
- handoffContext = `\n## Previous Session Handoff\n\n${handoffContent}\n\n**IMPORTANT**: Resume work from where previous session left off.`;
85
- }
86
- }
85
+ const info = await stat(fullPath);
86
+ return { name, fullPath, mtimeMs: info.mtimeMs };
87
87
  } catch {
88
- // No handoff files, skip
88
+ return { name, fullPath, mtimeMs: 0 };
89
89
  }
90
+ }),
91
+ );
92
+
93
+ withMtime.sort((a, b) => b.mtimeMs - a.mtimeMs);
94
+ const latest = withMtime[0];
95
+ const content = (await safeReadFile(latest.fullPath)).trim();
96
+ if (!content) return "";
97
+
98
+ return `Source: ${latest.name}\n${truncate(content, MAX_HANDOFF_CHARS)}`;
99
+ }
100
+
101
+ async function readInProgressBeads(directory: string): Promise<string> {
102
+ try {
103
+ const { stdout } = await execFileAsync(
104
+ "br",
105
+ ["list", "--status", "in_progress", "--json"],
106
+ {
107
+ cwd: directory,
108
+ timeout: 10000,
109
+ },
110
+ );
111
+
112
+ const parsed = JSON.parse(stdout as string) as Array<{
113
+ id: string;
114
+ title: string;
115
+ }>;
116
+
117
+ if (!Array.isArray(parsed) || parsed.length === 0) return "";
118
+
119
+ return parsed
120
+ .slice(0, MAX_BEADS)
121
+ .map((item) => `- ${item.id}: ${item.title}`)
122
+ .join("\n");
123
+ } catch {
124
+ return "";
125
+ }
126
+ }
127
+
128
+ export const CompactionPlugin: Plugin = async ({ directory }) => {
129
+ const memoryDir = path.join(directory, ".opencode", "memory");
130
+ const handoffDir = path.join(memoryDir, "handoffs");
90
131
 
91
- // Inject all context - session context FIRST (most important)
92
- const allContext = [
93
- sessionContext,
94
- beadsContext,
95
- handoffContext,
96
- memoryContext,
132
+ return {
133
+ "experimental.session.compacting": async (_input, output) => {
134
+ const sessionContext = truncate(
135
+ (await safeReadFile(path.join(memoryDir, "session-context.md"))).trim(),
136
+ MAX_SESSION_CONTEXT_CHARS,
137
+ );
138
+
139
+ const [projectContext, beadsContext, handoffContext] = await Promise.all([
140
+ readProjectMemoryContext(memoryDir),
141
+ readInProgressBeads(directory),
142
+ readLatestHandoff(handoffDir),
143
+ ]);
144
+
145
+ const combined = [
146
+ renderSection("Session Continuity", sessionContext),
147
+ renderSection("Active Beads", beadsContext),
148
+ renderSection("Previous Handoff", handoffContext),
149
+ renderSection("Project Memory", projectContext),
97
150
  ]
98
151
  .filter(Boolean)
99
- .join("\n");
152
+ .join("\n\n");
100
153
 
101
- if (allContext) {
102
- output.context.push(`## Session Context\n${allContext}\n`);
154
+ if (combined) {
155
+ output.context.push(
156
+ `## Session Context\n${truncate(combined, MAX_COMBINED_CONTEXT_CHARS)}\n`,
157
+ );
103
158
  }
104
159
 
105
- // Append post-compaction recovery protocol
106
- // NOTE: Context management rules (distill/compress/prune, tool guidance)
107
- // are handled by DCP plugin via experimental.chat.system.transform (always present).
108
- // This plugin ONLY handles session continuity and post-compaction recovery.
109
160
  output.prompt = `${output.prompt}
110
161
 
111
- ## Post-Compaction Recovery Protocol
112
-
113
- ### IMMEDIATE: Restore Session State (Do This First)
114
- 1. Read session-context.md via \`memory-read({ file: "session-context" })\`
115
- 2. If missing or stale, reconstruct from the compaction summary above
116
- 3. Resume work from the "Now" item do NOT restart from scratch
117
- 4. Ask 1-3 targeted questions ONLY if critical gaps remain (don't interrogate)
118
-
119
- ### Session Continuity Document
120
- Maintain session-context.md via memory-update tool. This is your lifeline after compaction.
121
-
122
- \`\`\`
123
- Goal: [What + measurable success criteria]
124
- Constraints: [Hard limits. Mark UNCONFIRMED if inferred, never assume]
125
- Decisions: [Key choices made this session + WHY]
126
- State:
127
- Done: [Completed items with verification status]
128
- Now: [Current focus - ONE thing only]
129
- Next: [Queued items in priority order]
130
- Open Questions: [Uncertainties - mark UNCONFIRMED, note what would resolve each]
131
- Working Set: [Exact file paths, bead IDs, branch name, worktree path if applicable]
132
- \`\`\`
133
-
134
- Update session-context.md when: goal changes, key decision made, state shifts, or uncertainty discovered.
135
-
136
- ### Preservation Priorities (Compaction-Specific)
137
- These items MUST survive compaction — if they appeared in conversation, include in summary:
138
- - **Bead state**: IDs (br-xxx), status, in-progress tasks, dependencies
139
- - **Todo items**: Exact IDs, statuses, priorities, content
140
- - **File paths**: Exact paths with line numbers where relevant
141
- - **User constraints**: Verbatim. Never paraphrase.
142
- - **Decisions + rationale**: Both WHAT and WHY
143
- - **UNCONFIRMED items**: Keep uncertainty markers intact
144
-
145
- ### Memory Persistence (Before Compaction Loses It)
146
- Save discoveries to persistent memory so they survive:
147
- - Gotchas/edge cases → observation({ type: "warning", ... })
148
- - Architecture insights → observation({ type: "pattern", ... })
149
- - Key decisions → observation({ type: "decision", ... })
162
+ <compaction_task>
163
+ Summarize conversation state for reliable continuation after compaction.
164
+ </compaction_task>
165
+
166
+ <compaction_rules>
167
+ - Preserve exact IDs, file paths, and unresolved constraints.
168
+ - Distinguish completed work from current in-progress work.
169
+ - Keep summary concise and execution-focused.
170
+ - If critical context is missing, state uncertainty explicitly.
171
+ </compaction_rules>
172
+
173
+ <compaction_output>
174
+ Include:
175
+ - What was done
176
+ - What is being worked on now
177
+ - Files currently in play
178
+ - Next actions
179
+ - Persistent user constraints/preferences
180
+ </compaction_output>
150
181
  `;
151
182
  },
152
183
  };
@@ -85,6 +85,31 @@ export interface MemoryFileRow {
85
85
  updated_at_epoch: number | null;
86
86
  }
87
87
 
88
+ export type ActionQueueSource = "approval" | "bead" | "worker";
89
+ export type ActionQueueStatus = "pending" | "ready" | "idle";
90
+
91
+ export interface ActionQueueItemRow {
92
+ id: string;
93
+ source: ActionQueueSource;
94
+ status: ActionQueueStatus;
95
+ title: string;
96
+ owner: string | null;
97
+ payload: string | null;
98
+ created_at: string;
99
+ created_at_epoch: number;
100
+ updated_at: string | null;
101
+ updated_at_epoch: number | null;
102
+ }
103
+
104
+ export interface ActionQueueItemInput {
105
+ id: string;
106
+ source: ActionQueueSource;
107
+ status: ActionQueueStatus;
108
+ title: string;
109
+ owner?: string;
110
+ payload?: Record<string, unknown>;
111
+ }
112
+
88
113
  // ============================================================================
89
114
  // Schema
90
115
  // ============================================================================
@@ -153,6 +178,23 @@ CREATE TABLE IF NOT EXISTS memory_files (
153
178
  );
154
179
 
155
180
  CREATE INDEX IF NOT EXISTS idx_memory_files_path ON memory_files(file_path);
181
+
182
+ -- Action queue table for orchestration status snapshots
183
+ CREATE TABLE IF NOT EXISTS action_queue_items (
184
+ id TEXT PRIMARY KEY,
185
+ source TEXT NOT NULL CHECK(source IN ('approval','bead','worker')),
186
+ status TEXT NOT NULL CHECK(status IN ('pending','ready','idle')),
187
+ title TEXT NOT NULL,
188
+ owner TEXT,
189
+ payload TEXT,
190
+ created_at TEXT NOT NULL,
191
+ created_at_epoch INTEGER NOT NULL,
192
+ updated_at TEXT,
193
+ updated_at_epoch INTEGER
194
+ );
195
+
196
+ CREATE INDEX IF NOT EXISTS idx_action_queue_status ON action_queue_items(status);
197
+ CREATE INDEX IF NOT EXISTS idx_action_queue_source ON action_queue_items(source);
156
198
  `;
157
199
 
158
200
  // FTS5 sync triggers (separate because they can't use IF NOT EXISTS)
@@ -574,6 +616,76 @@ export function getMemoryFile(filePath: string): MemoryFileRow | null {
574
616
  .get(filePath) as MemoryFileRow | null;
575
617
  }
576
618
 
619
+ /**
620
+ * Replace action queue snapshot with a new set of items.
621
+ */
622
+ export function replaceActionQueueItems(items: ActionQueueItemInput[]): void {
623
+ const db = getMemoryDB();
624
+ const now = new Date();
625
+
626
+ const insertStmt = db.query(
627
+ `
628
+ INSERT INTO action_queue_items
629
+ (id, source, status, title, owner, payload, created_at, created_at_epoch)
630
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
631
+ ON CONFLICT(id) DO UPDATE SET
632
+ source = excluded.source,
633
+ status = excluded.status,
634
+ title = excluded.title,
635
+ owner = excluded.owner,
636
+ payload = excluded.payload,
637
+ updated_at = excluded.created_at,
638
+ updated_at_epoch = excluded.created_at_epoch
639
+ `,
640
+ );
641
+
642
+ db.transaction(() => {
643
+ db.run("DELETE FROM action_queue_items");
644
+ for (const item of items) {
645
+ insertStmt.run(
646
+ item.id,
647
+ item.source,
648
+ item.status,
649
+ item.title,
650
+ item.owner ?? null,
651
+ item.payload ? JSON.stringify(item.payload) : null,
652
+ now.toISOString(),
653
+ now.getTime(),
654
+ );
655
+ }
656
+ })();
657
+ }
658
+
659
+ /**
660
+ * Return action queue items, optionally filtered by status.
661
+ */
662
+ export function listActionQueueItems(
663
+ status?: ActionQueueStatus,
664
+ ): ActionQueueItemRow[] {
665
+ const db = getMemoryDB();
666
+ if (!status) {
667
+ return db
668
+ .query(
669
+ "SELECT * FROM action_queue_items ORDER BY created_at_epoch DESC, id ASC",
670
+ )
671
+ .all() as ActionQueueItemRow[];
672
+ }
673
+
674
+ return db
675
+ .query(
676
+ "SELECT * FROM action_queue_items WHERE status = ? ORDER BY created_at_epoch DESC, id ASC",
677
+ )
678
+ .all(status) as ActionQueueItemRow[];
679
+ }
680
+
681
+ /**
682
+ * Clear all action queue items.
683
+ */
684
+ export function clearActionQueueItems(): void {
685
+ const db = getMemoryDB();
686
+ db.run("DELETE FROM action_queue_items");
687
+ }
688
+
577
689
  // ============================================================================
578
690
  // FTS5 Maintenance
579
691
  // ============================================================================