opencodekit 0.14.6 → 0.15.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/README.md +2 -2
- package/dist/index.js +100 -58
- package/dist/template/.opencode/.env.example +1 -0
- package/dist/template/.opencode/AGENTS.md +13 -24
- package/dist/template/.opencode/README.md +8 -119
- package/dist/template/.opencode/agent/explore.md +2 -3
- package/dist/template/.opencode/agent/general.md +56 -0
- package/dist/template/.opencode/agent/plan.md +54 -0
- package/dist/template/.opencode/agent/scout.md +15 -5
- package/dist/template/.opencode/command/analyze-project.md +2 -2
- package/dist/template/.opencode/command/brainstorm.md +1 -1
- package/dist/template/.opencode/command/design-audit.md +4 -5
- package/dist/template/.opencode/command/design.md +4 -13
- package/dist/template/.opencode/command/generate-pattern.md +2 -9
- package/dist/template/.opencode/command/implement.md +4 -4
- package/dist/template/.opencode/command/init.md +1 -1
- package/dist/template/.opencode/command/new-feature.md +2 -3
- package/dist/template/.opencode/command/plan.md +1 -1
- package/dist/template/.opencode/command/pr.md +0 -1
- package/dist/template/.opencode/command/research.md +20 -6
- package/dist/template/.opencode/command/restore-image.md +1 -9
- package/dist/template/.opencode/command/revert-feature.md +1 -1
- package/dist/template/.opencode/command/review-codebase.md +4 -4
- package/dist/template/.opencode/command/status.md +1 -2
- package/dist/template/.opencode/command/summarize.md +1 -2
- package/dist/template/.opencode/command/triage.md +4 -32
- package/dist/template/.opencode/dcp.jsonc +68 -68
- package/dist/template/.opencode/memory/_templates/README.md +35 -0
- package/dist/template/.opencode/memory/_templates/project/architecture.md +60 -0
- package/dist/template/.opencode/memory/_templates/project/commands.md +72 -0
- package/dist/template/.opencode/memory/_templates/project/conventions.md +68 -0
- package/dist/template/.opencode/memory/_templates/project/gotchas.md +41 -0
- package/dist/template/.opencode/memory/beads-workflow.md +30 -29
- package/dist/template/.opencode/memory/project/architecture.md +31 -50
- package/dist/template/.opencode/memory/project/commands.md +41 -22
- package/dist/template/.opencode/memory/project/conventions.md +39 -177
- package/dist/template/.opencode/memory/project/gotchas.md +21 -177
- package/dist/template/.opencode/memory/user.example.md +5 -0
- package/dist/template/.opencode/opencode.json +644 -579
- package/dist/template/.opencode/package.json +18 -21
- package/dist/template/.opencode/plugin/compaction.ts +79 -85
- package/dist/template/.opencode/plugin/env-ctx.ts +19 -19
- package/dist/template/.opencode/plugin/lib/notify.ts +41 -45
- package/dist/template/.opencode/plugin/lsp.ts +197 -200
- package/dist/template/.opencode/plugin/memory.ts +14 -112
- package/dist/template/.opencode/plugin/package.json +5 -5
- package/dist/template/.opencode/plugin/sessions.ts +1 -1
- package/dist/template/.opencode/plugin/skill-mcp.ts +486 -521
- package/dist/template/.opencode/plugin/truncator.ts +47 -50
- package/dist/template/.opencode/plugin/tsconfig.json +14 -14
- package/dist/template/.opencode/skill/chrome-devtools/mcp.json +17 -17
- package/dist/template/.opencode/skill/condition-based-waiting/SKILL.md +17 -12
- package/dist/template/.opencode/skill/condition-based-waiting/example.ts +63 -69
- package/dist/template/.opencode/skill/defense-in-depth/SKILL.md +14 -8
- package/dist/template/.opencode/skill/dispatching-parallel-agents/SKILL.md +14 -3
- package/dist/template/.opencode/skill/playwright/mcp.json +14 -14
- package/dist/template/.opencode/skill/receiving-code-review/SKILL.md +21 -8
- package/dist/template/.opencode/skill/requesting-code-review/review.md +14 -0
- package/dist/template/.opencode/skill/root-cause-tracing/SKILL.md +18 -4
- package/dist/template/.opencode/skill/source-code-research/SKILL.md +9 -7
- package/dist/template/.opencode/skill/test-driven-development/SKILL.md +49 -32
- package/dist/template/.opencode/skill/testing-anti-patterns/SKILL.md +40 -22
- package/dist/template/.opencode/skill/testing-skills-with-subagents/SKILL.md +46 -26
- package/dist/template/.opencode/skill/tool-priority/SKILL.md +117 -44
- package/dist/template/.opencode/skill/v0/SKILL.md +1 -7
- package/dist/template/.opencode/skill/verification-before-completion/SKILL.md +27 -19
- package/dist/template/.opencode/skill/writing-skills/anthropic-best-practices.md +171 -148
- package/dist/template/.opencode/skill/writing-skills/persuasion-principles.md +39 -6
- package/dist/template/.opencode/tool/memory-read.ts +44 -56
- package/dist/template/.opencode/tool/memory-search.ts +8 -291
- package/dist/template/.opencode/tool/memory-update.ts +47 -51
- package/dist/template/.opencode/tool/observation.ts +6 -180
- package/dist/template/.opencode/tsconfig.json +19 -19
- package/package.json +19 -15
- package/dist/template/.opencode/.background-tasks.json +0 -114
- package/dist/template/.opencode/.ralph-state.json +0 -12
- package/dist/template/.opencode/agent/build.md +0 -327
- package/dist/template/.opencode/agent/ninja.md +0 -351
- package/dist/template/.opencode/agent/planner.md +0 -281
- package/dist/template/.opencode/agent/rush.md +0 -223
- package/dist/template/.opencode/memory/handoffs/README.md +0 -83
- package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
- package/dist/template/.opencode/memory/observations/2026-01-09-pattern-ampcode-mcp-json-includetools-pattern.md +0 -42
- package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-0d25ba80-ba3b-4209-9046-b45d6093b4da.txn +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/data/1111100101010101011010004a9ef34df6b29f36a9a53a2892.lance +0 -0
- package/dist/template/.opencode/tool/ast-grep.ts +0 -245
- package/dist/template/.opencode/tool/background.ts +0 -509
- package/dist/template/.opencode/tool/bd-inbox.ts +0 -110
- package/dist/template/.opencode/tool/bd-msg.ts +0 -62
- package/dist/template/.opencode/tool/bd-release.ts +0 -71
- package/dist/template/.opencode/tool/bd-reserve.ts +0 -121
- package/dist/template/.opencode/tool/memory-embed.ts +0 -183
- package/dist/template/.opencode/tool/memory-index.ts +0 -769
- package/dist/template/.opencode/tool/repo-map.ts +0 -451
|
@@ -17,142 +17,140 @@ import type { Plugin } from "@opencode-ai/plugin";
|
|
|
17
17
|
|
|
18
18
|
// File extensions that support LSP
|
|
19
19
|
const LSP_SUPPORTED_EXTENSIONS = new Set([
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
42
|
]);
|
|
43
43
|
|
|
44
44
|
// Regex to extract file:line patterns from tool output
|
|
45
45
|
const FILE_LINE_PATTERNS = [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
52
|
];
|
|
53
53
|
|
|
54
54
|
// Patterns that indicate user is asking about code
|
|
55
55
|
const CODE_INTENT_PATTERNS = [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
61
|
];
|
|
62
62
|
|
|
63
63
|
interface FileMatch {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
filePath: string;
|
|
65
|
+
line?: number;
|
|
66
|
+
character?: number;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function extractFileMatches(output: string): FileMatch[] {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
96
|
}
|
|
97
97
|
|
|
98
98
|
function extractFilesFromUserMessage(text: string): string[] {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return files.slice(0, 3);
|
|
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);
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
function generateLspCommands(matches: FileMatch[]): string {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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 `
|
|
156
154
|
|
|
157
155
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
158
156
|
║ ⚡ LSP EXECUTION REQUIRED - DO NOT SKIP ║
|
|
@@ -178,12 +176,11 @@ ${uniqueCommands.map((cmd) => `║ → ${cmd}`).join("\n")}
|
|
|
178
176
|
}
|
|
179
177
|
|
|
180
178
|
function generateUserMessageNudge(files: string[]): string {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
);
|
|
179
|
+
const lspCommands = files.map(
|
|
180
|
+
(f) => `lsp({ operation: "documentSymbol", filePath: "${f}", line: 1, character: 1 })`,
|
|
181
|
+
);
|
|
185
182
|
|
|
186
|
-
|
|
183
|
+
return `
|
|
187
184
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
188
185
|
⚡ [LSP CONTEXT REQUIRED]
|
|
189
186
|
|
|
@@ -198,55 +195,55 @@ Workflow: READ file → LSP documentSymbol/hover → UNDERSTAND → THEN edit
|
|
|
198
195
|
}
|
|
199
196
|
|
|
200
197
|
function shouldInjectNudge(toolName: string): boolean {
|
|
201
|
-
|
|
198
|
+
return ["grep", "glob", "read", "batch"].includes(toolName);
|
|
202
199
|
}
|
|
203
200
|
|
|
204
201
|
function hasCodeFiles(output: string): boolean {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
for (const ext of LSP_SUPPORTED_EXTENSIONS) {
|
|
203
|
+
if (output.includes(ext)) return true;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
209
206
|
}
|
|
210
207
|
|
|
211
208
|
function userMessageMentionsCode(text: string): boolean {
|
|
212
|
-
|
|
209
|
+
return CODE_INTENT_PATTERNS.some((pattern) => pattern.test(text));
|
|
213
210
|
}
|
|
214
211
|
|
|
215
212
|
export const Lsp: Plugin = async () => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
+
: `
|
|
250
247
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
251
248
|
⚡ [LSP FIRST - HARD RULE]
|
|
252
249
|
|
|
@@ -260,45 +257,45 @@ Do NOT skip LSP. Editing without semantic context leads to broken code.
|
|
|
260
257
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
261
258
|
`;
|
|
262
259
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
+
};
|
|
302
299
|
};
|
|
303
300
|
|
|
304
301
|
export default Lsp;
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Features:
|
|
5
5
|
* 1. Keyword Detection: Detects "remember", "save this", etc. and nudges agent
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
9
|
-
* 5. Session Idle Hook: Prompts memory summary at session end
|
|
6
|
+
* 2. Code-Change Awareness: Flags stale observations when related code changes
|
|
7
|
+
* 3. Toast Notifications: Alerts agent when observations need review
|
|
8
|
+
* 4. Session Idle Hook: Prompts memory summary at session end
|
|
10
9
|
*
|
|
11
10
|
* Uses OpenCode's official event system (v1.1.2+):
|
|
12
11
|
* - file.edited: When OpenCode edits files
|
|
@@ -17,7 +16,6 @@
|
|
|
17
16
|
* Inspired by: Supermemory, Nia, Graphiti, GKG
|
|
18
17
|
*/
|
|
19
18
|
|
|
20
|
-
import fs from "node:fs";
|
|
21
19
|
import fsPromises from "node:fs/promises";
|
|
22
20
|
import path from "node:path";
|
|
23
21
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
@@ -27,7 +25,6 @@ import type { Plugin } from "@opencode-ai/plugin";
|
|
|
27
25
|
// ============================================================================
|
|
28
26
|
|
|
29
27
|
const MEMORY_DIR = ".opencode/memory";
|
|
30
|
-
const BEADS_DIR = ".beads/artifacts";
|
|
31
28
|
const SRC_DIR = "src";
|
|
32
29
|
// All extensions supported by OpenCode LSP (https://opencode.ai/docs/lsp/)
|
|
33
30
|
const CODE_EXTENSIONS = [
|
|
@@ -106,7 +103,6 @@ const CODE_EXTENSIONS = [
|
|
|
106
103
|
".yaml",
|
|
107
104
|
".yml",
|
|
108
105
|
];
|
|
109
|
-
const DEBOUNCE_MS = 30000; // 30 seconds
|
|
110
106
|
const CODE_CHANGE_DEBOUNCE_MS = 10000; // 10 seconds for toast responsiveness
|
|
111
107
|
|
|
112
108
|
// Default trigger patterns (case-insensitive)
|
|
@@ -194,12 +190,6 @@ interface MemoryConfig {
|
|
|
194
190
|
sessionSummaryEnabled?: boolean;
|
|
195
191
|
}
|
|
196
192
|
|
|
197
|
-
interface PendingChange {
|
|
198
|
-
file: string;
|
|
199
|
-
type: "code" | "memory";
|
|
200
|
-
timestamp: number;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
193
|
// ============================================================================
|
|
204
194
|
// Helpers
|
|
205
195
|
// ============================================================================
|
|
@@ -249,34 +239,13 @@ function isCodeFile(filePath: string): boolean {
|
|
|
249
239
|
return CODE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
|
|
250
240
|
}
|
|
251
241
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return (
|
|
255
|
-
normalized.includes(MEMORY_DIR) &&
|
|
256
|
-
filePath.endsWith(".md") &&
|
|
257
|
-
!normalized.includes("vector_db")
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Dynamic import for indexer
|
|
262
|
-
async function getIndexer() {
|
|
263
|
-
const toolPath = path.join(process.cwd(), ".opencode/tool/memory-index.ts");
|
|
264
|
-
try {
|
|
265
|
-
const module = await import(toolPath);
|
|
266
|
-
return module.indexMemoryFiles as (
|
|
267
|
-
memoryDir: string,
|
|
268
|
-
beadsDir: string,
|
|
269
|
-
) => Promise<{ indexed: number; skipped: number; errors: string[] }>;
|
|
270
|
-
} catch {
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// Plugin
|
|
275
244
|
// ============================================================================
|
|
276
245
|
// Plugin
|
|
277
246
|
// ============================================================================
|
|
278
247
|
|
|
279
|
-
export const MemoryPlugin: Plugin = async ({ client, $ }) => {
|
|
248
|
+
export const MemoryPlugin: Plugin = async ({ client, $: _$ }) => {
|
|
280
249
|
const config = await loadConfig();
|
|
281
250
|
|
|
282
251
|
if (config.enabled === false) {
|
|
@@ -332,52 +301,7 @@ export const MemoryPlugin: Plugin = async ({ client, $ }) => {
|
|
|
332
301
|
const processedMessages = new Set<string>();
|
|
333
302
|
|
|
334
303
|
// -------------------------------------------------------------------------
|
|
335
|
-
// Part 2:
|
|
336
|
-
// -------------------------------------------------------------------------
|
|
337
|
-
|
|
338
|
-
let rebuildTimer: ReturnType<typeof setTimeout> | null = null;
|
|
339
|
-
let pendingRebuild = false;
|
|
340
|
-
|
|
341
|
-
const triggerRebuild = async () => {
|
|
342
|
-
if (pendingRebuild) return;
|
|
343
|
-
pendingRebuild = true;
|
|
344
|
-
|
|
345
|
-
try {
|
|
346
|
-
const memoryDir = path.join(process.cwd(), MEMORY_DIR);
|
|
347
|
-
const beadsDir = path.join(process.cwd(), BEADS_DIR);
|
|
348
|
-
|
|
349
|
-
const indexMemoryFiles = await getIndexer();
|
|
350
|
-
if (!indexMemoryFiles) {
|
|
351
|
-
await log("Failed to load indexer - skipping rebuild", "warn");
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
await log("Rebuilding vector index...");
|
|
356
|
-
const result = await indexMemoryFiles(memoryDir, beadsDir);
|
|
357
|
-
|
|
358
|
-
await log(
|
|
359
|
-
`Vector index rebuilt: ${result.indexed} indexed, ${result.skipped} skipped`,
|
|
360
|
-
);
|
|
361
|
-
} catch (err) {
|
|
362
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
363
|
-
await log(`Vector index rebuild failed: ${msg}`, "warn");
|
|
364
|
-
} finally {
|
|
365
|
-
pendingRebuild = false;
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
const scheduleRebuild = () => {
|
|
370
|
-
if (rebuildTimer) {
|
|
371
|
-
clearTimeout(rebuildTimer);
|
|
372
|
-
}
|
|
373
|
-
rebuildTimer = setTimeout(() => {
|
|
374
|
-
rebuildTimer = null;
|
|
375
|
-
triggerRebuild();
|
|
376
|
-
}, DEBOUNCE_MS);
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
// -------------------------------------------------------------------------
|
|
380
|
-
// Part 3: Code-Change Awareness with Toast Notifications
|
|
304
|
+
// Part 2: Code-Change Awareness with Toast Notifications
|
|
381
305
|
// -------------------------------------------------------------------------
|
|
382
306
|
|
|
383
307
|
let codeChangeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -471,26 +395,13 @@ export const MemoryPlugin: Plugin = async ({ client, $ }) => {
|
|
|
471
395
|
// Part 4: Session Idle Summary
|
|
472
396
|
// -------------------------------------------------------------------------
|
|
473
397
|
|
|
474
|
-
const SESSION_SUMMARY_NUDGE = `
|
|
475
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
476
|
-
[SESSION ENDING - MEMORY CHECK]
|
|
477
|
-
|
|
478
|
-
Before this session ends, consider:
|
|
479
|
-
|
|
480
|
-
1. **Any key learnings** from this session worth remembering?
|
|
481
|
-
2. **Decisions made** that should be documented?
|
|
482
|
-
3. **Patterns discovered** that could help future sessions?
|
|
483
|
-
|
|
484
|
-
Use \`observation\` tool to save important insights, or skip if nothing notable.
|
|
485
|
-
|
|
486
|
-
Quick check: \`memory-search\` to see if related knowledge already exists.
|
|
487
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
488
|
-
`;
|
|
489
|
-
|
|
490
398
|
// -------------------------------------------------------------------------
|
|
491
399
|
// Return Event Hooks (Official OpenCode Event System)
|
|
492
400
|
// -------------------------------------------------------------------------
|
|
493
401
|
|
|
402
|
+
// Return Event Hooks (Official OpenCode Event System)
|
|
403
|
+
// -------------------------------------------------------------------------
|
|
404
|
+
|
|
494
405
|
return {
|
|
495
406
|
// Hook: Detect memory trigger keywords in user messages
|
|
496
407
|
"message.updated": async ({ event }) => {
|
|
@@ -503,7 +414,10 @@ Quick check: \`memory-search\` to see if related knowledge already exists.
|
|
|
503
414
|
|
|
504
415
|
// Get text content
|
|
505
416
|
const textParts = parts.filter(
|
|
506
|
-
(p: {
|
|
417
|
+
(p: {
|
|
418
|
+
type: string;
|
|
419
|
+
text?: string;
|
|
420
|
+
}): p is {
|
|
507
421
|
type: "text";
|
|
508
422
|
text: string;
|
|
509
423
|
} => p.type === "text",
|
|
@@ -550,12 +464,6 @@ Quick check: \`memory-search\` to see if related knowledge already exists.
|
|
|
550
464
|
await log(`Code edited by OpenCode: ${filePath}`);
|
|
551
465
|
handleCodeChange(absolutePath);
|
|
552
466
|
}
|
|
553
|
-
|
|
554
|
-
// Check if it's a memory file
|
|
555
|
-
if (isMemoryFile(absolutePath)) {
|
|
556
|
-
await log(`Memory file edited: ${filePath}`);
|
|
557
|
-
scheduleRebuild();
|
|
558
|
-
}
|
|
559
467
|
},
|
|
560
468
|
|
|
561
469
|
// Hook: file.watcher.updated - External file changes
|
|
@@ -581,12 +489,6 @@ Quick check: \`memory-search\` to see if related knowledge already exists.
|
|
|
581
489
|
await log(`External code change: ${filePath}`);
|
|
582
490
|
handleCodeChange(absolutePath);
|
|
583
491
|
}
|
|
584
|
-
|
|
585
|
-
// Memory file changes
|
|
586
|
-
if (isMemoryFile(absolutePath)) {
|
|
587
|
-
await log(`External memory change: ${filePath}`);
|
|
588
|
-
scheduleRebuild();
|
|
589
|
-
}
|
|
590
492
|
},
|
|
591
493
|
|
|
592
494
|
// Hook: session.idle - Session completed
|