opencodekit 0.23.0 → 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 +15 -0
- package/dist/template/.opencode/command/init.md +198 -34
- package/dist/template/.opencode/context/fallow.md +137 -0
- package/dist/template/.opencode/dcp-prompts/overrides/compress-range.md +89 -0
- package/dist/template/.opencode/opencode.json +110 -315
- package/dist/template/.opencode/plugin/README.md +10 -0
- 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/session-summary.ts +542 -0
- 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 +2 -2
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { upsertMemoryFile } from "./db/maintenance.js";
|
|
19
19
|
import type { ObservationRow } from "./db/types.js";
|
|
20
|
-
import { getMemoryDB } from "./db.js";
|
|
20
|
+
import { getMemoryDB } from "./db/schema.js";
|
|
21
21
|
import { TYPE_ICONS, parseConcepts } from "./helpers.js";
|
|
22
22
|
import { appendOperationLog } from "./operation-log.js";
|
|
23
23
|
|
|
@@ -26,25 +26,25 @@ import { appendOperationLog } from "./operation-log.js";
|
|
|
26
26
|
// ============================================================================
|
|
27
27
|
|
|
28
28
|
export interface ConceptCluster {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
concept: string;
|
|
30
|
+
observations: Pick<
|
|
31
|
+
ObservationRow,
|
|
32
|
+
"id" | "type" | "title" | "narrative" | "confidence" | "created_at"
|
|
33
|
+
>[];
|
|
34
|
+
relatedConcepts: string[];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export interface CompiledArticle {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
concept: string;
|
|
39
|
+
content: string;
|
|
40
|
+
observationCount: number;
|
|
41
|
+
relatedConcepts: string[];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface CompileResult {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
articles: CompiledArticle[];
|
|
46
|
+
totalObservations: number;
|
|
47
|
+
skippedClusters: number;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// ============================================================================
|
|
@@ -57,43 +57,43 @@ export interface CompileResult {
|
|
|
57
57
|
* Stores articles in memory_files as "compiled/{concept}".
|
|
58
58
|
*/
|
|
59
59
|
export function compileObservations(
|
|
60
|
-
|
|
60
|
+
options: { minObservations?: number; maxArticles?: number } = {},
|
|
61
61
|
): CompileResult {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
62
|
+
const minObs = options.minObservations ?? 3;
|
|
63
|
+
const maxArticles = options.maxArticles ?? 20;
|
|
64
|
+
|
|
65
|
+
const clusters = buildConceptClusters(minObs);
|
|
66
|
+
const articles: CompiledArticle[] = [];
|
|
67
|
+
let skipped = 0;
|
|
68
|
+
|
|
69
|
+
// Sort clusters by observation count (most connected first)
|
|
70
|
+
const sortedClusters = clusters
|
|
71
|
+
.sort((a, b) => b.observations.length - a.observations.length)
|
|
72
|
+
.slice(0, maxArticles);
|
|
73
|
+
|
|
74
|
+
let totalObservations = 0;
|
|
75
|
+
|
|
76
|
+
for (const cluster of sortedClusters) {
|
|
77
|
+
const article = compileCluster(cluster);
|
|
78
|
+
if (article) {
|
|
79
|
+
articles.push(article);
|
|
80
|
+
totalObservations += article.observationCount;
|
|
81
|
+
// Store in memory_files
|
|
82
|
+
const safeName = cluster.concept.replace(/[^a-z0-9-]/g, "-");
|
|
83
|
+
upsertMemoryFile(`compiled/${safeName}`, article.content, "replace");
|
|
84
|
+
} else {
|
|
85
|
+
skipped++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Log the operation
|
|
90
|
+
appendOperationLog({
|
|
91
|
+
operation: "compile-run",
|
|
92
|
+
targets: articles.map((a) => a.concept),
|
|
93
|
+
summary: `Compiled ${articles.length} articles from ${totalObservations} observations (${skipped} clusters skipped)`,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return { articles, totalObservations, skippedClusters: skipped };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// ============================================================================
|
|
@@ -104,150 +104,135 @@ export function compileObservations(
|
|
|
104
104
|
* Build concept clusters from active observations.
|
|
105
105
|
*/
|
|
106
106
|
function buildConceptClusters(minObservations: number): ConceptCluster[] {
|
|
107
|
-
|
|
107
|
+
const db = getMemoryDB();
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
const observations = db
|
|
110
|
+
.query(
|
|
111
|
+
`SELECT id, type, title, narrative, concepts, confidence, created_at
|
|
112
112
|
FROM observations
|
|
113
113
|
WHERE superseded_by IS NULL AND concepts IS NOT NULL
|
|
114
114
|
ORDER BY created_at_epoch DESC`,
|
|
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
|
-
observations: obsGroup,
|
|
161
|
-
relatedConcepts,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return clusters;
|
|
115
|
+
)
|
|
116
|
+
.all() as Pick<
|
|
117
|
+
ObservationRow,
|
|
118
|
+
"id" | "type" | "title" | "narrative" | "concepts" | "confidence" | "created_at"
|
|
119
|
+
>[];
|
|
120
|
+
|
|
121
|
+
// Build concept → observations map
|
|
122
|
+
const conceptMap = new Map<string, typeof observations>();
|
|
123
|
+
|
|
124
|
+
for (const obs of observations) {
|
|
125
|
+
const concepts = parseConcepts(obs.concepts);
|
|
126
|
+
for (const concept of concepts) {
|
|
127
|
+
const group = conceptMap.get(concept) ?? [];
|
|
128
|
+
group.push(obs);
|
|
129
|
+
conceptMap.set(concept, group);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Filter to clusters with enough observations
|
|
134
|
+
const clusters: ConceptCluster[] = [];
|
|
135
|
+
for (const [concept, obsGroup] of conceptMap) {
|
|
136
|
+
if (obsGroup.length < minObservations) continue;
|
|
137
|
+
|
|
138
|
+
// Find related concepts (co-occurring)
|
|
139
|
+
const relatedCounts = new Map<string, number>();
|
|
140
|
+
for (const obs of obsGroup) {
|
|
141
|
+
const concepts = parseConcepts(obs.concepts);
|
|
142
|
+
for (const c of concepts) {
|
|
143
|
+
if (c === concept) continue;
|
|
144
|
+
relatedCounts.set(c, (relatedCounts.get(c) ?? 0) + 1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const relatedConcepts = [...relatedCounts.entries()]
|
|
148
|
+
.filter(([, count]) => count >= 2)
|
|
149
|
+
.sort((a, b) => b[1] - a[1])
|
|
150
|
+
.map(([c]) => c);
|
|
151
|
+
|
|
152
|
+
clusters.push({
|
|
153
|
+
concept,
|
|
154
|
+
observations: obsGroup,
|
|
155
|
+
relatedConcepts,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return clusters;
|
|
166
160
|
}
|
|
167
161
|
|
|
168
162
|
/**
|
|
169
163
|
* Compile a single concept cluster into a markdown article.
|
|
170
164
|
*/
|
|
171
165
|
function compileCluster(cluster: ConceptCluster): CompiledArticle | null {
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
concept: cluster.concept,
|
|
248
|
-
content: lines.join("\n"),
|
|
249
|
-
observationCount: cluster.observations.length,
|
|
250
|
-
relatedConcepts: cluster.relatedConcepts,
|
|
251
|
-
};
|
|
166
|
+
if (cluster.observations.length === 0) return null;
|
|
167
|
+
|
|
168
|
+
const lines: string[] = [];
|
|
169
|
+
|
|
170
|
+
// Header
|
|
171
|
+
lines.push(`# ${cluster.concept}`);
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push(`> Compiled from ${cluster.observations.length} observations.`);
|
|
174
|
+
lines.push(`> Last compiled: ${new Date().toISOString().slice(0, 19)}`);
|
|
175
|
+
|
|
176
|
+
// Related concepts
|
|
177
|
+
if (cluster.relatedConcepts.length > 0) {
|
|
178
|
+
lines.push(`> Related: ${cluster.relatedConcepts.join(", ")}`);
|
|
179
|
+
}
|
|
180
|
+
lines.push("");
|
|
181
|
+
|
|
182
|
+
// Group observations by type for structure
|
|
183
|
+
const byType = new Map<string, typeof cluster.observations>();
|
|
184
|
+
for (const obs of cluster.observations) {
|
|
185
|
+
const group = byType.get(obs.type) ?? [];
|
|
186
|
+
group.push(obs);
|
|
187
|
+
byType.set(obs.type, group);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Decisions first (most important)
|
|
191
|
+
const typeOrder = [
|
|
192
|
+
"decision",
|
|
193
|
+
"pattern",
|
|
194
|
+
"warning",
|
|
195
|
+
"bugfix",
|
|
196
|
+
"discovery",
|
|
197
|
+
"feature",
|
|
198
|
+
"learning",
|
|
199
|
+
];
|
|
200
|
+
for (const type of typeOrder) {
|
|
201
|
+
const group = byType.get(type);
|
|
202
|
+
if (!group || group.length === 0) continue;
|
|
203
|
+
|
|
204
|
+
const icon = TYPE_ICONS[type] ?? "📌";
|
|
205
|
+
const heading = type.charAt(0).toUpperCase() + type.slice(1);
|
|
206
|
+
lines.push(`## ${icon} ${heading}s`);
|
|
207
|
+
lines.push("");
|
|
208
|
+
|
|
209
|
+
for (const obs of group) {
|
|
210
|
+
lines.push(`### #${obs.id}: ${obs.title}`);
|
|
211
|
+
if (obs.narrative) {
|
|
212
|
+
// Truncate long narratives for the compiled view
|
|
213
|
+
const narrative =
|
|
214
|
+
obs.narrative.length > 500 ? `${obs.narrative.slice(0, 500)}...` : obs.narrative;
|
|
215
|
+
lines.push("");
|
|
216
|
+
lines.push(narrative);
|
|
217
|
+
}
|
|
218
|
+
lines.push("");
|
|
219
|
+
lines.push(`_Confidence: ${obs.confidence} | Created: ${obs.created_at.slice(0, 10)}_`);
|
|
220
|
+
lines.push("");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Cross-reference footer
|
|
225
|
+
lines.push("---");
|
|
226
|
+
lines.push("");
|
|
227
|
+
lines.push(`**Source observations:** ${cluster.observations.map((o) => `#${o.id}`).join(", ")}`);
|
|
228
|
+
if (cluster.relatedConcepts.length > 0) {
|
|
229
|
+
lines.push(`**See also:** ${cluster.relatedConcepts.map((c) => `[[${c}]]`).join(", ")}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
concept: cluster.concept,
|
|
234
|
+
content: lines.join("\n"),
|
|
235
|
+
observationCount: cluster.observations.length,
|
|
236
|
+
relatedConcepts: cluster.relatedConcepts,
|
|
237
|
+
};
|
|
252
238
|
}
|
|
253
|
-
|