opencodekit 0.14.5 → 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.
Files changed (94) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +100 -58
  3. package/dist/template/.opencode/.env.example +1 -0
  4. package/dist/template/.opencode/AGENTS.md +13 -24
  5. package/dist/template/.opencode/README.md +8 -119
  6. package/dist/template/.opencode/agent/explore.md +2 -3
  7. package/dist/template/.opencode/agent/general.md +56 -0
  8. package/dist/template/.opencode/agent/plan.md +54 -0
  9. package/dist/template/.opencode/agent/scout.md +15 -5
  10. package/dist/template/.opencode/command/analyze-project.md +2 -2
  11. package/dist/template/.opencode/command/brainstorm.md +1 -1
  12. package/dist/template/.opencode/command/design-audit.md +4 -5
  13. package/dist/template/.opencode/command/design.md +4 -13
  14. package/dist/template/.opencode/command/generate-pattern.md +2 -9
  15. package/dist/template/.opencode/command/implement.md +4 -4
  16. package/dist/template/.opencode/command/init.md +1 -1
  17. package/dist/template/.opencode/command/new-feature.md +2 -3
  18. package/dist/template/.opencode/command/plan.md +1 -1
  19. package/dist/template/.opencode/command/pr.md +0 -1
  20. package/dist/template/.opencode/command/research.md +20 -6
  21. package/dist/template/.opencode/command/restore-image.md +1 -9
  22. package/dist/template/.opencode/command/revert-feature.md +1 -1
  23. package/dist/template/.opencode/command/review-codebase.md +4 -4
  24. package/dist/template/.opencode/command/status.md +1 -2
  25. package/dist/template/.opencode/command/summarize.md +1 -2
  26. package/dist/template/.opencode/command/triage.md +4 -32
  27. package/dist/template/.opencode/dcp.jsonc +68 -68
  28. package/dist/template/.opencode/memory/_templates/README.md +35 -0
  29. package/dist/template/.opencode/memory/_templates/project/architecture.md +60 -0
  30. package/dist/template/.opencode/memory/_templates/project/commands.md +72 -0
  31. package/dist/template/.opencode/memory/_templates/project/conventions.md +68 -0
  32. package/dist/template/.opencode/memory/_templates/project/gotchas.md +41 -0
  33. package/dist/template/.opencode/memory/beads-workflow.md +30 -29
  34. package/dist/template/.opencode/memory/project/architecture.md +31 -50
  35. package/dist/template/.opencode/memory/project/commands.md +41 -22
  36. package/dist/template/.opencode/memory/project/conventions.md +39 -177
  37. package/dist/template/.opencode/memory/project/gotchas.md +21 -177
  38. package/dist/template/.opencode/memory/user.example.md +5 -0
  39. package/dist/template/.opencode/opencode.json +644 -533
  40. package/dist/template/.opencode/package.json +18 -21
  41. package/dist/template/.opencode/plugin/compaction.ts +79 -85
  42. package/dist/template/.opencode/plugin/env-ctx.ts +34 -0
  43. package/dist/template/.opencode/plugin/lib/notify.ts +41 -45
  44. package/dist/template/.opencode/plugin/lsp.ts +197 -200
  45. package/dist/template/.opencode/plugin/memory.ts +14 -112
  46. package/dist/template/.opencode/plugin/package.json +5 -5
  47. package/dist/template/.opencode/plugin/sessions.ts +1 -1
  48. package/dist/template/.opencode/plugin/skill-mcp.ts +486 -521
  49. package/dist/template/.opencode/plugin/truncator.ts +47 -50
  50. package/dist/template/.opencode/plugin/tsconfig.json +14 -14
  51. package/dist/template/.opencode/skill/chrome-devtools/mcp.json +17 -17
  52. package/dist/template/.opencode/skill/condition-based-waiting/SKILL.md +17 -12
  53. package/dist/template/.opencode/skill/condition-based-waiting/example.ts +63 -69
  54. package/dist/template/.opencode/skill/defense-in-depth/SKILL.md +14 -8
  55. package/dist/template/.opencode/skill/dispatching-parallel-agents/SKILL.md +14 -3
  56. package/dist/template/.opencode/skill/playwright/mcp.json +14 -14
  57. package/dist/template/.opencode/skill/receiving-code-review/SKILL.md +21 -8
  58. package/dist/template/.opencode/skill/requesting-code-review/review.md +14 -0
  59. package/dist/template/.opencode/skill/root-cause-tracing/SKILL.md +18 -4
  60. package/dist/template/.opencode/skill/source-code-research/SKILL.md +9 -7
  61. package/dist/template/.opencode/skill/test-driven-development/SKILL.md +49 -32
  62. package/dist/template/.opencode/skill/testing-anti-patterns/SKILL.md +40 -22
  63. package/dist/template/.opencode/skill/testing-skills-with-subagents/SKILL.md +46 -26
  64. package/dist/template/.opencode/skill/tool-priority/SKILL.md +117 -44
  65. package/dist/template/.opencode/skill/v0/SKILL.md +1 -7
  66. package/dist/template/.opencode/skill/verification-before-completion/SKILL.md +27 -19
  67. package/dist/template/.opencode/skill/writing-skills/anthropic-best-practices.md +171 -148
  68. package/dist/template/.opencode/skill/writing-skills/persuasion-principles.md +39 -6
  69. package/dist/template/.opencode/tool/memory-read.ts +44 -56
  70. package/dist/template/.opencode/tool/memory-search.ts +8 -291
  71. package/dist/template/.opencode/tool/memory-update.ts +47 -51
  72. package/dist/template/.opencode/tool/observation.ts +6 -180
  73. package/dist/template/.opencode/tsconfig.json +19 -19
  74. package/package.json +19 -15
  75. package/dist/template/.opencode/.background-tasks.json +0 -114
  76. package/dist/template/.opencode/.ralph-state.json +0 -12
  77. package/dist/template/.opencode/agent/build.md +0 -327
  78. package/dist/template/.opencode/agent/planner.md +0 -281
  79. package/dist/template/.opencode/agent/rush.md +0 -223
  80. package/dist/template/.opencode/memory/handoffs/README.md +0 -83
  81. package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
  82. package/dist/template/.opencode/memory/observations/2026-01-09-pattern-ampcode-mcp-json-includetools-pattern.md +0 -42
  83. package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-0d25ba80-ba3b-4209-9046-b45d6093b4da.txn +0 -0
  84. package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
  85. package/dist/template/.opencode/memory/vector_db/memories.lance/data/1111100101010101011010004a9ef34df6b29f36a9a53a2892.lance +0 -0
  86. package/dist/template/.opencode/tool/ast-grep.ts +0 -245
  87. package/dist/template/.opencode/tool/background.ts +0 -509
  88. package/dist/template/.opencode/tool/bd-inbox.ts +0 -110
  89. package/dist/template/.opencode/tool/bd-msg.ts +0 -62
  90. package/dist/template/.opencode/tool/bd-release.ts +0 -71
  91. package/dist/template/.opencode/tool/bd-reserve.ts +0 -121
  92. package/dist/template/.opencode/tool/memory-embed.ts +0 -183
  93. package/dist/template/.opencode/tool/memory-index.ts +0 -769
  94. 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
