opencodekit 0.23.1 → 0.23.2
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/dist/index.js +354 -825
- package/dist/template/.opencode/AGENTS.md +12 -0
- package/dist/template/.opencode/command/init.md +198 -34
- package/dist/template/.opencode/context/fallow.md +137 -0
- package/dist/template/.opencode/opencode.json +12 -315
- package/dist/template/.opencode/plugin/memory/compile.ts +171 -186
- package/dist/template/.opencode/plugin/memory/index-generator.ts +118 -133
- package/dist/template/.opencode/plugin/memory/lint.ts +253 -275
- package/dist/template/.opencode/plugin/memory/tools.ts +224 -268
- package/dist/template/.opencode/plugin/memory/validate.ts +154 -164
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-preview.ts +13 -30
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-shared.ts +25 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search.ts +17 -34
- package/dist/template/.opencode/plugin/srcwalk.ts +775 -661
- package/dist/template/.opencode/skill/condition-based-waiting/example.ts +15 -2
- package/dist/template/.opencode/skill/fallow/SKILL.md +409 -0
- package/dist/template/.opencode/skill/fallow/references/cli-reference.md +1905 -0
- package/dist/template/.opencode/skill/fallow/references/gotchas.md +644 -0
- package/dist/template/.opencode/skill/fallow/references/patterns.md +791 -0
- 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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
38
|
+
handler: (args: T) => string | Promise<string>,
|
|
40
39
|
): (args: T, context: ToolContext) => Promise<string> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
55
|
+
handoffDir: string;
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
export function createCoreTools(deps: CoreToolDeps) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
+
? `\n⚠️ Warnings: ${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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
}
|