opencodekit 0.16.10 → 0.16.13
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/AGENTS.md +85 -32
- package/dist/template/.opencode/agent/build.md +24 -2
- package/dist/template/.opencode/command/handoff.md +37 -0
- package/dist/template/.opencode/command/start.md +6 -0
- package/dist/template/.opencode/command/status.md +33 -7
- package/dist/template/.opencode/dcp.jsonc +79 -79
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +905 -885
- package/dist/template/.opencode/plugin/compaction.ts +52 -51
- package/dist/template/.opencode/plugin/memory.ts +22 -551
- package/dist/template/.opencode/plugin/skill-mcp.ts +514 -486
- package/dist/template/.opencode/plugin/swarm-enforcer.ts +43 -119
- package/dist/template/.opencode/skill/agent-teams/SKILL.md +242 -0
- package/dist/template/.opencode/skill/compaction/SKILL.md +338 -0
- package/dist/template/.opencode/skill/jira/SKILL.md +177 -50
- package/dist/template/.opencode/skill/jira/mcp.json +2 -10
- package/package.json +1 -1
- package/dist/template/.opencode/plugin/env-ctx.ts +0 -34
- package/dist/template/.opencode/plugin/lsp.ts +0 -301
- package/dist/template/.opencode/plugin/truncator.ts +0 -59
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
-
|
|
3
|
-
const session_set = new Set<string>();
|
|
4
|
-
const cwd = process.cwd();
|
|
5
|
-
const env_ctx = `
|
|
6
|
-
<environment_context>
|
|
7
|
-
<cwd>${cwd}</cwd>
|
|
8
|
-
</environment_context>
|
|
9
|
-
`.trim();
|
|
10
|
-
|
|
11
|
-
export const EnvironmentContextPlugin: Plugin = async () => {
|
|
12
|
-
return {
|
|
13
|
-
async "chat.message"(_input, output) {
|
|
14
|
-
const { message } = output;
|
|
15
|
-
const sessionID = message?.sessionID;
|
|
16
|
-
if (!sessionID) return;
|
|
17
|
-
|
|
18
|
-
if (session_set.has(sessionID)) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
output.parts.unshift({
|
|
23
|
-
id: `env-ctx-${Date.now()}`,
|
|
24
|
-
messageID: message.id,
|
|
25
|
-
sessionID,
|
|
26
|
-
type: "text",
|
|
27
|
-
text: env_ctx,
|
|
28
|
-
synthetic: true,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
session_set.add(sessionID);
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
};
|
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LSP Plugin - Active LSP Tool Enforcement
|
|
3
|
-
*
|
|
4
|
-
* Forces agents to actively use LSP tools when code files are detected.
|
|
5
|
-
* This is NOT a suggestion - agents MUST execute LSP operations immediately.
|
|
6
|
-
*
|
|
7
|
-
* Mechanism:
|
|
8
|
-
* 1. Hooks into grep/glob/read tool outputs (tool.execute.after)
|
|
9
|
-
* 2. Hooks into user messages mentioning code files (chat.message)
|
|
10
|
-
* 3. Injects MANDATORY LSP execution commands
|
|
11
|
-
* 4. Uses strong language to override agent tendencies to skip
|
|
12
|
-
*
|
|
13
|
-
* Based on oh-my-opencode's LSP forcing pattern.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { Plugin } from "@opencode-ai/plugin";
|
|
17
|
-
|
|
18
|
-
// File extensions that support LSP
|
|
19
|
-
const LSP_SUPPORTED_EXTENSIONS = new Set([
|
|
20
|
-
".ts",
|
|
21
|
-
".tsx",
|
|
22
|
-
".js",
|
|
23
|
-
".jsx",
|
|
24
|
-
".py",
|
|
25
|
-
".go",
|
|
26
|
-
".rs",
|
|
27
|
-
".java",
|
|
28
|
-
".c",
|
|
29
|
-
".cpp",
|
|
30
|
-
".h",
|
|
31
|
-
".hpp",
|
|
32
|
-
".cs",
|
|
33
|
-
".rb",
|
|
34
|
-
".php",
|
|
35
|
-
".swift",
|
|
36
|
-
".kt",
|
|
37
|
-
".scala",
|
|
38
|
-
".lua",
|
|
39
|
-
".zig",
|
|
40
|
-
".vue",
|
|
41
|
-
".svelte",
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
// Regex to extract file:line patterns from tool output
|
|
45
|
-
const FILE_LINE_PATTERNS = [
|
|
46
|
-
// Standard grep output: path/file.ts:42: content
|
|
47
|
-
/^([^\s:]+\.(ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|hpp|cs|rb|php|swift|kt|scala|lua|zig|vue|svelte)):(\d+):/gm,
|
|
48
|
-
// Just path with line: path/file.ts:42
|
|
49
|
-
/([^\s:]+\.(ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|hpp|cs|rb|php|swift|kt|scala|lua|zig|vue|svelte)):(\d+)/g,
|
|
50
|
-
// Glob/list output with just paths
|
|
51
|
-
/^([^\s:]+\.(ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|hpp|cs|rb|php|swift|kt|scala|lua|zig|vue|svelte))$/gm,
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
// Patterns that indicate user is asking about code
|
|
55
|
-
const CODE_INTENT_PATTERNS = [
|
|
56
|
-
/\b(edit|modify|change|update|fix|refactor|add|remove|delete)\b.*\.(ts|tsx|js|jsx|py|go|rs)/i,
|
|
57
|
-
/\b(function|class|method|variable|type|interface)\s+\w+/i,
|
|
58
|
-
/\b(implement|create|build)\b.*\b(feature|component|module)/i,
|
|
59
|
-
/@[^\s]+\.(ts|tsx|js|jsx|py|go|rs)/i, // @file.ts mentions
|
|
60
|
-
/\b(src|lib|app|components?)\/[^\s]+\.(ts|tsx|js|jsx)/i, // Path patterns
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
interface FileMatch {
|
|
64
|
-
filePath: string;
|
|
65
|
-
line?: number;
|
|
66
|
-
character?: number;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function extractFileMatches(output: string): FileMatch[] {
|
|
70
|
-
const matches: FileMatch[] = [];
|
|
71
|
-
const seen = new Set<string>();
|
|
72
|
-
|
|
73
|
-
for (const pattern of FILE_LINE_PATTERNS) {
|
|
74
|
-
pattern.lastIndex = 0;
|
|
75
|
-
|
|
76
|
-
let match = pattern.exec(output);
|
|
77
|
-
while (match !== null) {
|
|
78
|
-
const filePath = match[1];
|
|
79
|
-
const line = match[3] ? Number.parseInt(match[3], 10) : undefined;
|
|
80
|
-
|
|
81
|
-
const key = `${filePath}:${line || 0}`;
|
|
82
|
-
if (!seen.has(key)) {
|
|
83
|
-
seen.add(key);
|
|
84
|
-
matches.push({
|
|
85
|
-
filePath,
|
|
86
|
-
line,
|
|
87
|
-
character: 1,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
match = pattern.exec(output);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return matches.slice(0, 5);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function extractFilesFromUserMessage(text: string): string[] {
|
|
99
|
-
const files: string[] = [];
|
|
100
|
-
const seen = new Set<string>();
|
|
101
|
-
|
|
102
|
-
// Match @file.ts patterns
|
|
103
|
-
const atMentions = text.match(/@([^\s]+\.(ts|tsx|js|jsx|py|go|rs))/gi) || [];
|
|
104
|
-
for (const mention of atMentions) {
|
|
105
|
-
const file = mention.replace("@", "");
|
|
106
|
-
if (!seen.has(file)) {
|
|
107
|
-
seen.add(file);
|
|
108
|
-
files.push(file);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Match path patterns like src/foo.ts
|
|
113
|
-
const pathPatterns =
|
|
114
|
-
text.match(/\b(src|lib|app|components?)\/[^\s]+\.(ts|tsx|js|jsx|py|go|rs)/gi) || [];
|
|
115
|
-
for (const path of pathPatterns) {
|
|
116
|
-
if (!seen.has(path)) {
|
|
117
|
-
seen.add(path);
|
|
118
|
-
files.push(path);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return files.slice(0, 3);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function generateLspCommands(matches: FileMatch[]): string {
|
|
126
|
-
if (matches.length === 0) return "";
|
|
127
|
-
|
|
128
|
-
const commands: string[] = [];
|
|
129
|
-
|
|
130
|
-
for (const match of matches) {
|
|
131
|
-
if (match.line) {
|
|
132
|
-
// With line number - get definition and references
|
|
133
|
-
commands.push(
|
|
134
|
-
`lsp({ operation: "goToDefinition", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
|
|
135
|
-
);
|
|
136
|
-
commands.push(
|
|
137
|
-
`lsp({ operation: "findReferences", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
|
|
138
|
-
);
|
|
139
|
-
commands.push(
|
|
140
|
-
`lsp({ operation: "hover", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
|
|
141
|
-
);
|
|
142
|
-
} else {
|
|
143
|
-
// Just file path - get document symbols
|
|
144
|
-
commands.push(
|
|
145
|
-
`lsp({ operation: "documentSymbol", filePath: "${match.filePath}", line: 1, character: 1 })`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const uniqueCommands = [...new Set(commands)].slice(0, 6);
|
|
151
|
-
|
|
152
|
-
// AGGRESSIVE enforcement format - designed to force action
|
|
153
|
-
return `
|
|
154
|
-
|
|
155
|
-
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
156
|
-
║ ⚡ LSP EXECUTION REQUIRED - DO NOT SKIP ║
|
|
157
|
-
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
158
|
-
║ ║
|
|
159
|
-
║ Code files detected. You MUST execute these LSP operations NOW. ║
|
|
160
|
-
║ DO NOT proceed with edits until LSP context is retrieved. ║
|
|
161
|
-
║ ║
|
|
162
|
-
║ EXECUTE IMMEDIATELY (copy-paste ready): ║
|
|
163
|
-
║ ║
|
|
164
|
-
${uniqueCommands.map((cmd) => `║ → ${cmd}`).join("\n")}
|
|
165
|
-
║ ║
|
|
166
|
-
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
167
|
-
║ WHY THIS IS MANDATORY: ║
|
|
168
|
-
║ • grep/read show text, LSP shows semantic meaning ║
|
|
169
|
-
║ • LSP provides accurate type info, definitions, call hierarchies ║
|
|
170
|
-
║ • Editing without LSP context leads to broken code ║
|
|
171
|
-
║ • This is a HARD RULE, not a suggestion ║
|
|
172
|
-
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
173
|
-
║ WORKFLOW: grep/read → LSP → understand → THEN edit ║
|
|
174
|
-
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
175
|
-
`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function generateUserMessageNudge(files: string[]): string {
|
|
179
|
-
const lspCommands = files.map(
|
|
180
|
-
(f) => `lsp({ operation: "documentSymbol", filePath: "${f}", line: 1, character: 1 })`,
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
return `
|
|
184
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
185
|
-
⚡ [LSP CONTEXT REQUIRED]
|
|
186
|
-
|
|
187
|
-
Before making ANY changes to these files, you MUST first retrieve LSP context:
|
|
188
|
-
|
|
189
|
-
${lspCommands.map((cmd) => `→ ${cmd}`).join("\n")}
|
|
190
|
-
|
|
191
|
-
This is a HARD RULE. Do NOT guess about code structure.
|
|
192
|
-
Workflow: READ file → LSP documentSymbol/hover → UNDERSTAND → THEN edit
|
|
193
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
194
|
-
`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function shouldInjectNudge(toolName: string): boolean {
|
|
198
|
-
return ["grep", "glob", "read", "batch"].includes(toolName);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function hasCodeFiles(output: string): boolean {
|
|
202
|
-
for (const ext of LSP_SUPPORTED_EXTENSIONS) {
|
|
203
|
-
if (output.includes(ext)) return true;
|
|
204
|
-
}
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function userMessageMentionsCode(text: string): boolean {
|
|
209
|
-
return CODE_INTENT_PATTERNS.some((pattern) => pattern.test(text));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export const Lsp: Plugin = async () => {
|
|
213
|
-
return {
|
|
214
|
-
name: "lsp",
|
|
215
|
-
version: "1.1.0",
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Hook: chat.message
|
|
219
|
-
* Injects LSP reminder when user mentions code files in their message
|
|
220
|
-
* This catches intent BEFORE tools are executed
|
|
221
|
-
*/
|
|
222
|
-
"chat.message": async (input, output) => {
|
|
223
|
-
const { sessionID, messageID } = input;
|
|
224
|
-
const { message, parts } = output;
|
|
225
|
-
|
|
226
|
-
// Only process user messages
|
|
227
|
-
if (message.role !== "user") return;
|
|
228
|
-
|
|
229
|
-
// Extract text from all parts
|
|
230
|
-
const fullText = parts
|
|
231
|
-
.filter((p) => p.type === "text")
|
|
232
|
-
.map((p) => ("text" in p ? p.text : ""))
|
|
233
|
-
.join(" ");
|
|
234
|
-
|
|
235
|
-
// Check if user is mentioning code files or asking about code
|
|
236
|
-
if (!userMessageMentionsCode(fullText)) return;
|
|
237
|
-
|
|
238
|
-
// Extract specific files mentioned
|
|
239
|
-
const files = extractFilesFromUserMessage(fullText);
|
|
240
|
-
|
|
241
|
-
// If files found, inject specific LSP commands
|
|
242
|
-
// If no specific files but code intent detected, inject general reminder
|
|
243
|
-
const nudgeText =
|
|
244
|
-
files.length > 0
|
|
245
|
-
? generateUserMessageNudge(files)
|
|
246
|
-
: `
|
|
247
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
248
|
-
⚡ [LSP FIRST - HARD RULE]
|
|
249
|
-
|
|
250
|
-
Code modification detected. Before editing:
|
|
251
|
-
1. Use grep/glob to find relevant files
|
|
252
|
-
2. Use READ to view the file
|
|
253
|
-
3. Use LSP (documentSymbol, goToDefinition, findReferences) to understand structure
|
|
254
|
-
4. ONLY THEN make edits
|
|
255
|
-
|
|
256
|
-
Do NOT skip LSP. Editing without semantic context leads to broken code.
|
|
257
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
258
|
-
`;
|
|
259
|
-
|
|
260
|
-
// Inject synthetic message part with all required fields
|
|
261
|
-
const partId = `lsp-nudge-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
262
|
-
parts.push({
|
|
263
|
-
id: partId,
|
|
264
|
-
sessionID,
|
|
265
|
-
messageID: messageID || "",
|
|
266
|
-
type: "text",
|
|
267
|
-
text: nudgeText,
|
|
268
|
-
synthetic: true,
|
|
269
|
-
} as import("@opencode-ai/sdk").Part);
|
|
270
|
-
},
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Hook: tool.execute.after
|
|
274
|
-
* Injects LSP commands after grep/glob/read returns code file results
|
|
275
|
-
* This catches AFTER tools show code files
|
|
276
|
-
*/
|
|
277
|
-
"tool.execute.after": async (input, output) => {
|
|
278
|
-
const { tool: toolName } = input;
|
|
279
|
-
const result = output.output;
|
|
280
|
-
|
|
281
|
-
// Skip if not a search/read tool
|
|
282
|
-
if (!shouldInjectNudge(toolName)) return;
|
|
283
|
-
|
|
284
|
-
// Skip if no code files in output
|
|
285
|
-
if (typeof result !== "string" || !hasCodeFiles(result)) return;
|
|
286
|
-
|
|
287
|
-
// Extract file matches
|
|
288
|
-
const matches = extractFileMatches(result);
|
|
289
|
-
if (matches.length === 0) return;
|
|
290
|
-
|
|
291
|
-
// Generate LSP commands (not suggestions - COMMANDS)
|
|
292
|
-
const lspBlock = generateLspCommands(matches);
|
|
293
|
-
if (!lspBlock) return;
|
|
294
|
-
|
|
295
|
-
// Append mandatory LSP block to tool output
|
|
296
|
-
output.output = `${result}${lspBlock}`;
|
|
297
|
-
},
|
|
298
|
-
};
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
export default Lsp;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenCode Truncator Plugin
|
|
3
|
-
* Warns when tools return large outputs under context pressure
|
|
4
|
-
*
|
|
5
|
-
* Note: This doesn't actually truncate - OpenCode handles that via compaction.prune.
|
|
6
|
-
* This only adds WARNINGS that OpenCode doesn't provide.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Plugin } from "@opencode-ai/plugin";
|
|
10
|
-
|
|
11
|
-
export const TruncatorPlugin: Plugin = async ({ client }) => {
|
|
12
|
-
const sessionContext = new Map<string, number>();
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
event: async ({ event }) => {
|
|
16
|
-
const props = event.properties as Record<string, unknown>;
|
|
17
|
-
|
|
18
|
-
if (event.type === "session.updated") {
|
|
19
|
-
const info = props?.info as Record<string, unknown> | undefined;
|
|
20
|
-
const tokenStats = (info?.tokens || props?.tokens) as
|
|
21
|
-
| { used: number; limit: number }
|
|
22
|
-
| undefined;
|
|
23
|
-
const sessionId = (info?.id || props?.sessionID) as string | undefined;
|
|
24
|
-
|
|
25
|
-
if (sessionId && tokenStats?.used && tokenStats?.limit) {
|
|
26
|
-
sessionContext.set(sessionId, Math.round((tokenStats.used / tokenStats.limit) * 100));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (event.type === "session.deleted") {
|
|
31
|
-
const sessionId = props?.sessionID as string | undefined;
|
|
32
|
-
if (sessionId) {
|
|
33
|
-
sessionContext.delete(sessionId);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
"tool.execute.after": async (input, output) => {
|
|
39
|
-
const pct = sessionContext.get(input.sessionID) || 0;
|
|
40
|
-
if (pct < 70) return; // Only warn under pressure
|
|
41
|
-
|
|
42
|
-
// Thresholds get tighter as context fills up
|
|
43
|
-
const threshold = pct >= 95 ? 5000 : pct >= 85 ? 10000 : 20000;
|
|
44
|
-
const outputStr = output.output || "";
|
|
45
|
-
|
|
46
|
-
if (outputStr.length > threshold) {
|
|
47
|
-
await client.app
|
|
48
|
-
.log({
|
|
49
|
-
body: {
|
|
50
|
-
service: "truncator",
|
|
51
|
-
level: pct >= 95 ? "warn" : "info",
|
|
52
|
-
message: `Large output from ${input.tool}: ${outputStr.length} chars (threshold: ${threshold}, context: ${pct}%)`,
|
|
53
|
-
},
|
|
54
|
-
})
|
|
55
|
-
.catch(() => {});
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
};
|