- ".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",
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
- // 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,
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
- /\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
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
- filePath: string;
65
- line?: number;
66
- character?: number;
64
+ filePath: string;
65
+ line?: number;
66
+ character?: number;
67
67
  }
68
68
 
69
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);
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
- 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(
115
- /\b(src|lib|app|components?)\/[^\s]+\.(ts|tsx|js|jsx|py|go|rs)/gi,
116
- ) || [];
117
- for (const path of pathPatterns) {
118
- if (!seen.has(path)) {
119
- seen.add(path);
120
- files.push(path);
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
- if (matches.length === 0) return "";
129
-
130
- const commands: string[] = [];
131
-
132
- for (const match of matches) {
133
- if (match.line) {
134
- // With line number - get definition and references
135
- commands.push(
136
- `lsp({ operation: "goToDefinition", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
137
- );
138
- commands.push(
139
- `lsp({ operation: "findReferences", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
140
- );
141
- commands.push(
142
- `lsp({ operation: "hover", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
143
- );
144
- } else {
145
- // Just file path - get document symbols
146
- commands.push(
147
- `lsp({ operation: "documentSymbol", filePath: "${match.filePath}", line: 1, character: 1 })`,
148
- );
149
- }
150
- }
151
-
152
- const uniqueCommands = [...new Set(commands)].slice(0, 6);
153
-
154
- // AGGRESSIVE enforcement format - designed to force action
155
- return `
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
- const lspCommands = files.map(
182
- (f) =>
183
- `lsp({ operation: "documentSymbol", filePath: "${f}", line: 1, character: 1 })`,
184
- );
179
+ const lspCommands = files.map(
180
+ (f) => `lsp({ operation: "documentSymbol", filePath: "${f}", line: 1, character: 1 })`,
181
+ );
185
182
 
186
- return `
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
- return ["grep", "glob", "read", "batch"].includes(toolName);
198
+ return ["grep", "glob", "read", "batch"].includes(toolName);
202
199
  }
203
200
 
204
201
  function hasCodeFiles(output: string): boolean {
205
- for (const ext of LSP_SUPPORTED_EXTENSIONS) {
206
- if (output.includes(ext)) return true;
207
- }
208
- return false;
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
- return CODE_INTENT_PATTERNS.some((pattern) => pattern.test(text));
209
+ return CODE_INTENT_PATTERNS.some((pattern) => pattern.test(text));
213
210
  }
214
211
 
215
212
  export const Lsp: Plugin = async () => {
216
- return {
217
- name: "lsp",
218
- version: "1.1.0",
219
-
220
- /**
221
- * Hook: chat.message
222
- * Injects LSP reminder when user mentions code files in their message
223
- * This catches intent BEFORE tools are executed
224
- */
225
- "chat.message": async (input, output) => {
226
- const { sessionID, messageID } = input;
227
- const { message, parts } = output;
228
-
229
- // Only process user messages
230
- if (message.role !== "user") return;
231
-
232
- // Extract text from all parts
233
- const fullText = parts
234
- .filter((p) => p.type === "text")
235
- .map((p) => ("text" in p ? p.text : ""))
236
- .join(" ");
237
-
238
- // Check if user is mentioning code files or asking about code
239
- if (!userMessageMentionsCode(fullText)) return;
240
-
241
- // Extract specific files mentioned
242
- const files = extractFilesFromUserMessage(fullText);
243
-
244
- // If files found, inject specific LSP commands
245
- // If no specific files but code intent detected, inject general reminder
246
- const nudgeText =
247
- files.length > 0
248
- ? generateUserMessageNudge(files)
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
- // Inject synthetic message part with all required fields
264
- const partId = `lsp-nudge-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
265
- parts.push({
266
- id: partId,
267
- sessionID,
268
- messageID: messageID || "",
269
- type: "text",
270
- text: nudgeText,
271
- synthetic: true,
272
- } as import("@opencode-ai/sdk").Part);
273
- },
274
-
275
- /**
276
- * Hook: tool.execute.after
277
- * Injects LSP commands after grep/glob/read returns code file results
278
- * This catches AFTER tools show code files
279
- */
280
- "tool.execute.after": async (input, output) => {
281
- const { tool: toolName } = input;
282
- const result = output.output;
283
-
284
- // Skip if not a search/read tool
285
- if (!shouldInjectNudge(toolName)) return;
286
-
287
- // Skip if no code files in output
288
- if (typeof result !== "string" || !hasCodeFiles(result)) return;
289
-
290
- // Extract file matches
291
- const matches = extractFileMatches(result);
292
- if (matches.length === 0) return;
293
-
294
- // Generate LSP commands (not suggestions - COMMANDS)
295
- const lspBlock = generateLspCommands(matches);
296
- if (!lspBlock) return;
297
-
298
- // Append mandatory LSP block to tool output
299
- output.output = `${result}${lspBlock}`;
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. Auto-Index Rebuild: Rebuilds vector index when memory files change
7
- * 3. Code-Change Awareness: Flags stale observations when related code changes
8
- * 4. Toast Notifications: Alerts agent when observations need review
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
- function isMemoryFile(filePath: string): boolean {
253
- const normalized = filePath.replace(/\\/g, "/");
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: Index Rebuild
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: { type: string; text?: string }): p is {
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
@@ -1,7 +1,7 @@
1
1
  {
2
- "name": "opencode-plugins",
3
- "type": "module",
4
- "dependencies": {
5
- "@opencode-ai/plugin": "^1.1.8"
6
- }
2
+ "name": "opencode-plugins",
3
+ "type": "module",
4
+ "dependencies": {
5
+ "@opencode-ai/plugin": "^1.1.13"
6
+ }
7
7
  }