opencodekit 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/template/.opencode/command/handoff.md +6 -6
- package/dist/template/.opencode/lib/lsp/config.ts +85 -3
- package/dist/template/.opencode/lib/lsp/constants.ts +216 -15
- package/dist/template/.opencode/memory/handoffs/2025-12-27T103000Z.md +76 -0
- package/dist/template/.opencode/opencode.json +535 -494
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/handoff.ts +182 -0
- package/dist/template/.opencode/tool/lsp.ts +13 -6
- package/package.json +1 -1
- package/dist/template/.opencode/pickle-thinker.jsonc +0 -11
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Handoff Plugin
|
|
3
|
+
* Injects handoff context into session compaction for seamless session transitions
|
|
4
|
+
*
|
|
5
|
+
* Workflow:
|
|
6
|
+
* 1. User runs /handoff command → creates handoff markdown file
|
|
7
|
+
* 2. User presses Ctrl+K to compact session
|
|
8
|
+
* 3. This plugin injects the handoff context into the compaction prompt
|
|
9
|
+
* 4. New session starts with full context from previous session
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
15
|
+
|
|
16
|
+
const HANDOFF_DIRS = [
|
|
17
|
+
".opencode/memory/handoffs",
|
|
18
|
+
".beads/artifacts", // Check for bead-specific handoffs
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
interface HandoffFile {
|
|
22
|
+
path: string;
|
|
23
|
+
mtime: number;
|
|
24
|
+
content: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function findLatestHandoff(baseDir: string): HandoffFile | null {
|
|
28
|
+
const handoffs: HandoffFile[] = [];
|
|
29
|
+
|
|
30
|
+
for (const dir of HANDOFF_DIRS) {
|
|
31
|
+
const fullPath = join(baseDir, dir);
|
|
32
|
+
if (!existsSync(fullPath)) continue;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const files = findMarkdownFiles(fullPath);
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
try {
|
|
38
|
+
const stat = require("fs").statSync(file);
|
|
39
|
+
const content = readFileSync(file, "utf-8");
|
|
40
|
+
|
|
41
|
+
// Only include files that look like handoffs
|
|
42
|
+
if (
|
|
43
|
+
content.includes("## Resume Instructions") ||
|
|
44
|
+
content.includes("## Progress") ||
|
|
45
|
+
content.includes("# Handoff:")
|
|
46
|
+
) {
|
|
47
|
+
handoffs.push({
|
|
48
|
+
path: file,
|
|
49
|
+
mtime: stat.mtimeMs,
|
|
50
|
+
content,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Skip unreadable files
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Skip inaccessible directories
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (handoffs.length === 0) return null;
|
|
63
|
+
|
|
64
|
+
// Return most recent
|
|
65
|
+
return handoffs.sort((a, b) => b.mtime - a.mtime)[0];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function findMarkdownFiles(dir: string, depth = 3): string[] {
|
|
69
|
+
if (depth <= 0) return [];
|
|
70
|
+
|
|
71
|
+
const results: string[] = [];
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
75
|
+
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const fullPath = join(dir, entry.name);
|
|
78
|
+
|
|
79
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
80
|
+
results.push(...findMarkdownFiles(fullPath, depth - 1));
|
|
81
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
82
|
+
results.push(fullPath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Skip inaccessible directories
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatHandoffForCompaction(handoff: HandoffFile): string {
|
|
93
|
+
// Extract key sections from handoff
|
|
94
|
+
const content = handoff.content;
|
|
95
|
+
|
|
96
|
+
return `## Previous Session Handoff
|
|
97
|
+
|
|
98
|
+
<handoff-context>
|
|
99
|
+
${content}
|
|
100
|
+
</handoff-context>
|
|
101
|
+
|
|
102
|
+
**IMPORTANT**: Resume work from where the previous session left off. Check the "Resume Instructions" section above for specific next steps.`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const HandoffPlugin: Plugin = async ({ client, directory }) => {
|
|
106
|
+
client.app
|
|
107
|
+
.log({
|
|
108
|
+
body: {
|
|
109
|
+
service: "handoff-plugin",
|
|
110
|
+
level: "info",
|
|
111
|
+
message: "🔄 Handoff Plugin loaded - session continuity enabled",
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
.catch(() => {});
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
// Inject handoff context when session is being compacted
|
|
118
|
+
"experimental.session.compacting": async (_input, output) => {
|
|
119
|
+
const handoff = findLatestHandoff(directory);
|
|
120
|
+
|
|
121
|
+
if (!handoff) {
|
|
122
|
+
client.app
|
|
123
|
+
.log({
|
|
124
|
+
body: {
|
|
125
|
+
service: "handoff-plugin",
|
|
126
|
+
level: "debug",
|
|
127
|
+
message: "No handoff found - compacting without handoff context",
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
.catch(() => {});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check if handoff is recent (within last hour)
|
|
135
|
+
const oneHourAgo = Date.now() - 60 * 60 * 1000;
|
|
136
|
+
const isRecent = handoff.mtime > oneHourAgo;
|
|
137
|
+
|
|
138
|
+
if (!isRecent) {
|
|
139
|
+
client.app
|
|
140
|
+
.log({
|
|
141
|
+
body: {
|
|
142
|
+
service: "handoff-plugin",
|
|
143
|
+
level: "debug",
|
|
144
|
+
message: `Handoff found but stale (${new Date(handoff.mtime).toISOString()}) - skipping injection`,
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
.catch(() => {});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Inject handoff context
|
|
152
|
+
const formattedHandoff = formatHandoffForCompaction(handoff);
|
|
153
|
+
output.context.push(formattedHandoff);
|
|
154
|
+
|
|
155
|
+
client.app
|
|
156
|
+
.log({
|
|
157
|
+
body: {
|
|
158
|
+
service: "handoff-plugin",
|
|
159
|
+
level: "info",
|
|
160
|
+
message: `📋 Injected handoff context from: ${handoff.path}`,
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
.catch(() => {});
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Log when compaction completes
|
|
167
|
+
event: async ({ event }) => {
|
|
168
|
+
if (event.type === "session.compacted") {
|
|
169
|
+
client.app
|
|
170
|
+
.log({
|
|
171
|
+
body: {
|
|
172
|
+
service: "handoff-plugin",
|
|
173
|
+
level: "info",
|
|
174
|
+
message:
|
|
175
|
+
"✅ Session compacted with handoff context - ready to continue",
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
.catch(() => {});
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
};
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* Semantic code refactoring using Language Server Protocol
|
|
4
4
|
*
|
|
5
5
|
* Provides: lsp_rename, lsp_code_actions, lsp_code_action_apply, lsp_organize_imports
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* Uses OpenCode's bundled LSP servers when available, falls back to system-installed servers.
|
|
8
|
+
* @see https://opencode.ai/docs/lsp/
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import { resolve } from "node:path";
|
|
@@ -55,11 +57,16 @@ IMPORTANT: This tool directly modifies files. Review the changes shown in output
|
|
|
55
57
|
if (!server) {
|
|
56
58
|
return `Error: No LSP server available for file type: ${absPath}
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
60
|
+
OpenCode supports 30+ languages with built-in LSP servers.
|
|
61
|
+
See https://opencode.ai/docs/lsp/ for the full list.
|
|
62
|
+
|
|
63
|
+
Common servers:
|
|
64
|
+
- TypeScript: Requires 'typescript' in project dependencies
|
|
65
|
+
- Python: Requires 'pyright' dependency installed
|
|
66
|
+
- Go: Requires 'go' command available
|
|
67
|
+
- Rust: Requires 'rust-analyzer' command available
|
|
68
|
+
- Java: Requires Java SDK 21+ installed (OpenCode auto-installs jdtls)
|
|
69
|
+
- C/C++: Requires 'clangd' available (OpenCode auto-installs for C/C++ projects)`;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
try {
|
package/package.json
CHANGED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Ultrathink config for pickle-thinker
|
|
3
|
-
// mode: "lite" keeps the original behavior (prefix user prompts only).
|
|
4
|
-
// mode: "tool" adds an extra user turn after each tool result to force deeper analysis.
|
|
5
|
-
// Note: tool mode increases turns/tokens and may impact subscription limits.
|
|
6
|
-
"enabled": true,
|
|
7
|
-
// "lite" | "tool"
|
|
8
|
-
"mode": "tool",
|
|
9
|
-
// Change the thinking keyword if you like
|
|
10
|
-
"prefix": "Ultrathink: "
|
|
11
|
-
}
|