opencodekit 0.13.2 → 0.14.1

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 (49) hide show
  1. package/dist/index.js +50 -3
  2. package/dist/template/.opencode/AGENTS.md +51 -7
  3. package/dist/template/.opencode/README.md +98 -2
  4. package/dist/template/.opencode/agent/build.md +44 -1
  5. package/dist/template/.opencode/agent/explore.md +1 -0
  6. package/dist/template/.opencode/agent/planner.md +40 -1
  7. package/dist/template/.opencode/agent/review.md +1 -0
  8. package/dist/template/.opencode/agent/rush.md +35 -0
  9. package/dist/template/.opencode/agent/scout.md +1 -0
  10. package/dist/template/.opencode/command/brainstorm.md +83 -5
  11. package/dist/template/.opencode/command/finish.md +39 -12
  12. package/dist/template/.opencode/command/fix.md +24 -15
  13. package/dist/template/.opencode/command/handoff.md +17 -0
  14. package/dist/template/.opencode/command/implement.md +81 -18
  15. package/dist/template/.opencode/command/import-plan.md +30 -8
  16. package/dist/template/.opencode/command/new-feature.md +37 -4
  17. package/dist/template/.opencode/command/plan.md +51 -1
  18. package/dist/template/.opencode/command/pr.md +25 -15
  19. package/dist/template/.opencode/command/research.md +61 -5
  20. package/dist/template/.opencode/command/resume.md +31 -0
  21. package/dist/template/.opencode/command/revert-feature.md +15 -3
  22. package/dist/template/.opencode/command/skill-optimize.md +71 -7
  23. package/dist/template/.opencode/command/start.md +81 -5
  24. package/dist/template/.opencode/command/triage.md +16 -1
  25. package/dist/template/.opencode/dcp.jsonc +11 -7
  26. package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
  27. package/dist/template/.opencode/memory/observations/2026-01-09-pattern-ampcode-mcp-json-includetools-pattern.md +42 -0
  28. package/dist/template/.opencode/memory/project/conventions.md +31 -0
  29. package/dist/template/.opencode/memory/project/gotchas.md +52 -5
  30. package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-0d25ba80-ba3b-4209-9046-b45d6093b4da.txn +0 -0
  31. package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
  32. package/dist/template/.opencode/memory/vector_db/memories.lance/data/1111100101010101011010004a9ef34df6b29f36a9a53a2892.lance +0 -0
  33. package/dist/template/.opencode/opencode.json +5 -3
  34. package/dist/template/.opencode/package.json +3 -1
  35. package/dist/template/.opencode/plugin/memory.ts +686 -0
  36. package/dist/template/.opencode/plugin/package.json +1 -1
  37. package/dist/template/.opencode/plugin/skill-mcp.ts +155 -36
  38. package/dist/template/.opencode/skill/chrome-devtools/SKILL.md +43 -65
  39. package/dist/template/.opencode/skill/chrome-devtools/mcp.json +19 -0
  40. package/dist/template/.opencode/skill/executing-plans/SKILL.md +32 -2
  41. package/dist/template/.opencode/skill/finishing-a-development-branch/SKILL.md +42 -17
  42. package/dist/template/.opencode/skill/playwright/SKILL.md +58 -133
  43. package/dist/template/.opencode/skill/playwright/mcp.json +16 -0
  44. package/dist/template/.opencode/tool/memory-embed.ts +183 -0
  45. package/dist/template/.opencode/tool/memory-index.ts +769 -0
  46. package/dist/template/.opencode/tool/memory-search.ts +358 -66
  47. package/dist/template/.opencode/tool/observation.ts +301 -12
  48. package/dist/template/.opencode/tool/repo-map.ts +451 -0
  49. package/package.json +1 -1
@@ -1,6 +1,9 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import * as lancedb from "@lancedb/lancedb";
3
4
  import { tool } from "@opencode-ai/plugin";
5
+ import { generateEmbedding } from "./memory-embed";
6
+ import { searchVectorStore } from "./memory-index";
4
7
 
5
8
  // Observation types following claude-mem patterns
