opencodekit 0.20.1 → 0.20.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 (52) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.opencode/agent/build.md +4 -0
  3. package/dist/template/.opencode/agent/explore.md +4 -0
  4. package/dist/template/.opencode/agent/general.md +4 -0
  5. package/dist/template/.opencode/agent/plan.md +4 -0
  6. package/dist/template/.opencode/agent/review.md +4 -0
  7. package/dist/template/.opencode/agent/scout.md +4 -0
  8. package/dist/template/.opencode/command/create.md +119 -25
  9. package/dist/template/.opencode/command/design.md +1 -2
  10. package/dist/template/.opencode/command/health.md +234 -0
  11. package/dist/template/.opencode/command/init-user.md +15 -0
  12. package/dist/template/.opencode/command/plan.md +3 -4
  13. package/dist/template/.opencode/command/pr.md +13 -0
  14. package/dist/template/.opencode/command/research.md +15 -3
  15. package/dist/template/.opencode/command/review-codebase.md +11 -1
  16. package/dist/template/.opencode/command/ship.md +72 -8
  17. package/dist/template/.opencode/command/status.md +1 -1
  18. package/dist/template/.opencode/command/ui-review.md +0 -1
  19. package/dist/template/.opencode/command/ui-slop-check.md +1 -1
  20. package/dist/template/.opencode/command/verify.md +11 -1
  21. package/dist/template/.opencode/dcp.jsonc +31 -24
  22. package/dist/template/.opencode/memory.db +0 -0
  23. package/dist/template/.opencode/memory.db-shm +0 -0
  24. package/dist/template/.opencode/memory.db-wal +0 -0
  25. package/dist/template/.opencode/opencode.json +1678 -1677
  26. package/dist/template/.opencode/package.json +1 -1
  27. package/dist/template/.opencode/plugin/lib/compile.ts +253 -0
  28. package/dist/template/.opencode/plugin/lib/index-generator.ts +170 -0
  29. package/dist/template/.opencode/plugin/lib/lint.ts +359 -0
  30. package/dist/template/.opencode/plugin/lib/memory-admin-tools.ts +42 -1
  31. package/dist/template/.opencode/plugin/lib/memory-db.ts +7 -0
  32. package/dist/template/.opencode/plugin/lib/memory-helpers.ts +30 -0
  33. package/dist/template/.opencode/plugin/lib/memory-hooks.ts +10 -0
  34. package/dist/template/.opencode/plugin/lib/memory-tools.ts +30 -1
  35. package/dist/template/.opencode/plugin/lib/operation-log.ts +109 -0
  36. package/dist/template/.opencode/plugin/lib/validate.ts +243 -0
  37. package/dist/template/.opencode/skill/design-taste-frontend/SKILL.md +13 -1
  38. package/dist/template/.opencode/skill/figma-go/SKILL.md +1 -1
  39. package/dist/template/.opencode/skill/full-output-enforcement/SKILL.md +13 -0
  40. package/dist/template/.opencode/skill/high-end-visual-design/SKILL.md +13 -0
  41. package/dist/template/.opencode/skill/industrial-brutalist-ui/SKILL.md +13 -0
  42. package/dist/template/.opencode/skill/memory-system/SKILL.md +65 -1
  43. package/dist/template/.opencode/skill/minimalist-ui/SKILL.md +13 -0
  44. package/dist/template/.opencode/skill/redesign-existing-projects/SKILL.md +13 -0
  45. package/dist/template/.opencode/skill/requesting-code-review/SKILL.md +48 -2
  46. package/dist/template/.opencode/skill/requesting-code-review/references/specialist-profiles.md +108 -0
  47. package/dist/template/.opencode/skill/skill-creator/SKILL.md +25 -0
  48. package/dist/template/.opencode/skill/stitch-design-taste/SKILL.md +13 -0
  49. package/dist/template/.opencode/skill/verification-before-completion/SKILL.md +46 -0
  50. package/package.json +1 -1
  51. package/dist/template/.opencode/agent/runner.md +0 -79
  52. package/dist/template/.opencode/command/start.md +0 -156
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@google/stitch-sdk": "^0.0.3",
15
- "@opencode-ai/plugin": "1.3.13"
15
+ "@opencode-ai/plugin": "1.3.17"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^25.3.0",
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Memory Compilation — Observations → Structured Articles
3
+ *
4
+ * Inspired by Karpathy's LLM Wiki "compilation" layer:
5
+ * reads clusters of related observations and produces structured
6
+ * summary articles with cross-references. This is the missing 5th stage:
7
+ *
8
+ * capture → distill → curate → **compile** → inject
9
+ *
10
+ * Unlike Karpathy's approach (which uses LLM for compilation), this
11
+ * implementation uses pure heuristics — grouping by concept clusters,
12
+ * ranking by confidence/recency, and templated article generation.
13
+ *
14
+ * For LLM-powered compilation, use the compile admin operation which
15
+ * generates articles the agent can review and edit.
16
+ */
17
+
18
+ import { upsertMemoryFile } from "./db/maintenance.js";
19
+ import type { ObservationRow } from "./db/types.js";
20
+ import { getMemoryDB } from "./memory-db.js";
21
+ import { TYPE_ICONS, parseConcepts } from "./memory-helpers.js";
22
+ import { appendOperationLog } from "./operation-log.js";
23
+
24
+ // ============================================================================
25
+ // Types
26
+ // ============================================================================
27
+
28
+ export interface ConceptCluster {
29
+ concept: string;
30
+ observations: Pick<
31
+ ObservationRow,
32
+ "id" | "type" | "title" | "narrative" | "confidence" | "created_at"
33
+ >[];
34
+ relatedConcepts: string[];
35
+ }
36
+
37
+ export interface CompiledArticle {
38
+ concept: string;
39
+ content: string;
40
+ observationCount: number;
41
+ relatedConcepts: string[];
42
+ }
43
+
44
+ export interface CompileResult {
45
+ articles: CompiledArticle[];
46
+ totalObservations: number;
47
+ skippedClusters: number;
48
+ }
49
+
50
+ // ============================================================================
51
+ // Compilation Operations
52
+ // ============================================================================
53
+
54
+ /**
55
+ * Compile observations into structured articles grouped by concept.
56
+ * Only generates articles for concepts with `minObservations` or more observations.
57
+ * Stores articles in memory_files as "compiled/{concept}".
58
+ */
59
+ export function compileObservations(
60
+ options: { minObservations?: number; maxArticles?: number } = {},
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 };
97
+ }
98
+
99
+ // ============================================================================
100
+ // Internal
101
+ // ============================================================================
102
+
103
+ /**
104
+ * Build concept clusters from active observations.
105
+ */
106
+ function buildConceptClusters(minObservations: number): ConceptCluster[] {
107
+ const db = getMemoryDB();
108
+
109
+ const observations = db
110
+ .query(
111
+ `SELECT id, type, title, narrative, concepts, confidence, created_at
112
+ FROM observations
113
+ WHERE superseded_by IS NULL AND concepts IS NOT NULL
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;
166
+ }
167
+
168
+ /**
169
+ * Compile a single concept cluster into a markdown article.
170
+ */
171
+ 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
+ };
252
+ }
253
+
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Memory Index Generator — Auto-Generated Knowledge Catalog
3
+ *
4
+ * Inspired by Karpathy's LLM Wiki index.md:
5
+ * generates a structured catalog of all observations, grouped by type,
6
+ * with cross-references and concept clusters.
7
+ *
8
+ * Stored in memory_files as "index" for injection or on-demand reading.
9
+ */
10
+
11
+ import { upsertMemoryFile } from "./db/maintenance.js";
12
+ import type { ObservationRow } from "./db/types.js";
13
+ import { getMemoryDB } from "./memory-db.js";
14
+ import { TYPE_ICONS, parseConcepts } from "./memory-helpers.js";
15
+
16
+ // ============================================================================
17
+ // Types
18
+ // ============================================================================
19
+
20
+ export interface IndexEntry {
21
+ id: number;
22
+ type: string;
23
+ title: string;
24
+ concepts: string[];
25
+ created_at: string;
26
+ }
27
+
28
+ export interface IndexResult {
29
+ entryCount: number;
30
+ conceptCount: number;
31
+ content: string;
32
+ }
33
+
34
+ // ============================================================================
35
+ // Index Generation
36
+ // ============================================================================
37
+
38
+ /**
39
+ * Generate a comprehensive index of all active observations.
40
+ * Groups by type, lists concept clusters, and writes to memory_files.
41
+ */
42
+ export function generateMemoryIndex(): IndexResult {
43
+ const db = getMemoryDB();
44
+
45
+ const observations = db
46
+ .query(
47
+ `SELECT id, type, title, concepts, created_at FROM observations
48
+ WHERE superseded_by IS NULL
49
+ ORDER BY type, created_at_epoch DESC`,
50
+ )
51
+ .all() as Pick<
52
+ ObservationRow,
53
+ "id" | "type" | "title" | "concepts" | "created_at"
54
+ >[];
55
+
56
+ // Group by type
57
+ const byType = new Map<string, IndexEntry[]>();
58
+ const allConcepts = new Map<string, number[]>();
59
+
60
+ for (const obs of observations) {
61
+ const entry: IndexEntry = {
62
+ id: obs.id,
63
+ type: obs.type,
64
+ title: obs.title,
65
+ concepts: parseConcepts(obs.concepts),
66
+ created_at: obs.created_at,
67
+ };
68
+
69
+ const group = byType.get(obs.type) ?? [];
70
+ group.push(entry);
71
+ byType.set(obs.type, group);
72
+
73
+ for (const concept of entry.concepts) {
74
+ const ids = allConcepts.get(concept) ?? [];
75
+ ids.push(obs.id);
76
+ allConcepts.set(concept, ids);
77
+ }
78
+ }
79
+
80
+ // Build markdown
81
+ const lines: string[] = [];
82
+ lines.push("# Memory Index");
83
+ lines.push("");
84
+ lines.push(
85
+ `> Auto-generated catalog of ${observations.length} active observations.`,
86
+ );
87
+ lines.push(`> Last updated: ${new Date().toISOString().slice(0, 19)}`);
88
+ lines.push("");
89
+
90
+ // Summary table
91
+ lines.push("## Summary");
92
+ lines.push("");
93
+ lines.push("| Type | Count |");
94
+ lines.push("|------|-------|");
95
+ for (const [type, entries] of byType) {
96
+ const icon = TYPE_ICONS[type] ?? "📌";
97
+ lines.push(`| ${icon} ${type} | ${entries.length} |`);
98
+ }
99
+ lines.push(`| **Total** | **${observations.length}** |`);
100
+ lines.push("");
101
+
102
+ // By type
103
+ for (const [type, entries] of byType) {
104
+ const icon = TYPE_ICONS[type] ?? "📌";
105
+ lines.push(
106
+ `## ${icon} ${type.charAt(0).toUpperCase() + type.slice(1)} (${entries.length})`,
107
+ );
108
+ lines.push("");
109
+ for (const entry of entries) {
110
+ const concepts =
111
+ entry.concepts.length > 0 ? ` [${entry.concepts.join(", ")}]` : "";
112
+ lines.push(
113
+ `- **#${entry.id}** ${entry.title}${concepts} _(${entry.created_at.slice(0, 10)})_`,
114
+ );
115
+ }
116
+ lines.push("");
117
+ }
118
+
119
+ // Concept clusters
120
+ const significantConcepts = [...allConcepts.entries()]
121
+ .filter(([, ids]) => ids.length >= 2)
122
+ .sort((a, b) => b[1].length - a[1].length);
123
+
124
+ if (significantConcepts.length > 0) {
125
+ lines.push("## Concept Clusters");
126
+ lines.push("");
127
+ lines.push("Concepts appearing in 2+ observations:");
128
+ lines.push("");
129
+ for (const [concept, ids] of significantConcepts.slice(0, 30)) {
130
+ lines.push(
131
+ `- **${concept}** (${ids.length}): ${ids.map((id) => `#${id}`).join(", ")}`,
132
+ );
133
+ }
134
+ lines.push("");
135
+ }
136
+
137
+ // Orphan concepts
138
+ const orphanConcepts = [...allConcepts.entries()].filter(
139
+ ([, ids]) => ids.length === 1,
140
+ );
141
+
142
+ if (orphanConcepts.length > 0) {
143
+ lines.push("## Orphan Concepts");
144
+ lines.push("");
145
+ lines.push(
146
+ `${orphanConcepts.length} concepts appear in only 1 observation:`,
147
+ );
148
+ lines.push("");
149
+ const orphanList = orphanConcepts
150
+ .slice(0, 20)
151
+ .map(([concept, ids]) => `${concept} (#${ids[0]})`);
152
+ lines.push(orphanList.join(", "));
153
+ if (orphanConcepts.length > 20) {
154
+ lines.push(`... and ${orphanConcepts.length - 20} more`);
155
+ }
156
+ lines.push("");
157
+ }
158
+
159
+ const content = lines.join("\n");
160
+
161
+ // Store in memory_files
162
+ upsertMemoryFile("index", content, "replace");
163
+
164
+ return {
165
+ entryCount: observations.length,
166
+ conceptCount: allConcepts.size,
167
+ content,
168
+ };
169
+ }
170
+