opencodekit 0.23.1 → 0.23.3

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 (30) hide show
  1. package/dist/index.js +354 -825
  2. package/dist/template/.opencode/AGENTS.md +15 -2
  3. package/dist/template/.opencode/command/init.md +198 -34
  4. package/dist/template/.opencode/context/fallow.md +137 -0
  5. package/dist/template/.opencode/opencode.json +12 -315
  6. package/dist/template/.opencode/plugin/codesearch.ts +730 -0
  7. package/dist/template/.opencode/plugin/memory/compile.ts +171 -186
  8. package/dist/template/.opencode/plugin/memory/index-generator.ts +118 -133
  9. package/dist/template/.opencode/plugin/memory/lint.ts +253 -275
  10. package/dist/template/.opencode/plugin/memory/tools.ts +224 -268
  11. package/dist/template/.opencode/plugin/memory/validate.ts +154 -164
  12. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-preview.ts +13 -30
  13. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-shared.ts +25 -0
  14. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search.ts +17 -34
  15. package/dist/template/.opencode/plugin/session-summary.ts +0 -2
  16. package/dist/template/.opencode/plugin/srcwalk.ts +646 -667
  17. package/dist/template/.opencode/skill/code-navigation/SKILL.md +10 -10
  18. package/dist/template/.opencode/skill/code-review-and-quality/SKILL.md +1 -1
  19. package/dist/template/.opencode/skill/condition-based-waiting/example.ts +15 -2
  20. package/dist/template/.opencode/skill/debugging-and-error-recovery/SKILL.md +1 -1
  21. package/dist/template/.opencode/skill/deep-module-design/SKILL.md +1 -1
  22. package/dist/template/.opencode/skill/fallow/SKILL.md +409 -0
  23. package/dist/template/.opencode/skill/fallow/references/cli-reference.md +1905 -0
  24. package/dist/template/.opencode/skill/fallow/references/gotchas.md +644 -0
  25. package/dist/template/.opencode/skill/fallow/references/patterns.md +791 -0
  26. package/dist/template/.opencode/skill/planning-and-task-breakdown/SKILL.md +1 -1
  27. package/dist/template/.opencode/skill/srcwalk/SKILL.md +10 -13
  28. package/dist/template/.opencode/skill/ubiquitous-language/SKILL.md +1 -1
  29. package/dist/template/.opencode/tool/grepsearch.ts +92 -103
  30. package/package.json +1 -1
@@ -8,26 +8,25 @@
8
8
 
9
9
  import { tool, type ToolContext } from "@opencode-ai/plugin/tool";
10
10
  import {
11
- type ConfidenceLevel,
12
- type ObservationSource,
13
- type ObservationType,
14
- type HallType,
15
- VALID_HALLS,
11
+ type ConfidenceLevel,
12
+ type ObservationSource,
13
+ type ObservationType,
14
+ type HallType,
16
15
  } from "./db/types.js";
17
16
  import {
18
- autoDetectFiles,
19
- parseCSV,
20
- formatObservation,
21
- TYPE_ICONS,
22
- VALID_TYPES,
17
+ autoDetectFiles,
18
+ parseCSV,
19
+ formatObservation,
20
+ TYPE_ICONS,
21
+ VALID_TYPES,
23
22
  } from "./helpers.js";
24
23
  import { validateObservation } from "./validate.js";
25
24
  import {
26
- storeObservation,
27
- getObservationById,
28
- searchObservationsFTS,
29
- recordFeedback,
30
- markObservationsRetrieved,
25
+ storeObservation,
26
+ getObservationById,
27
+ searchObservationsFTS,
28
+ recordFeedback,
29
+ markObservationsRetrieved,
31
30
  } from "./db/observations.js";
32
31
  import { getMemoryFile } from "./db/maintenance.js";
33
32
 
@@ -36,16 +35,16 @@ import { getMemoryFile } from "./db/maintenance.js";
36
35
  // ============================================================================
37
36
 
