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.
Files changed (23) hide show
  1. package/dist/index.js +354 -825
  2. package/dist/template/.opencode/AGENTS.md +15 -0
  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/dcp-prompts/overrides/compress-range.md +89 -0
  6. package/dist/template/.opencode/opencode.json +110 -315
  7. package/dist/template/.opencode/plugin/README.md +10 -0
  8. package/dist/template/.opencode/plugin/memory/compile.ts +171 -186
  9. package/dist/template/.opencode/plugin/memory/index-generator.ts +118 -133
  10. package/dist/template/.opencode/plugin/memory/lint.ts +253 -275
  11. package/dist/template/.opencode/plugin/memory/tools.ts +224 -268
  12. package/dist/template/.opencode/plugin/memory/validate.ts +154 -164
  13. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-preview.ts +13 -30
  14. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-shared.ts +25 -0
  15. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search.ts +17 -34
  16. package/dist/template/.opencode/plugin/session-summary.ts +542 -0
  17. package/dist/template/.opencode/plugin/srcwalk.ts +775 -661
  18. package/dist/template/.opencode/skill/condition-based-waiting/example.ts +15 -2
  19. package/dist/template/.opencode/skill/fallow/SKILL.md +409 -0
  20. package/dist/template/.opencode/skill/fallow/references/cli-reference.md +1905 -0
  21. package/dist/template/.opencode/skill/fallow/references/gotchas.md +644 -0
  22. package/dist/template/.opencode/skill/fallow/references/patterns.md +791 -0
  23. 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
- concept: string;
30
- observations: Pick<
31
- ObservationRow,
32
- "id" | "type" | "title" | "narrative" | "confidence" | "created_at"
33
- >[];
34
- relatedConcepts: string[];
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
- concept: string;
39
- content: string;
40
- observationCount: number;
41
- relatedConcepts: string[];
38
+ concept: string;
39
+ content: string;
40
+ observationCount: number;
41
+ relatedConcepts: string[];
42
42
  }
43
43
 
44
44
  export interface CompileResult {
45
- articles: CompiledArticle[];
46
- totalObservations: number;
47
- skippedClusters: number;
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
- options: { minObservations?: number; maxArticles?: number } = {},
60
+ options: { minObservations?: number; maxArticles?: number } = {},
61
61
  ): CompileResult {
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 };
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
- const db = getMemoryDB();
107
+ const db = getMemoryDB();
108
108
 
109
- const observations = db
110
- .query(
111
- `SELECT id, type, title, narrative, concepts, confidence, created_at
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
- .all() as Pick<
117
- ObservationRow,
118
- | "id"
119
- | "type"
120
- | "title"
121
- | "narrative"
122
- | "concepts"
123
- | "confidence"
124
- | "created_at"
125
- >[];
126
-
127
- // Build concept observations map
128
- const conceptMap = new Map<string, typeof observations>();
129
-
130
- for (const obs of observations) {
131
- const concepts = parseConcepts(obs.concepts);
132
- for (const concept of concepts) {
133
- const group = conceptMap.get(concept) ?? [];
134
- group.push(obs);
135
- conceptMap.set(concept, group);
136
- }
137
- }
138
-
139
- // Filter to clusters with enough observations
140
- const clusters: ConceptCluster[] = [];
141
- for (const [concept, obsGroup] of conceptMap) {
142
- if (obsGroup.length < minObservations) continue;
143
-
144
- // Find related concepts (co-occurring)
145
- const relatedCounts = new Map<string, number>();
146
- for (const obs of obsGroup) {
147
- const concepts = parseConcepts(obs.concepts);
148
- for (const c of concepts) {
149
- if (c === concept) continue;
150
- relatedCounts.set(c, (relatedCounts.get(c) ?? 0) + 1);
151
- }
152
- }
153
- const relatedConcepts = [...relatedCounts.entries()]
154
- .filter(([, count]) => count >= 2)
155
- .sort((a, b) => b[1] - a[1])
156
- .map(([c]) => c);
157
-
158
- clusters.push({
159
- concept,
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
- if (cluster.observations.length === 0) return null;
173
-
174
- const lines: string[] = [];
175
-
176
- // Header
177
- lines.push(`# ${cluster.concept}`);
178
- lines.push("");
179
- lines.push(`> Compiled from ${cluster.observations.length} observations.`);
180
- lines.push(`> Last compiled: ${new Date().toISOString().slice(0, 19)}`);
181
-
182
- // Related concepts
183
- if (cluster.relatedConcepts.length > 0) {
184
- lines.push(`> Related: ${cluster.relatedConcepts.join(", ")}`);
185
- }
186
- lines.push("");
187
-
188
- // Group observations by type for structure
189
- const byType = new Map<string, typeof cluster.observations>();
190
- for (const obs of cluster.observations) {
191
- const group = byType.get(obs.type) ?? [];
192
- group.push(obs);
193
- byType.set(obs.type, group);
194
- }
195
-
196
- // Decisions first (most important)
197
- const typeOrder = [
198
- "decision",
199
- "pattern",
200
- "warning",
201
- "bugfix",
202
- "discovery",
203
- "feature",
204
- "learning",
205
- ];
206
- for (const type of typeOrder) {
207
- const group = byType.get(type);
208
- if (!group || group.length === 0) continue;
209
-
210
- const icon = TYPE_ICONS[type] ?? "📌";
211
- const heading = type.charAt(0).toUpperCase() + type.slice(1);
212
- lines.push(`## ${icon} ${heading}s`);
213
- lines.push("");
214
-
215
- for (const obs of group) {
216
- lines.push(`### #${obs.id}: ${obs.title}`);
217
- if (obs.narrative) {
218
- // Truncate long narratives for the compiled view
219
- const narrative =
220
- obs.narrative.length > 500
221
- ? `${obs.narrative.slice(0, 500)}...`
222
- : obs.narrative;
223
- lines.push("");
224
- lines.push(narrative);
225
- }
226
- lines.push("");
227
- lines.push(
228
- `_Confidence: ${obs.confidence} | Created: ${obs.created_at.slice(0, 10)}_`,
229
- );
230
- lines.push("");
231
- }
232
- }
233
-
234
- // Cross-reference footer
235
- lines.push("---");
236
- lines.push("");
237
- lines.push(
238
- `**Source observations:** ${cluster.observations.map((o) => `#${o.id}`).join(", ")}`,
239
- );
240
- if (cluster.relatedConcepts.length > 0) {
241
- lines.push(
242
- `**See also:** ${cluster.relatedConcepts.map((c) => `[[${c}]]`).join(", ")}`,
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
-