6
9
  type ObservationType =
@@ -12,6 +15,8 @@ type ObservationType =
12
15
  | "learning"
13
16
  | "warning";
14
17
 
18
+ type ConfidenceLevel = "high" | "medium" | "low";
19
+
15
20
  const TYPE_ICONS: Record<ObservationType, string> = {
16
21
  decision: "šŸŽÆ",
17
22
  bugfix: "šŸ›",
@@ -22,6 +27,173 @@ const TYPE_ICONS: Record<ObservationType, string> = {
22
27
  warning: "āš ļø",
23
28
  };
24
29
 
30
+ const CONFIDENCE_ICONS: Record<ConfidenceLevel, string> = {
31
+ high: "🟢",
32
+ medium: "🟔",
33
+ low: "šŸ”“",
34
+ };
35
+
36
+ // Patterns to detect file references in observation content
37
+ const FILE_PATTERNS = [
38
+ // file:line format (e.g., src/auth.ts:42)
39
+ /(?:^|\s)([a-zA-Z0-9_\-./]+\.[a-zA-Z]{2,4}):(\d+)/g,
40
+ // backtick file paths (e.g., `src/auth.ts`)
41
+ /`([a-zA-Z0-9_\-./]+\.[a-zA-Z]{2,4})`/g,
42
+ // common source paths
43
+ /(?:^|\s)(src\/[a-zA-Z0-9_\-./]+\.[a-zA-Z]{2,4})/g,
44
+ /(?:^|\s)(\.opencode\/[a-zA-Z0-9_\-./]+\.[a-zA-Z]{2,4})/g,
45
+ ];
46
+
47
+ interface FileReference {
48
+ file: string;
49
+ line?: number;
50
+ }
51
+
52
+ interface CodeLink {
53
+ name: string;
54
+ type: string;
55
+ file: string;
56
+ line: number;
57
+ }
58
+
59
+ // Extract file references from observation content
60
+ function extractFileReferences(content: string): FileReference[] {
61
+ const refs: FileReference[] = [];
62
+ const seen = new Set<string>();
63
+
64
+ for (const pattern of FILE_PATTERNS) {
65
+ // Reset regex state
66
+ pattern.lastIndex = 0;
67
+ let match = pattern.exec(content);
68
+
69
+ while (match !== null) {
70
+ const file = match[1];
71
+ const line = match[2] ? Number.parseInt(match[2], 10) : undefined;
72
+ const key = `${file}:${line || ""}`;
73
+
74
+ if (!seen.has(key) && !file.includes("node_modules")) {
75
+ seen.add(key);
76
+ refs.push({ file, line });
77
+ }
78
+ match = pattern.exec(content);
79
+ }
80
+ }
81
+
82
+ return refs;
83
+ }
84
+
85
+ // Find related code definitions for an observation
86
+ async function findRelatedCode(
87
+ title: string,
88
+ content: string,
89
+ fileRefs: FileReference[],
90
+ ): Promise<CodeLink[]> {
91
+ const links: CodeLink[] = [];
92
+
93
+ try {
94
+ // Search for code definitions related to the observation
95
+ const searchQuery = `${title} ${fileRefs.map((r) => r.file).join(" ")}`;
96
+ const results = await searchVectorStore(searchQuery, 5, "code");
97
+
98
+ for (const result of results) {
99
+ // Check if this code is in one of the referenced files
100
+ const isDirectRef = fileRefs.some(
101
+ (ref) =>
102
+ result.file_path.includes(ref.file) ||
103
+ ref.file.includes(result.file_path),
104
+ );
105
+
106
+ if (isDirectRef) {
107
+ links.push({
108
+ name: result.title.replace(/^(function|class|type|interface): /, ""),
109
+ type: result.title.split(":")[0] || "code",
110
+ file: result.file_path,
111
+ line: 0, // Would need to parse from result
112
+ });
113
+ }
114
+ }
115
+ } catch {
116
+ // Vector store might not have code indexed yet
117
+ }
118
+
119
+ return links.slice(0, 5); // Limit to 5 links
120
+ }
121
+
122
+ // Vector store configuration
123
+ const VECTOR_DB_PATH = ".opencode/memory/vector_db";
124
+ const TABLE_NAME = "memories";
125
+
126
+ async function addToVectorStore(
127
+ filePath: string,
128
+ title: string,
129
+ content: string,
130
+ fileType: string,
131
+ ): Promise<{ success: boolean; error?: string }> {
132
+ try {
133
+ const embeddingResult = await generateEmbedding(content.substring(0, 8000));
134
+ if (!embeddingResult) {
135
+ return { success: false, error: "Failed to generate embedding" };
136
+ }
137
+
138
+ const dbPath = path.join(process.cwd(), VECTOR_DB_PATH);
139
+ await fs.mkdir(dbPath, { recursive: true });
140
+
141
+ const db = await lancedb.connect(dbPath);
142
+ const relativePath = path.relative(process.cwd(), filePath);
143
+
144
+ const document: Record<string, unknown> = {
145
+ id: relativePath.replace(/[\/\\]/g, "_"),
146
+ file_path: relativePath,
147
+ title,
148
+ content,
149
+ content_preview: content.substring(0, 500),
150
+ embedding: embeddingResult.embedding,
151
+ indexed_at: new Date().toISOString(),
152
+ file_type: fileType,
153
+ };
154
+
155
+ let table: lancedb.Table;
156
+ try {
157
+ table = await db.openTable(TABLE_NAME);
158
+ // Add to existing table
159
+ await table.add([document]);
160
+ } catch {
161
+ // Table doesn't exist, create it
162
+ await db.createTable(TABLE_NAME, [document]);
163
+ }
164
+
165
+ return { success: true };
166
+ } catch (err) {
167
+ const msg = err instanceof Error ? err.message : String(err);
168
+ return { success: false, error: msg };
169
+ }
170
+ }
171
+
172
+ // Check for potentially contradicting observations
173
+ async function findSimilarObservations(
174
+ title: string,
175
+ content: string,
176
+ concepts: string[],
177
+ ): Promise<{ file: string; title: string; similarity: string }[]> {
178
+ try {
179
+ // Search for similar observations using semantic search
180
+ const searchQuery = `${title} ${concepts.join(" ")}`;
181
+ const similar = await searchVectorStore(searchQuery, 5, "observation");
182
+
183
+ // Filter to only observations with high similarity
184
+ return similar
185
+ .filter((doc) => doc.title !== title) // Exclude self
186
+ .slice(0, 3)
187
+ .map((doc) => ({
188
+ file: path.basename(doc.file_path),
189
+ title: doc.title,
190
+ similarity: "high",
191
+ }));
192
+ } catch {
193
+ return [];
194
+ }
195
+ }
196
+
25
197
  export default tool({
26
198
  description:
27
199
  "Create a structured observation for future reference. Observations are categorized by type (decision, bugfix, feature, pattern, discovery, learning, warning) and stored in .opencode/memory/observations/.",
@@ -51,6 +223,18 @@ export default tool({
51
223
  .string()
52
224
  .optional()
53
225
  .describe("Related bead ID for traceability"),
226
+ confidence: tool.schema
227
+ .string()
228
+ .optional()
229
+ .describe(
230
+ "Confidence level: high (verified), medium (likely), low (uncertain). Defaults to high.",
231
+ ),
232
+ supersedes: tool.schema
233
+ .string()
234
+ .optional()
235
+ .describe(
236
+ "Filename of observation this supersedes (for contradiction handling)",
237
+ ),
54
238
  },
55
239
  execute: async (args: {
56
240
  type: string;
@@ -59,6 +243,8 @@ export default tool({
59
243
  concepts?: string;
60
244
  files?: string;
61
245
  bead_id?: string;
246
+ confidence?: string;
247
+ supersedes?: string;
62
248
  }) => {
63
249
  const obsDir = path.join(process.cwd(), ".opencode/memory/observations");
64
250
 
@@ -77,6 +263,14 @@ export default tool({
77
263
  return `Error: Invalid observation type '${args.type}'.\nValid types: ${validTypes.join(", ")}`;
78
264
  }
79
265
 
266
+ // Validate confidence level
267
+ const validConfidence: ConfidenceLevel[] = ["high", "medium", "low"];
268
+ const confidence = (args.confidence?.toLowerCase() ||
269
+ "high") as ConfidenceLevel;
270
+ if (!validConfidence.includes(confidence)) {
271
+ return `Error: Invalid confidence level '${args.confidence}'.\nValid levels: ${validConfidence.join(", ")}`;
272
+ }
273
+
80
274
  // Generate filename: YYYY-MM-DD-type-slug.md
81
275
  const now = new Date();
82
276
  const dateStr = now.toISOString().split("T")[0];
@@ -92,27 +286,59 @@ export default tool({
92
286
  const concepts = args.concepts
93
287
  ? args.concepts.split(",").map((c) => c.trim())
94
288
  : [];
95
- const files = args.files ? args.files.split(",").map((f) => f.trim()) : [];
289
+ let files = args.files ? args.files.split(",").map((f) => f.trim()) : [];
290
+
291
+ // Auto-detect file references from content (Phase 2: Link to code)
292
+ const detectedRefs = extractFileReferences(args.content);
293
+ const detectedFiles = detectedRefs.map((r) => r.file);
294
+
295
+ // Merge detected files with explicitly provided files
296
+ const allFiles = [...new Set([...files, ...detectedFiles])];
297
+ files = allFiles;
298
+
299
+ // Find related code definitions
300
+ const codeLinks = await findRelatedCode(
301
+ args.title,
302
+ args.content,
303
+ detectedRefs,
304
+ );
96
305
 
97
- // Build observation content
306
+ // Build observation content with YAML frontmatter for temporal tracking
98
307
  const icon = TYPE_ICONS[obsType];
99
- let observation = `# ${icon} ${args.title}\n\n`;
100
- observation += `**Type:** ${obsType}\n`;
101
- observation += `**Created:** ${now.toISOString()}\n`;
308
+ const confidenceIcon = CONFIDENCE_ICONS[confidence];
102
309
 
310
+ // YAML frontmatter for temporal validity (Graphiti-inspired)
311
+ let observation = "---\n";
312
+ observation += `type: ${obsType}\n`;
313
+ observation += `created: ${now.toISOString()}\n`;
314
+ observation += `confidence: ${confidence}\n`;
315
+ observation += "valid_until: null\n"; // null = still valid
316
+ observation += "superseded_by: null\n"; // null = not superseded
317
+ if (args.supersedes) {
318
+ observation += `supersedes: ${args.supersedes}\n`;
319
+ }
103
320
  if (args.bead_id) {
104
- observation += `**Bead:** ${args.bead_id}\n`;
321
+ observation += `bead_id: ${args.bead_id}\n`;
105
322
  }
106
-
107
323
  if (concepts.length > 0) {
108
- observation += `**Concepts:** ${concepts.map((c) => `\`${c}\``).join(", ")}\n`;
324
+ observation += `concepts: [${concepts.map((c) => `"${c}"`).join(", ")}]\n`;
109
325
  }
110
-
111
326
  if (files.length > 0) {
112
- observation += `**Files:** ${files.map((f) => `\`${f}\``).join(", ")}\n`;
327
+ observation += `files: [${files.map((f) => `"${f}"`).join(", ")}]\n`;
328
+ }
329
+ if (codeLinks.length > 0) {
330
+ observation += "code_links:\n";
331
+ for (const link of codeLinks) {
332
+ observation += ` - name: "${link.name}"\n`;
333
+ observation += ` type: "${link.type}"\n`;
334
+ observation += ` file: "${link.file}"\n`;
335
+ }
113
336
  }
337
+ observation += "---\n\n";
114
338
 
115
- observation += "\n---\n\n";
339
+ // Content
340
+ observation += `# ${icon} ${args.title}\n\n`;
341
+ observation += `${confidenceIcon} **Confidence:** ${confidence}\n\n`;
116
342
  observation += args.content;
117
343
  observation += "\n";
118
344
 
@@ -123,8 +349,59 @@ export default tool({
123
349
  // Write observation
124
350
  await fs.writeFile(filePath, observation, "utf-8");
125
351
 
352
+ // Generate embedding and add to vector store
353
+ let embeddingStatus = "";
354
+ const vectorResult = await addToVectorStore(
355
+ filePath,
356
+ args.title,
357
+ observation,
358
+ "observation",
359
+ );
360
+ if (vectorResult.success) {
361
+ embeddingStatus = "\nEmbedding: āœ“ Added to vector store";
362
+ } else {
363
+ embeddingStatus = `\nEmbedding: ⚠ ${vectorResult.error || "Failed"}`;
364
+ }
365
+
366
+ // Handle supersedes - update old observation's superseded_by field
367
+ let supersedesStatus = "";
368
+ if (args.supersedes) {
369
+ try {
370
+ const oldPath = path.join(obsDir, args.supersedes);
371
+ const oldContent = await fs.readFile(oldPath, "utf-8");
372
+ // Update superseded_by in frontmatter
373
+ const updatedContent = oldContent.replace(
374
+ /superseded_by: null/,
375
+ `superseded_by: "${filename}"`,
376
+ );
377
+ if (updatedContent !== oldContent) {
378
+ await fs.writeFile(oldPath, updatedContent, "utf-8");
379
+ supersedesStatus = `\nSupersedes: āœ“ Marked ${args.supersedes} as superseded`;
380
+ }
381
+ } catch {
382
+ supersedesStatus = `\nSupersedes: ⚠ Could not update ${args.supersedes}`;
383
+ }
384
+ }
385
+
126
386
  let beadUpdate = "";
127
387
 
388
+ // Check for similar/potentially contradicting observations
389
+ let contradictionWarning = "";
390
+ const similarObs = await findSimilarObservations(
391
+ args.title,
392
+ args.content,
393
+ concepts,
394
+ );
395
+ if (similarObs.length > 0) {
396
+ contradictionWarning =
397
+ "\n\nāš ļø **Similar observations found** (potential contradictions):";
398
+ for (const obs of similarObs) {
399
+ contradictionWarning += `\n- ${obs.title} (\`${obs.file}\`)`;
400
+ }
401
+ contradictionWarning +=
402
+ "\n\nIf this supersedes an older observation, use `supersedes` arg to mark it.";
403
+ }
404
+
128
405
  // Update bead notes if bead_id provided
129
406
  if (args.bead_id) {
130
407
  try {
@@ -144,7 +421,19 @@ export default tool({
144
421
  }
145
422
  }
146
423
 
147
- return `āœ“ Observation saved: ${filename}\n\nType: ${icon} ${obsType}\nTitle: ${args.title}\nConcepts: ${concepts.join(", ") || "none"}\nFiles: ${files.join(", ") || "none"}${beadUpdate}\n\nPath: ${filePath}`;
424
+ // Build code links info
425
+ let codeLinksInfo = "";
426
+ if (codeLinks.length > 0) {
427
+ codeLinksInfo = `\nCode Links: ${codeLinks.map((l) => `${l.type}:${l.name}`).join(", ")}`;
428
+ }
429
+
430
+ // Build auto-detected files info
431
+ let autoDetectedInfo = "";
432
+ if (detectedFiles.length > 0) {
433
+ autoDetectedInfo = `\nAuto-detected: ${detectedFiles.length} file reference(s)`;
434
+ }
435
+
436
+ return `āœ“ Observation saved: ${filename}\n\nType: ${icon} ${obsType}\nTitle: ${args.title}\nConfidence: ${confidenceIcon} ${confidence}\nConcepts: ${concepts.join(", ") || "none"}\nFiles: ${files.join(", ") || "none"}${codeLinksInfo}${autoDetectedInfo}${embeddingStatus}${supersedesStatus}${beadUpdate}${contradictionWarning}\n\nPath: ${filePath}`;
148
437
  } catch (error) {
149
438
  if (error instanceof Error) {
150
439
  return `Error saving observation: ${error.message}`;