opencodekit 0.17.13 → 0.18.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 (36) hide show
  1. package/dist/index.js +4 -6
  2. package/dist/template/.opencode/dcp.jsonc +81 -81
  3. package/dist/template/.opencode/memory/memory.db +0 -0
  4. package/dist/template/.opencode/memory.db +0 -0
  5. package/dist/template/.opencode/memory.db-shm +0 -0
  6. package/dist/template/.opencode/memory.db-wal +0 -0
  7. package/dist/template/.opencode/opencode.json +199 -23
  8. package/dist/template/.opencode/opencode.json.tui-migration.bak +1380 -0
  9. package/dist/template/.opencode/package.json +1 -1
  10. package/dist/template/.opencode/plugin/lib/capture.ts +177 -0
  11. package/dist/template/.opencode/plugin/lib/context.ts +194 -0
  12. package/dist/template/.opencode/plugin/lib/curator.ts +234 -0
  13. package/dist/template/.opencode/plugin/lib/db/maintenance.ts +312 -0
  14. package/dist/template/.opencode/plugin/lib/db/observations.ts +299 -0
  15. package/dist/template/.opencode/plugin/lib/db/pipeline.ts +520 -0
  16. package/dist/template/.opencode/plugin/lib/db/schema.ts +356 -0
  17. package/dist/template/.opencode/plugin/lib/db/types.ts +211 -0
  18. package/dist/template/.opencode/plugin/lib/distill.ts +376 -0
  19. package/dist/template/.opencode/plugin/lib/inject.ts +126 -0
  20. package/dist/template/.opencode/plugin/lib/memory-admin-tools.ts +188 -0
  21. package/dist/template/.opencode/plugin/lib/memory-db.ts +54 -936
  22. package/dist/template/.opencode/plugin/lib/memory-helpers.ts +202 -0
  23. package/dist/template/.opencode/plugin/lib/memory-hooks.ts +240 -0
  24. package/dist/template/.opencode/plugin/lib/memory-tools.ts +341 -0
  25. package/dist/template/.opencode/plugin/memory.ts +56 -60
  26. package/dist/template/.opencode/plugin/sessions.ts +372 -93
  27. package/dist/template/.opencode/tui.json +15 -0
  28. package/package.json +1 -1
  29. package/dist/template/.opencode/tool/action-queue.ts +0 -313
  30. package/dist/template/.opencode/tool/memory-admin.ts +0 -445
  31. package/dist/template/.opencode/tool/memory-get.ts +0 -143
  32. package/dist/template/.opencode/tool/memory-read.ts +0 -45
  33. package/dist/template/.opencode/tool/memory-search.ts +0 -264
  34. package/dist/template/.opencode/tool/memory-timeline.ts +0 -105
  35. package/dist/template/.opencode/tool/memory-update.ts +0 -63
  36. package/dist/template/.opencode/tool/observation.ts +0 -357