38
37
  function withDBErrorHandling<T extends Record<string, unknown>>(
39
- handler: (args: T) => string | Promise<string>,
38
+ handler: (args: T) => string | Promise<string>,
40
39
  ): (args: T, context: ToolContext) => Promise<string> {
41
- return async (args: T, _context: ToolContext) => {
42
- try {
43
- return await handler(args);
44
- } catch (err) {
45
- const msg = err instanceof Error ? err.message : String(err);
46
- return `Error: ${msg}`;
47
- }
48
- };
40
+ return async (args: T, _context: ToolContext) => {
41
+ try {
42
+ return await handler(args);
43
+ } catch (err) {
44
+ const msg = err instanceof Error ? err.message : String(err);
45
+ return `Error: ${msg}`;
46
+ }
47
+ };
49
48
  }
50
49
 
51
50
  // ============================================================================
@@ -53,18 +52,18 @@ function withDBErrorHandling<T extends Record<string, unknown>>(
53
52
  // ============================================================================
54
53
 
55
54
  interface CoreToolDeps {
56
- handoffDir: string;
55
+ handoffDir: string;
57
56
  }
58
57
 
59
58
  export function createCoreTools(deps: CoreToolDeps) {
60
- const { handoffDir } = deps;
61
-
62
- return {
63
- // --------------------------------------------------------------------
64
- // observation: Create or give feedback
65
- // --------------------------------------------------------------------
66
- observation: tool({
67
- description: `Create a durable observation for future retrieval, or mark an existing one as helpful/harmful.
59
+ void deps;
60
+
61
+ return {
62
+ // --------------------------------------------------------------------
63
+ // observation: Create or give feedback
64
+ // --------------------------------------------------------------------
65
+ observation: tool({
66
+ description: `Create a durable observation for future retrieval, or mark an existing one as helpful/harmful.
68
67
  Pass id+feedback for feedback mode; pass type+title for create mode.
69
68
 
70
69
  Create mode captures decisions, bugs, features, patterns, discoveries, learnings, or warnings.
@@ -77,176 +76,142 @@ Create mode examples:
77
76
  Feedback mode examples:
78
77
  observation({ id: 42, feedback: "helpful" })
79
78
  observation({ id: 42, feedback: "harmful", reason: "Outdated information" })`,
80
- args: {
81
- // --- Create mode params ---
82
- type: tool.schema
83
- .string()
84
- .optional()
85
- .describe(
86
- "Observation type for create mode: decision, bugfix, feature, pattern, discovery, learning, warning",
87
- ),
88
- title: tool.schema.string().optional().describe("Short title (create mode)"),
89
- subtitle: tool.schema.string().optional().describe("Optional subtitle"),
90
- facts: tool.schema
91
- .string()
92
- .optional()
93
- .describe("Comma-separated key facts"),
94
- narrative: tool.schema
95
- .string()
96
- .optional()
97
- .describe("Detailed context"),
98
- content: tool.schema
99
- .string()
100
- .optional()
101
- .describe("Alias for narrative"),
102
- concepts: tool.schema
103
- .string()
104
- .optional()
105
- .describe("Comma-separated concepts"),
106
- files_read: tool.schema
107
- .string()
108
- .optional()
109
- .describe("Comma-separated files read"),
110
- files_modified: tool.schema
111
- .string()
112
- .optional()
113
- .describe("Comma-separated files modified"),
114
- files: tool.schema
115
- .string()
116
- .optional()
117
- .describe("Alias for files_modified"),
118
- bead_id: tool.schema.string().optional().describe("Related bead ID"),
119
- confidence: tool.schema
120
- .string()
121
- .optional()
122
- .describe("high, medium, or low"),
123
- supersedes: tool.schema
124
- .string()
125
- .optional()
126
- .describe("Observation ID this supersedes"),
127
- source: tool.schema
128
- .string()
129
- .optional()
130
- .describe("manual, curator, or imported"),
131
- hall: tool.schema
132
- .string()
133
- .optional()
134
- .describe("Navigation hall: facts, events, discoveries, preferences, advice"),
135
-
136
- // --- Feedback mode params ---
137
- id: tool.schema
138
- .number()
139
- .optional()
140
- .describe("Observation ID for feedback mode"),
141
- feedback: tool.schema
142
- .string()
143
- .optional()
144
- .describe('"helpful" or "harmful" (feedback mode)'),
145
- reason: tool.schema
146
- .string()
147
- .optional()
148
- .describe("Optional reason for feedback"),
149
- },
150
- execute: withDBErrorHandling(async (args) => {
151
- // --- Feedback mode: observation #id marked helpful/harmful ---
152
- if (args.id !== undefined && args.feedback !== undefined) {
153
- const obsId = Number(args.id);
154
- if (!Number.isInteger(obsId)) return "❌ id must be an integer.";
155
- const fb = String(args.feedback);
156
- if (fb !== "helpful" && fb !== "harmful") return '❌ feedback must be "helpful" or "harmful".';
157
-
158
- const result = recordFeedback(obsId, fb, args.reason as string | undefined);
159
- if (!result.success) return `❌ ${result.error ?? "Failed to record feedback."}`;
160
-
161
- return [
162
- `✅ Observation #${obsId} marked as ${fb}.`,
163
- `- Feedback: ${result.helpfulCount} helpful / ${result.harmfulCount} harmful`,
164
- ].join("\n");
165
- }
166
-
167
- // --- Create mode: store a new observation ---
168
- if (!args.type || !args.title) {
169
- return "❌ Provide type+title to create an observation, or id+feedback to give feedback.";
170
- }
171
-
172
- const obsType = args.type as ObservationType;
173
- if (!VALID_TYPES.includes(obsType)) {
174
- return `Error: Invalid type "${args.type}". Valid: ${VALID_TYPES.join(", ")}`;
175
- }
176
-
177
- const confidence = (args.confidence ?? "high") as ConfidenceLevel;
178
- if (!["high", "medium", "low"].includes(confidence)) {
179
- return `Error: Invalid confidence "${args.confidence}". Valid: high, medium, low`;
180
- }
181
-
182
- const narrative = args.narrative ?? args.content;
183
- const facts = parseCSV(args.facts);
184
- const concepts = parseCSV(args.concepts);
185
- let filesRead = parseCSV(args.files_read);
186
- const filesModified = parseCSV(args.files_modified ?? args.files);
187
-
188
- // Auto-detect files from narrative if not explicitly provided
189
- if (!filesRead && narrative) {
190
- const detected = autoDetectFiles(narrative);
191
- if (detected.length > 0) filesRead = detected;
192
- }
193
-
194
- let supersedes: number | undefined;
195
- if (args.supersedes) {
196
- const parsed = Number.parseInt(args.supersedes, 10);
197
- if (!Number.isNaN(parsed)) supersedes = parsed;
198
- }
199
-
200
- const source = (args.source ?? "manual") as ObservationSource;
201
- const hall = args.hall as HallType | undefined;
202
-
203
- // Validation gate: check for duplicates, contradictions, low quality
204
- const validation = validateObservation({
205
- type: obsType,
206
- title: args.title,
207
- facts,
208
- narrative,
209
- concepts,
210
- confidence,
211
- });
212
-
213
- if (validation.verdict === "reject") {
214
- const reasons = validation.issues.map(i => i.message).join("; ");
215
- const dupHint = validation.duplicateOf
216
- ? ` Use \`observation({ supersedes: "${validation.duplicateOf}", ... })\` to update it.`
217
- : "";
218
- return `Rejected: ${reasons}.${dupHint}`;
219
- }
220
-
221
- const id = storeObservation({
222
- type: obsType,
223
- title: args.title,
224
- subtitle: args.subtitle,
225
- facts,
226
- narrative,
227
- concepts,
228
- files_read: filesRead,
229
- files_modified: filesModified,
230
- confidence,
231
- bead_id: args.bead_id,
232
- supersedes,
233
- source,
234
- hall,
235
- });
236
-
237
- const warnings = validation.issues.length > 0
238
- ? `\n⚠️ Warnings: ${validation.issues.map(i => i.message).join("; ")}`
239
- : "";
240
-
241
- return `${TYPE_ICONS[obsType] ?? "📌"} Observation #${id} stored [${obsType}] "${args.title}" (confidence: ${confidence}, source: ${source})${warnings}`;
242
- }),
243
- }),
244
-
245
- // --------------------------------------------------------------------
246
- // memory-search: Search or read memory
247
- // --------------------------------------------------------------------
248
- "memory-search": tool({
249
- description: `Search observations by text query / #id references, or read a memory file by path.
79
+ args: {
80
+ // --- Create mode params ---
81
+ type: tool.schema
82
+ .string()
83
+ .optional()
84
+ .describe(
85
+ "Observation type for create mode: decision, bugfix, feature, pattern, discovery, learning, warning",
86
+ ),
87
+ title: tool.schema.string().optional().describe("Short title (create mode)"),
88
+ subtitle: tool.schema.string().optional().describe("Optional subtitle"),
89
+ facts: tool.schema.string().optional().describe("Comma-separated key facts"),
90
+ narrative: tool.schema.string().optional().describe("Detailed context"),
91
+ content: tool.schema.string().optional().describe("Alias for narrative"),
92
+ concepts: tool.schema.string().optional().describe("Comma-separated concepts"),
93
+ files_read: tool.schema.string().optional().describe("Comma-separated files read"),
94
+ files_modified: tool.schema.string().optional().describe("Comma-separated files modified"),
95
+ files: tool.schema.string().optional().describe("Alias for files_modified"),
96
+ bead_id: tool.schema.string().optional().describe("Related bead ID"),
97
+ confidence: tool.schema.string().optional().describe("high, medium, or low"),
98
+ supersedes: tool.schema.string().optional().describe("Observation ID this supersedes"),
99
+ source: tool.schema.string().optional().describe("manual, curator, or imported"),
100
+ hall: tool.schema
101
+ .string()
102
+ .optional()
103
+ .describe("Navigation hall: facts, events, discoveries, preferences, advice"),
104
+
105
+ // --- Feedback mode params ---
106
+ id: tool.schema.number().optional().describe("Observation ID for feedback mode"),
107
+ feedback: tool.schema
108
+ .string()
109
+ .optional()
110
+ .describe('"helpful" or "harmful" (feedback mode)'),
111
+ reason: tool.schema.string().optional().describe("Optional reason for feedback"),
112
+ },
113
+ execute: withDBErrorHandling(async (args) => {
114
+ // --- Feedback mode: observation #id marked helpful/harmful ---
115
+ if (args.id !== undefined && args.feedback !== undefined) {
116
+ const obsId = Number(args.id);
117
+ if (!Number.isInteger(obsId)) return "id must be an integer.";
118
+ const fb = String(args.feedback);
119
+ if (fb !== "helpful" && fb !== "harmful")
120
+ return 'feedback must be "helpful" or "harmful".';
121
+
122
+ const result = recordFeedback(obsId, fb, args.reason as string | undefined);
123
+ if (!result.success) return `${result.error ?? "Failed to record feedback."}`;
124
+
125
+ return [
126
+ `Observation #${obsId} marked as ${fb}.`,
127
+ `- Feedback: ${result.helpfulCount} helpful / ${result.harmfulCount} harmful`,
128
+ ].join("\n");
129
+ }
130
+
131
+ // --- Create mode: store a new observation ---
132
+ if (!args.type || !args.title) {
133
+ return "Provide type+title to create an observation, or id+feedback to give feedback.";
134
+ }
135
+
136
+ const obsType = args.type as ObservationType;
137
+ if (!VALID_TYPES.includes(obsType)) {
138
+ return `Error: Invalid type "${args.type}". Valid: ${VALID_TYPES.join(", ")}`;
139
+ }
140
+
141
+ const confidence = (args.confidence ?? "high") as ConfidenceLevel;
142
+ if (!["high", "medium", "low"].includes(confidence)) {
143
+ return `Error: Invalid confidence "${args.confidence}". Valid: high, medium, low`;
144
+ }
145
+
146
+ const narrative = args.narrative ?? args.content;
147
+ const facts = parseCSV(args.facts);
148
+ const concepts = parseCSV(args.concepts);
149
+ let filesRead = parseCSV(args.files_read);
150
+ const filesModified = parseCSV(args.files_modified ?? args.files);
151
+
152
+ // Auto-detect files from narrative if not explicitly provided
153
+ if (!filesRead && narrative) {
154
+ const detected = autoDetectFiles(narrative);
155
+ if (detected.length > 0) filesRead = detected;
156
+ }
157
+
158
+ let supersedes: number | undefined;
159
+ if (args.supersedes) {
160
+ const parsed = Number.parseInt(args.supersedes, 10);
161
+ if (!Number.isNaN(parsed)) supersedes = parsed;
162
+ }
163
+
164
+ const source = (args.source ?? "manual") as ObservationSource;
165
+ const hall = args.hall as HallType | undefined;
166
+
167
+ // Validation gate: check for duplicates, contradictions, low quality
168
+ const validation = validateObservation({
169
+ type: obsType,
170
+ title: args.title,
171
+ facts,
172
+ narrative,
173
+ concepts,
174
+ confidence,
175
+ });
176
+
177
+ if (validation.verdict === "reject") {
178
+ const reasons = validation.issues.map((i) => i.message).join("; ");
179
+ const dupHint = validation.duplicateOf
180
+ ? ` Use \`observation({ supersedes: "${validation.duplicateOf}", ... })\` to update it.`
181
+ : "";
182
+ return `Rejected: ${reasons}.${dupHint}`;
183
+ }
184
+
185
+ const id = storeObservation({
186
+ type: obsType,
187
+ title: args.title,
188
+ subtitle: args.subtitle,
189
+ facts,
190
+ narrative,
191
+ concepts,
192
+ files_read: filesRead,
193
+ files_modified: filesModified,
194
+ confidence,
195
+ bead_id: args.bead_id,
196
+ supersedes,
197
+ source,
198
+ hall,
199
+ });
200
+
201
+ const warnings =
202
+ validation.issues.length > 0
203
+ ? `\nWarnings: ${validation.issues.map((i) => i.message).join("; ")}`
204
+ : "";
205
+
206
+ return `${TYPE_ICONS[obsType] ?? "📌"} Observation #${id} stored [${obsType}] "${args.title}" (confidence: ${confidence}, source: ${source})${warnings}`;
207
+ }),
208
+ }),
209
+
210
+ // --------------------------------------------------------------------
211
+ // memory-search: Search or read memory
212
+ // --------------------------------------------------------------------
213
+ "memory-search": tool({
214
+ description: `Search observations by text query / #id references, or read a memory file by path.
250
215
  Pass file=path to read a memory file; pass query=text to search observations.
251
216
 
252
217
  Search modes:
@@ -254,69 +219,60 @@ Search modes:
254
219
  - query + type: Filter observations by type (decision, bugfix, etc.)
255
220
  - file=path: Read a memory file (e.g. persona/default, scenes/<id>)
256
221
  - #id refs: Lookup observations by ID number`,
257
- args: {
258
- query: tool.schema
259
- .string()
260
- .optional()
261
- .describe("Search query or #id references"),
262
- type: tool.schema
263
- .string()
264
- .optional()
265
- .describe("Optional observation type filter"),
266
- limit: tool.schema
267
- .number()
268
- .optional()
269
- .describe("Maximum results, default 10"),
270
- file: tool.schema
271
- .string()
272
- .optional()
273
- .describe("Memory file path (e.g. persona/default, scenes/<id>)"),
274
- },
275
- execute: withDBErrorHandling(async (args) => {
276
- const limit = Math.min(args.limit ?? 10, 50);
277
-
278
- // --- File mode: read a memory file by path ---
279
- if (args.file) {
280
- const filePath = String(args.file);
281
- const row = getMemoryFile(filePath);
282
- if (!row) return `Memory file not found: ${filePath}`;
283
- return row.content;
284
- }
285
-
286
- // --- ID refs mode: resolve #N references ---
287
- if (args.query) {
288
- const query = String(args.query);
289
- const idRefs = [...query.matchAll(/#(\d+)/g)].map(m => parseInt(m[1], 10));
290
- if (idRefs.length > 0) {
291
- const rows = idRefs
292
- .map(id => getObservationById(id))
293
- .filter((r): r is NonNullable<typeof r> => r !== null);
294
- if (rows.length === 0) return "No observations found for the requested IDs.";
295
- return rows.map(r => formatObservation(r)).join("\n\n---\n\n");
296
- }
297
- }
298
-
299
- // --- Search mode ---
300
- const query = args.query ? String(args.query).trim() : "";
301
- const scope = (args.type ?? "observations") as string;
302
- const typeFilter = VALID_TYPES.includes(scope as ObservationType)
303
- ? (scope as ObservationType)
304
- : undefined;
305
-
306
- let rows;
307
- try {
308
- rows = searchObservationsFTS(query, { type: typeFilter, limit });
309
- } catch {
310
- return "Search failed.";
311
- }
312
-
313
- if (rows.length === 0) return "No matching observations found.";
314
-
315
- // Track retrieval
316
- markObservationsRetrieved(rows.map(r => r.id));
317
-
318
- return rows.map(r => formatObservation(r)).join("\n\n---\n\n");
319
- }),
320
- }),
321
- };
222
+ args: {
223
+ query: tool.schema.string().optional().describe("Search query or #id references"),
224
+ type: tool.schema.string().optional().describe("Optional observation type filter"),
225
+ limit: tool.schema.number().optional().describe("Maximum results, default 10"),
226
+ file: tool.schema
227
+ .string()
228
+ .optional()
229
+ .describe("Memory file path (e.g. persona/default, scenes/<id>)"),
230
+ },
231
+ execute: withDBErrorHandling(async (args) => {
232
+ const limit = Math.min(args.limit ?? 10, 50);
233
+
234
+ // --- File mode: read a memory file by path ---
235
+ if (args.file) {
236
+ const filePath = String(args.file);
237
+ const row = getMemoryFile(filePath);
238
+ if (!row) return `Memory file not found: ${filePath}`;
239
+ return row.content;
240
+ }
241
+
242
+ // --- ID refs mode: resolve #N references ---
243
+ if (args.query) {
244
+ const query = String(args.query);
245
+ const idRefs = [...query.matchAll(/#(\d+)/g)].map((m) => parseInt(m[1], 10));
246
+ if (idRefs.length > 0) {
247
+ const rows = idRefs
248
+ .map((id) => getObservationById(id))
249
+ .filter((r): r is NonNullable<typeof r> => r !== null);
250
+ if (rows.length === 0) return "No observations found for the requested IDs.";
251
+ return rows.map((r) => formatObservation(r)).join("\n\n---\n\n");
252
+ }
253
+ }
254
+
255
+ // --- Search mode ---
256
+ const query = args.query ? String(args.query).trim() : "";
257
+ const scope = (args.type ?? "observations") as string;
258
+ const typeFilter = VALID_TYPES.includes(scope as ObservationType)
259
+ ? (scope as ObservationType)
260
+ : undefined;
261
+
262
+ let rows;
263
+ try {
264
+ rows = searchObservationsFTS(query, { type: typeFilter, limit });
265
+ } catch {
266
+ return "Search failed.";
267
+ }
268
+
269
+ if (rows.length === 0) return "No matching observations found.";
270
+
271
+ // Track retrieval
272
+ markObservationsRetrieved(rows.map((r) => r.id));
273
+
274
+ return rows.map((r) => formatObservation(r)).join("\n\n---\n\n");
275
+ }),
276
+ }),
277
+ };
322
278
  }