@@ -1,264 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { tool } from "@opencode-ai/plugin";
4
- import {
5
- type ObservationType,
6
- type SearchIndexResult,
7
- checkFTS5Available,
8
- searchObservationsFTS,
9
- } from "../plugin/lib/memory-db";
10
-
11
- // Fallback file-based search for non-SQLite content
12
- interface FileSearchResult {
13
- file: string;
14
- matches: { line: number; content: string }[];
15
- }
16
-
17
- async function searchDirectory(
18
- dir: string,
19
- pattern: RegExp,
20
- results: FileSearchResult[],
21
- baseDir: string,
22
- ): Promise<void> {
23
- try {
24
- const entries = await fs.readdir(dir, { withFileTypes: true });
25
-
26
- for (const entry of entries) {
27
- const fullPath = path.join(dir, entry.name);
28
-
29
- if (entry.isDirectory()) {
30
- // Skip hidden directories and vector_db
31
- if (entry.name.startsWith(".") || entry.name === "vector_db") {
32
- continue;
33
- }
34
- await searchDirectory(fullPath, pattern, results, baseDir);
35
- } else if (entry.name.endsWith(".md")) {
36
- const content = await fs.readFile(fullPath, "utf-8");
37
- const lines = content.split("\n");
38
- const matches: { line: number; content: string }[] = [];
39
-
40
- for (let index = 0; index < lines.length; index++) {
41
- const line = lines[index];
42
- if (pattern.test(line)) {
43
- matches.push({
44
- line: index + 1,
45
- content: line.trim().substring(0, 150),
46
- });
47
- }
48
- }
49
-
50
- if (matches.length > 0) {
51
- const relativePath = path.relative(baseDir, fullPath);
52
- results.push({ file: relativePath, matches });
53
- }
54
- }
55
- }
56
- } catch {
57
- // Directory doesn't exist or not readable
58
- }
59
- }
60
-
61
- async function fallbackKeywordSearch(
62
- query: string,
63
- type: string | undefined,
64
- limit: number,
65
- ): Promise<FileSearchResult[]> {
66
- const memoryDir = path.join(process.cwd(), ".opencode/memory");
67
- const beadsDir = path.join(process.cwd(), ".beads/artifacts");
68
- const globalMemoryDir = path.join(
69
- process.env.HOME || "",
70
- ".config/opencode/memory",
71
- );
72
-
73
- // Create case-insensitive regex from query
74
- let pattern: RegExp;
75
- try {
76
- pattern = new RegExp(query, "i");
77
- } catch {
78
- // Escape special chars if not valid regex
79
- const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
80
- pattern = new RegExp(escaped, "i");
81
- }
82
-
83
- const results: FileSearchResult[] = [];
84
-
85
- // Handle type filtering
86
- if (type === "beads") {
87
- await searchDirectory(beadsDir, pattern, results, beadsDir);
88
- } else if (type && type !== "all" && type !== "observations") {
89
- const typeMap: Record<string, string> = {
90
- handoffs: "handoffs",
91
- research: "research",
92
- templates: "_templates",
93
- };
94
- const subdir = typeMap[type];
95
- if (subdir) {
96
- const searchDir = path.join(memoryDir, subdir);
97
- await searchDirectory(searchDir, pattern, results, memoryDir);
98
- }
99
- } else {
100
- // Search all: memory + beads
101
- await searchDirectory(memoryDir, pattern, results, memoryDir);
102
- await searchDirectory(beadsDir, pattern, results, beadsDir);
103
- await searchDirectory(globalMemoryDir, pattern, results, globalMemoryDir);
104
- }
105
-
106
- return results.slice(0, limit);
107
- }
108
-
109
- const TYPE_ICONS: Record<string, string> = {
110
- decision: "🎯",
111
- bugfix: "🐛",
112
- feature: "✨",
113
- pattern: "🔄",
114
- discovery: "💡",
115
- learning: "📚",
116
- warning: "⚠️",
117
- };
118
-
119
- function formatCompactIndex(
120
- results: SearchIndexResult[],
121
- query: string,
122
- ): string {
123
- if (results.length === 0) {
124
- return `No observations found for "${query}".\n\nTip: Use memory-search without query to see recent observations.`;
125
- }
126
-
127
- let output = `# Search Results: "${query}"\n\n`;
128
- output += `Found **${results.length}** observation(s). Use \`memory-get\` for full details.\n\n`;
129
- output += "| ID | Type | Title | Date |\n";
130
- output += "|---|---|---|---|\n";
131
-
132
- for (const result of results) {
133
- const icon = TYPE_ICONS[result.type] || "📝";
134
- const date = result.created_at.split("T")[0];
135
- const title =
136
- result.title.length > 50
137
- ? `${result.title.substring(0, 47)}...`
138
- : result.title;
139
- output += `| #${result.id} | ${icon} ${result.type} | ${title} | ${date} |\n`;
140
- }
141
-
142
- output += "\n## Snippets\n\n";
143
- for (const result of results) {
144
- const icon = TYPE_ICONS[result.type] || "📝";
145
- output += `**#${result.id}** ${icon} ${result.title}\n`;
146
- if (result.snippet) {
147
- output += `> ${result.snippet}...\n`;
148
- }
149
- output += "\n";
150
- }
151
-
152
- output += "\n---\n";
153
- output += `💡 **Next steps:**\n`;
154
- output += `- \`memory-get({ ids: "${results
155
- .map((r) => r.id)
156
- .slice(0, 3)
157
- .join(",")}" })\` - Get full details\n`;
158
- output += `- \`memory-timeline({ anchor_id: ${results[0].id} })\` - See chronological context\n`;
159
-
160
- return output;
161
- }
162
-
163
- function formatFallbackResults(
164
- query: string,
165
- results: FileSearchResult[],
166
- limit: number,
167
- ): string {
168
- if (results.length === 0) {
169
- return `No matches found for "${query}" in non-observation files.`;
170
- }
171
-
172
- let output = `# File Search: "${query}"\n\n`;
173
- output += `Found ${results.length} file(s) with matches.\n\n`;
174
-
175
- for (const result of results) {
176
- output += `## ${result.file}\n\n`;
177
- const matchesToShow = result.matches.slice(0, limit);
178
- for (const match of matchesToShow) {
179
- output += `- **Line ${match.line}:** ${match.content}\n`;
180
- }
181
- if (result.matches.length > limit) {
182
- output += `- ... and ${result.matches.length - limit} more matches\n`;
183
- }
184
- output += "\n";
185
- }
186
-
187
- return output;
188
- }
189
-
190
- export default tool({
191
- description: `Search memory across observations and markdown archives.
192
-
193
- Purpose:
194
- - Fast, ranked search across all observations in SQLite (when FTS5 is available)
195
- - Returns compact index (~50-100 tokens per result) for progressive disclosure
196
- - Use memory-get for full details after identifying relevant observations
197
-
198
- FTS5 availability:
199
- - Auto-detected at runtime; if unavailable, observation searches fall back to file scan
200
-
201
- Search modes and hints:
202
- - "observations" (default): Best for decisions, bugs, learnings; uses FTS5 ranking when available
203
- - "handoffs": Use for past session handoffs and summaries
204
- - "research": Use for research notes and external findings
205
- - "templates": Use for memory templates and boilerplate references
206
- - "beads": Use for task artifacts in .beads/artifacts
207
- - "all": Use when you are unsure where info lives; searches SQLite + markdown + beads
208
-
209
- Example:
210
- memory-search({ query: "authentication" })
211
- memory-search({ query: "auth", type: "decision", limit: 5 })`,
212
- args: {
213
- query: tool.schema.string().describe("Search query: keywords or phrase"),
214
- type: tool.schema
215
- .string()
216
- .optional()
217
- .describe(
218
- "Filter by observation type (decision, bugfix, feature, pattern, discovery, learning, warning) or search scope (observations, handoffs, research, templates, beads, all)",
219
- ),
220
- limit: tool.schema
221
- .number()
222
- .optional()
223
- .describe("Max results (default: 10)"),
224
- },
225
- execute: async (args: { query: string; type?: string; limit?: number }) => {
226
- const limit = args.limit || 10;
227
-
228
- // Determine if we should use SQLite FTS5 or fallback
229
- const observationTypes = [
230
- "decision",
231
- "bugfix",
232
- "feature",
233
- "pattern",
234
- "discovery",
235
- "learning",
236
- "warning",
237
- ];
238
- const isObservationType =
239
- args.type && observationTypes.includes(args.type.toLowerCase());
240
- const isObservationsScope =
241
- !args.type || args.type === "observations" || isObservationType;
242
-
243
- // Try SQLite FTS5 for observations
244
- if (isObservationsScope && checkFTS5Available()) {
245
- try {
246
- const obsType = isObservationType
247
- ? (args.type?.toLowerCase() as ObservationType)
248
- : undefined;
249
- const results = searchObservationsFTS(args.query, {
250
- type: obsType,
251
- limit,
252
- });
253
- return formatCompactIndex(results, args.query);
254
- } catch {
255
- // FTS5 failed, fall through to file search
256
- // Silently fall back to file-based search
257
- }
258
- }
259
-
260
- // Fallback to file-based search for non-observation types or FTS5 failure
261
- const results = await fallbackKeywordSearch(args.query, args.type, limit);
262
- return formatFallbackResults(args.query, results, limit);
263
- },
264
- });
@@ -1,105 +0,0 @@
1
- import { tool } from "@opencode-ai/plugin";
2
- import {
3
- type SearchIndexResult,
4
- getTimelineAroundObservation,
5
- } from "../plugin/lib/memory-db";
6
-
7
- const TYPE_ICONS: Record<string, string> = {
8
- decision: "🎯",
9
- bugfix: "🐛",
10
- feature: "✨",
11
- pattern: "🔄",
12
- discovery: "💡",
13
- learning: "📚",
14
- warning: "⚠️",
15
- };
16
-
17
- function formatTimelineResult(
18
- anchor: {
19
- id: number;
20
- type: string;
21
- title: string;
22
- created_at: string;
23
- } | null,
24
- before: SearchIndexResult[],
25
- after: SearchIndexResult[],
26
- ): string {
27
- if (!anchor) {
28
- return "Anchor observation not found.";
29
- }
30
-
31
- let output = "# Timeline Context\n\n";
32
-
33
- // Before (older observations)
34
- if (before.length > 0) {
35
- output += "## Earlier Observations\n\n";
36
- for (const obs of before) {
37
- const icon = TYPE_ICONS[obs.type] || "📝";
38
- const date = obs.created_at.split("T")[0];
39
- output += `- **#${obs.id}** ${icon} ${obs.title} _(${date})_\n`;
40
- if (obs.snippet) {
41
- output += ` ${obs.snippet.substring(0, 80)}...\n`;
42
- }
43
- }
44
- output += "\n";
45
- }
46
-
47
- // Anchor
48
- const anchorIcon = TYPE_ICONS[anchor.type] || "📝";
49
- const anchorDate = anchor.created_at.split("T")[0];
50
- output += `## ▶ Current: #${anchor.id}\n\n`;
51
- output += `${anchorIcon} **${anchor.title}** _(${anchorDate})_\n\n`;
52
-
53
- // After (newer observations)
54
- if (after.length > 0) {
55
- output += "## Later Observations\n\n";
56
- for (const obs of after) {
57
- const icon = TYPE_ICONS[obs.type] || "📝";
58
- const date = obs.created_at.split("T")[0];
59
- output += `- **#${obs.id}** ${icon} ${obs.title} _(${date})_\n`;
60
- if (obs.snippet) {
61
- output += ` ${obs.snippet.substring(0, 80)}...\n`;
62
- }
63
- }
64
- }
65
-
66
- return output;
67
- }
68
-
69
- export default tool({
70
- description: `Get chronological context around an observation.
71
-
72
- Purpose:
73
- - Progressive disclosure: see what was happening before/after a specific observation
74
- - Understand decision context over time
75
- - Navigate memory timeline
76
-
77
- Example:
78
- memory-timeline({ anchor_id: 42, depth_before: 5, depth_after: 5 })`,
79
- args: {
80
- anchor_id: tool.schema
81
- .number()
82
- .describe("ID of the observation to get context around"),
83
- depth_before: tool.schema
84
- .number()
85
- .optional()
86
- .describe("Number of earlier observations to include (default: 5)"),
87
- depth_after: tool.schema
88
- .number()
89
- .optional()
90
- .describe("Number of later observations to include (default: 5)"),
91
- },
92
- execute: async (args: {
93
- anchor_id: number;
94
- depth_before?: number;
95
- depth_after?: number;
96
- }) => {
97
- const { anchor, before, after } = getTimelineAroundObservation(
98
- args.anchor_id,
99
- args.depth_before ?? 5,
100
- args.depth_after ?? 5,
101
- );
102
-
103
- return formatTimelineResult(anchor, before, after);
104
- },
105
- });
@@ -1,63 +0,0 @@
1
- import { tool } from "@opencode-ai/plugin";
2
- import { upsertMemoryFile } from "../plugin/lib/memory-db.js";
3
-
4
- export default tool({
5
- description: `Update memory files with new learnings, progress, or context.
6
-
7
- Purpose:
8
- - Write or append to project memory in SQLite
9
- - Supports subdirectories (e.g., 'research/2024-01-topic')
10
- - Two modes: 'replace' (overwrite) or 'append' (add to end)
11
-
12
- Example:
13
- memory-update({ file: "research/session-findings", content: "..." })
14
- memory-update({ file: "handoffs/phase-2", content: "...", mode: "append" })`,
15
- args: {
16
- file: tool.schema
17
- .string()
18
- .describe(
19
- "Memory file to update: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic",
20
- ),
21
- content: tool.schema
22
- .string()
23
- .describe("Content to write or append to the memory file"),
24
- mode: tool.schema
25
- .string()
26
- .optional()
27
- .default("replace")
28
- .describe(
29
- "Update mode: 'replace' (overwrite file) or 'append' (add to end).",
30
- ),
31
- },
32
- execute: async (args: { file: string; content: string; mode?: string }) => {
33
- // Normalize file path: strip existing .md extension
34
- const normalizedFile = args.file.replace(/\.md$/i, "");
35
- const mode = (args.mode || "replace") as "replace" | "append";
36
-
37
- const timestamp = new Date().toISOString();
38
- let finalContent: string;
39
-
40
- if (mode === "append") {
41
- finalContent = `\n\n---\n**Updated:** ${timestamp}\n\n${args.content}`;
42
- } else {
43
- // Replace mode - update timestamp placeholder if present
44
- finalContent = args.content.replace(
45
- /\*\*Last Updated:\*\* \[Timestamp\]/,
46
- `**Last Updated:** ${timestamp}`,
47
- );
48
- }
49
-
50
- try {
51
- // Store in SQLite (single source of truth)
52
- upsertMemoryFile(normalizedFile, finalContent, mode);
53
-
54
- const action = mode === "append" ? "Appended to" : "Updated";
55
- return `✓ ${action} ${normalizedFile}`;
56
- } catch (error) {
57
- if (error instanceof Error) {
58
- return `Error updating memory: ${error.message}`;
59
- }
60
- return "Unknown error updating memory file";
61
- }
62
- },
63
- });