opencodekit 0.16.0 → 0.16.1

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 (119) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.opencode/AGENTS.md +64 -3
  3. package/dist/template/.opencode/command/create.md +34 -0
  4. package/dist/template/.opencode/command/design.md +35 -0
  5. package/dist/template/.opencode/command/handoff.md +15 -0
  6. package/dist/template/.opencode/command/init.md +40 -47
  7. package/dist/template/.opencode/command/plan.md +1 -0
  8. package/dist/template/.opencode/command/pr.md +15 -0
  9. package/dist/template/.opencode/command/research.md +3 -0
  10. package/dist/template/.opencode/command/resume.md +1 -0
  11. package/dist/template/.opencode/command/review-codebase.md +30 -0
  12. package/dist/template/.opencode/command/ship.md +43 -0
  13. package/dist/template/.opencode/command/start.md +1 -0
  14. package/dist/template/.opencode/command/status.md +24 -1
  15. package/dist/template/.opencode/command/ui-review.md +31 -0
  16. package/dist/template/.opencode/command/verify.md +35 -7
  17. package/dist/template/.opencode/memory/project/tech-stack.md +25 -22
  18. package/dist/template/.opencode/memory.db +0 -0
  19. package/dist/template/.opencode/memory.db-shm +0 -0
  20. package/dist/template/.opencode/memory.db-wal +0 -0
  21. package/dist/template/.opencode/opencode.json +817 -916
  22. package/dist/template/.opencode/package.json +1 -0
  23. package/dist/template/.opencode/plans/1770006237537-mighty-otter.md +418 -0
  24. package/dist/template/.opencode/plans/1770006913647-glowing-forest.md +170 -0
  25. package/dist/template/.opencode/plans/1770013678126-witty-planet.md +278 -0
  26. package/dist/template/.opencode/plugin/lib/memory-db.ts +828 -0
  27. package/dist/template/.opencode/plugin/memory.ts +38 -1
  28. package/dist/template/.opencode/skill/index-knowledge/SKILL.md +76 -31
  29. package/dist/template/.opencode/skill/memory-system/SKILL.md +110 -55
  30. package/dist/template/.opencode/tool/memory-get.ts +143 -0
  31. package/dist/template/.opencode/tool/memory-maintain.ts +167 -0
  32. package/dist/template/.opencode/tool/memory-migrate.ts +319 -0
  33. package/dist/template/.opencode/tool/memory-read.ts +17 -46
  34. package/dist/template/.opencode/tool/memory-search.ts +131 -28
  35. package/dist/template/.opencode/tool/memory-timeline.ts +105 -0
  36. package/dist/template/.opencode/tool/memory-update.ts +21 -26
  37. package/dist/template/.opencode/tool/observation.ts +112 -100
  38. package/dist/template/.opencode/tsconfig.json +19 -19
  39. package/package.json +1 -1
  40. package/dist/template/.opencode/memory/_templates/README.md +0 -73
  41. package/dist/template/.opencode/memory/_templates/observation.md +0 -39
  42. package/dist/template/.opencode/memory/_templates/prompt-engineering.md +0 -333
  43. package/dist/template/.opencode/memory/observations/2026-01-22-decision-agents-md-prompt-engineering-improvement.md +0 -29
  44. package/dist/template/.opencode/memory/observations/2026-01-25-decision-agent-roles-build-orchestrates-general-e.md +0 -14
  45. package/dist/template/.opencode/memory/observations/2026-01-25-decision-simplified-swarm-helper-tool-to-fix-type.md +0 -20
  46. package/dist/template/.opencode/memory/observations/2026-01-25-decision-use-beads-as-swarm-board-source-of-truth.md +0 -14
  47. package/dist/template/.opencode/memory/observations/2026-01-25-learning-user-wants-real-swarm-coordination-guida.md +0 -15
  48. package/dist/template/.opencode/memory/observations/2026-01-28-decision-created-deep-research-skill-for-thorough.md +0 -29
  49. package/dist/template/.opencode/memory/observations/2026-01-28-decision-gh-grep-mcp-wrapper-vs-native-grep-searc.md +0 -21
  50. package/dist/template/.opencode/memory/observations/2026-01-28-decision-oracle-tool-optimal-usage-patterns.md +0 -32
  51. package/dist/template/.opencode/memory/observations/2026-01-28-learning-ampcode-deep-mode-research-integration-w.md +0 -42
  52. package/dist/template/.opencode/memory/observations/2026-01-28-pattern-research-delegation-pattern-explore-for-.md +0 -32
  53. package/dist/template/.opencode/memory/observations/2026-01-29-decision-copilot-auth-plugin-rate-limit-handling.md +0 -27
  54. package/dist/template/.opencode/memory/observations/2026-01-29-decision-spec-driven-approach-for-opencodekit.md +0 -21
  55. package/dist/template/.opencode/memory/observations/2026-01-29-learning-karpathy-llm-coding-insights-dec-2025.md +0 -44
  56. package/dist/template/.opencode/memory/observations/2026-01-30-decision-github-copilot-claude-routing-keep-disab.md +0 -32
  57. package/dist/template/.opencode/memory/observations/2026-01-30-discovery-context-management-research-critical-gap.md +0 -14
  58. package/dist/template/.opencode/memory/observations/2026-01-30-discovery-kimi-k2-5-agent-swarm-architecture-patte.md +0 -45
  59. package/dist/template/.opencode/memory/observations/2026-01-30-pattern-swarm-tools-architecture.md +0 -28
  60. package/dist/template/.opencode/memory/observations/2026-01-31-decision-copilot-auth-plugin-updated-with-baseurl.md +0 -63
  61. package/dist/template/.opencode/memory/observations/2026-01-31-decision-created-dedicated-worker-agent-for-swarm.md +0 -20
  62. package/dist/template/.opencode/memory/observations/2026-01-31-decision-rollback-to-v1-1-47-for-copilot-claude-r.md +0 -21
  63. package/dist/template/.opencode/memory/observations/2026-01-31-decision-simplified-swarm-to-task-tool-pattern.md +0 -44
  64. package/dist/template/.opencode/memory/observations/2026-01-31-decision-swarm-architecture-task-tool-over-tmux.md +0 -33
  65. package/dist/template/.opencode/memory/observations/2026-01-31-decision-worker-skills-defined-for-swarm-delegati.md +0 -30
  66. package/dist/template/.opencode/memory/observations/2026-01-31-learning-gpt-reasoning-config-for-github-copilot.md +0 -51
  67. package/dist/template/.opencode/memory/observations/2026-01-31-learning-opencode-copilot-auth-comparison-finding.md +0 -61
  68. package/dist/template/.opencode/memory/observations/2026-01-31-learning-opencode-copilot-reasoning-architecture-.md +0 -66
  69. package/dist/template/.opencode/memory/observations/2026-01-31-learning-opencode-custom-tools-api.md +0 -48
  70. package/dist/template/.opencode/memory/observations/2026-01-31-learning-opencode-v1-1-48-skills-as-slash-command.md +0 -21
  71. package/dist/template/.opencode/memory/observations/2026-01-31-learning-swarm-system-simplified-removed-mailbox-.md +0 -30
  72. package/dist/template/.opencode/memory/observations/2026-01-31-learning-v1-1-48-native-copilot-reasoning-via-pr-.md +0 -45
  73. package/dist/template/.opencode/memory/observations/2026-01-31-warning-cannot-add-custom-config-to-opencode-jso.md +0 -18
  74. package/dist/template/.opencode/memory/observations/2026-01-31-warning-copilot-claude-v1-endpoint-returns-404-c.md +0 -48
  75. package/dist/template/.opencode/memory/observations/2026-01-31-warning-opencode-v1-1-48-claude-thinking-block-s.md +0 -51
  76. package/dist/template/.opencode/memory/observations/2026-02-01-decision-add-skills-vs-commands-to-global-agents-.md +0 -15
  77. package/dist/template/.opencode/memory/observations/2026-02-01-decision-build-agent-auto-loads-skills-contextual.md +0 -31
  78. package/dist/template/.opencode/memory/observations/2026-02-01-decision-fixed-agent-configuration-for-opencodeki.md +0 -25
  79. package/dist/template/.opencode/memory/observations/2026-02-01-decision-focused-agents-md-upgrade-for-opencode-k.md +0 -14
  80. package/dist/template/.opencode/memory/observations/2026-02-01-decision-implement-tier-1-permission-upgrades.md +0 -15
  81. package/dist/template/.opencode/memory/observations/2026-02-01-decision-instructions-config-explicit-paths-not-w.md +0 -40
  82. package/dist/template/.opencode/memory/observations/2026-02-01-decision-merged-context-into-memory-project-singl.md +0 -42
  83. package/dist/template/.opencode/memory/observations/2026-02-01-decision-oracle-tool-should-use-review-agent-not-.md +0 -14
  84. package/dist/template/.opencode/memory/observations/2026-02-01-decision-plan-agent-auto-loads-skills-contextuall.md +0 -31
  85. package/dist/template/.opencode/memory/observations/2026-02-01-decision-plan-phased-oracle-command-merge-into-ne.md +0 -14
  86. package/dist/template/.opencode/memory/observations/2026-02-01-decision-prd-workflow-uses-prd-and-prd-task-skill.md +0 -23
  87. package/dist/template/.opencode/memory/observations/2026-02-01-decision-prefer-review-agent-via-opencode-cli-ove.md +0 -14
  88. package/dist/template/.opencode/memory/observations/2026-02-01-decision-remove-oracle-tool-add-ship-command-with.md +0 -14
  89. package/dist/template/.opencode/memory/observations/2026-02-01-decision-remove-oracle-tool-and-add-ship-command-.md +0 -14
  90. package/dist/template/.opencode/memory/observations/2026-02-01-decision-remove-oracle-tool-and-add-ship-command.md +0 -14
  91. package/dist/template/.opencode/memory/observations/2026-02-01-decision-remove-skills-vs-commands-section-from-a.md +0 -14
  92. package/dist/template/.opencode/memory/observations/2026-02-01-decision-replace-oracle-tool-with-ship-command-fl.md +0 -14
  93. package/dist/template/.opencode/memory/observations/2026-02-01-decision-replace-oracle-with-ship-command-workflo.md +0 -14
  94. package/dist/template/.opencode/memory/observations/2026-02-01-decision-replace-proxypal-oracle-with-cli-review-.md +0 -14
  95. package/dist/template/.opencode/memory/observations/2026-02-01-decision-simplified-dist-template-only-tech-stack.md +0 -50
  96. package/dist/template/.opencode/memory/observations/2026-02-01-decision-simplified-templates-only-tech-stack-md.md +0 -26
  97. package/dist/template/.opencode/memory/observations/2026-02-01-decision-subagents-load-minimal-skills-stay-lean.md +0 -29
  98. package/dist/template/.opencode/memory/observations/2026-02-01-decision-user-approved-permission-upgrades-in-ope.md +0 -15
  99. package/dist/template/.opencode/memory/observations/2026-02-01-discovery-verify-command-already-implemented.md +0 -28
  100. package/dist/template/.opencode/memory/observations/2026-02-01-feature-openspec-phase-b-complete-template-upgra.md +0 -43
  101. package/dist/template/.opencode/memory/observations/2026-02-01-learning-build-agent-should-use-dynamic-lsp-not-f.md +0 -14
  102. package/dist/template/.opencode/memory/observations/2026-02-01-learning-kimi-k2-5-model-requires-temperature-1-0.md +0 -22
  103. package/dist/template/.opencode/memory/observations/2026-02-01-learning-opencode-context-injection-already-imple.md +0 -27
  104. package/dist/template/.opencode/memory/observations/2026-02-01-learning-opencode-context-injection-uses-instruct.md +0 -35
  105. package/dist/template/.opencode/memory/observations/2026-02-01-learning-update-build-agent-prompt-to-use-context.md +0 -14
  106. package/dist/template/.opencode/memory/observations/2026-02-01-learning-upgrade-agents-md-using-opencode-expert-.md +0 -14
  107. package/dist/template/.opencode/memory/observations/2026-02-01-learning-upgrade-agents-md-with-opencode-expert-g.md +0 -14
  108. package/dist/template/.opencode/memory/observations/2026-02-01-learning-upgrade-agents-md-with-opencode-expert-r.md +0 -14
  109. package/dist/template/.opencode/memory/observations/2026-02-01-learning-user-prefers-copilot-gpt-5-2-codex-mediu.md +0 -14
  110. package/dist/template/.opencode/memory/observations/2026-02-01-learning-user-wants-general-agent-prompt-contextu.md +0 -15
  111. package/dist/template/.opencode/memory/observations/2026-02-01-learning-user-wants-general-agent-prompt-reviewed.md +0 -15
  112. package/dist/template/.opencode/memory/project/architecture.md +0 -60
  113. package/dist/template/.opencode/memory/project/command-rules.md +0 -122
  114. package/dist/template/.opencode/memory/project/commands.md +0 -72
  115. package/dist/template/.opencode/memory/project/conventions.md +0 -68
  116. package/dist/template/.opencode/memory/project/gotchas.md +0 -41
  117. /package/dist/template/.opencode/memory/_templates/{project/tech-stack.md → tech-stack.md} +0 -0
  118. /package/dist/template/.opencode/memory/{user.example.md → _templates/user.md} +0 -0
  119. /package/dist/template/.opencode/memory/{user.md → project/user.md} +0 -0
@@ -0,0 +1,167 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import {
3
+ archiveOldObservations,
4
+ checkpointWAL,
5
+ getDatabaseSizes,
6
+ getObservationStats,
7
+ runFullMaintenance,
8
+ vacuumDatabase,
9
+ } from "../plugin/lib/memory-db.js";
10
+
11
+ export default tool({
12
+ description: `Maintain and cleanup the memory system for long-term storage health.
13
+
14
+ Purpose:
15
+ - Archive old/superseded observations (default: >90 days)
16
+ - Checkpoint WAL file back to main database
17
+ - Vacuum database to reclaim space and defragment
18
+ - Optimize FTS5 search index
19
+
20
+ Operations:
21
+ - "status": Show current storage stats (default)
22
+ - "full": Run full maintenance cycle
23
+ - "archive": Only archive old observations
24
+ - "checkpoint": Only checkpoint WAL
25
+ - "vacuum": Only vacuum database
26
+
27
+ Example:
28
+ memory-maintain({ operation: "status" })
29
+ memory-maintain({ operation: "full", older_than_days: 60 })
30
+ memory-maintain({ operation: "archive", dry_run: true })`,
31
+ args: {
32
+ operation: tool.schema
33
+ .string()
34
+ .optional()
35
+ .default("status")
36
+ .describe("Operation: status, full, archive, checkpoint, vacuum"),
37
+ older_than_days: tool.schema
38
+ .number()
39
+ .optional()
40
+ .default(90)
41
+ .describe("Archive observations older than this many days (default: 90)"),
42
+ dry_run: tool.schema
43
+ .boolean()
44
+ .optional()
45
+ .default(false)
46
+ .describe("Preview changes without executing"),
47
+ },
48
+ execute: async (args: {
49
+ operation?: string;
50
+ older_than_days?: number;
51
+ dry_run?: boolean;
52
+ }) => {
53
+ const operation = args.operation || "status";
54
+ const olderThanDays = args.older_than_days ?? 90;
55
+ const dryRun = args.dry_run ?? false;
56
+
57
+ const results: string[] = [];
58
+
59
+ // Helper to format bytes
60
+ const formatBytes = (bytes: number): string => {
61
+ if (bytes < 1024) return `${bytes} B`;
62
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
63
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
64
+ };
65
+
66
+ // ===== STATUS =====
67
+ if (operation === "status") {
68
+ const sizes = getDatabaseSizes();
69
+ const stats = getObservationStats();
70
+
71
+ results.push("## Memory System Status\n");
72
+ results.push("### Database Size");
73
+ results.push(`- Main DB: ${formatBytes(sizes.mainDb)}`);
74
+ results.push(`- WAL file: ${formatBytes(sizes.wal)}`);
75
+ results.push(`- **Total: ${formatBytes(sizes.total)}**\n`);
76
+
77
+ results.push("### Observations");
78
+ results.push(`- Total: ${stats.total}`);
79
+ for (const [type, count] of Object.entries(stats)) {
80
+ if (type !== "total") {
81
+ results.push(`- ${type}: ${count}`);
82
+ }
83
+ }
84
+
85
+ // Archive candidates
86
+ const archiveCandidates = archiveOldObservations({
87
+ olderThanDays,
88
+ includeSuperseded: true,
89
+ dryRun: true,
90
+ });
91
+ results.push(`\n### Maintenance Recommendations`);
92
+ results.push(
93
+ `- Archive candidates (>${olderThanDays} days): ${archiveCandidates}`,
94
+ );
95
+ if (sizes.wal > 1024 * 1024) {
96
+ results.push(`- WAL checkpoint recommended (WAL > 1MB)`);
97
+ }
98
+
99
+ return results.join("\n");
100
+ }
101
+
102
+ // ===== FULL MAINTENANCE =====
103
+ if (operation === "full") {
104
+ results.push(
105
+ dryRun ? "## Full Maintenance (DRY RUN)\n" : "## Full Maintenance\n",
106
+ );
107
+
108
+ const stats = runFullMaintenance({
109
+ olderThanDays,
110
+ includeSuperseded: true,
111
+ dryRun,
112
+ });
113
+
114
+ results.push(`### Results`);
115
+ results.push(`- Archived observations: ${stats.archived}`);
116
+ results.push(`- WAL checkpointed: ${stats.checkpointed ? "Yes" : "No"}`);
117
+ results.push(`- Database vacuumed: ${stats.vacuumed ? "Yes" : "No"}`);
118
+ results.push(`- Space before: ${formatBytes(stats.dbSizeBefore)}`);
119
+ results.push(`- Space after: ${formatBytes(stats.dbSizeAfter)}`);
120
+ results.push(`- **Freed: ${formatBytes(stats.freedBytes)}**`);
121
+
122
+ return results.join("\n");
123
+ }
124
+
125
+ // ===== ARCHIVE ONLY =====
126
+ if (operation === "archive") {
127
+ const archived = archiveOldObservations({
128
+ olderThanDays,
129
+ includeSuperseded: true,
130
+ dryRun,
131
+ });
132
+
133
+ if (dryRun) {
134
+ return `## Archive Preview\n\nWould archive ${archived} observations older than ${olderThanDays} days.\n\nRun without dry_run to execute.`;
135
+ }
136
+
137
+ return `## Archive Complete\n\nArchived ${archived} observations to observations_archive table.`;
138
+ }
139
+
140
+ // ===== CHECKPOINT ONLY =====
141
+ if (operation === "checkpoint") {
142
+ if (dryRun) {
143
+ const sizes = getDatabaseSizes();
144
+ return `## Checkpoint Preview\n\nWAL size: ${formatBytes(sizes.wal)}\n\nRun without dry_run to checkpoint.`;
145
+ }
146
+
147
+ const result = checkpointWAL();
148
+ return `## Checkpoint Complete\n\nCheckpointed: ${result.checkpointed ? "Yes" : "No"}\nWAL pages processed: ${result.walSize}`;
149
+ }
150
+
151
+ // ===== VACUUM ONLY =====
152
+ if (operation === "vacuum") {
153
+ if (dryRun) {
154
+ const sizes = getDatabaseSizes();
155
+ return `## Vacuum Preview\n\nCurrent size: ${formatBytes(sizes.total)}\n\nRun without dry_run to vacuum.`;
156
+ }
157
+
158
+ const before = getDatabaseSizes();
159
+ const success = vacuumDatabase();
160
+ const after = getDatabaseSizes();
161
+
162
+ return `## Vacuum Complete\n\nSuccess: ${success ? "Yes" : "No"}\nBefore: ${formatBytes(before.total)}\nAfter: ${formatBytes(after.total)}\nFreed: ${formatBytes(before.total - after.total)}`;
163
+ }
164
+
165
+ return `Unknown operation: ${operation}. Use: status, full, archive, checkpoint, vacuum`;
166
+ },
167
+ });
@@ -0,0 +1,319 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { tool } from "@opencode-ai/plugin";
4
+ import {
5
+ type ConfidenceLevel,
6
+ type ObservationInput,
7
+ type ObservationType,
8
+ getMemoryDB,
9
+ rebuildFTS5,
10
+ storeObservation,
11
+ } from "../plugin/lib/memory-db";
12
+
13
+ interface ParsedObservation {
14
+ type: ObservationType;
15
+ title: string;
16
+ subtitle?: string;
17
+ facts: string[];
18
+ narrative: string;
19
+ concepts: string[];
20
+ files_read: string[];
21
+ files_modified: string[];
22
+ confidence: ConfidenceLevel;
23
+ bead_id?: string;
24
+ supersedes?: string;
25
+ markdown_file: string;
26
+ created_at: string;
27
+ created_at_epoch: number;
28
+ }
29
+
30
+ /**
31
+ * Parse YAML frontmatter from markdown content.
32
+ */
33
+ function parseYAML(yamlContent: string): Record<string, unknown> {
34
+ const result: Record<string, unknown> = {};
35
+
36
+ for (const line of yamlContent.split("\n")) {
37
+ const match = line.match(/^(\w+):\s*(.*)$/);
38
+ if (match) {
39
+ const [, key, value] = match;
40
+ // Handle JSON arrays
41
+ if (value.startsWith("[")) {
42
+ try {
43
+ result[key] = JSON.parse(value);
44
+ } catch {
45
+ result[key] = value;
46
+ }
47
+ } else if (value === "null" || value === "") {
48
+ result[key] = null;
49
+ } else if (value.startsWith('"') && value.endsWith('"')) {
50
+ result[key] = value.slice(1, -1);
51
+ } else {
52
+ result[key] = value;
53
+ }
54
+ }
55
+ }
56
+
57
+ return result;
58
+ }
59
+
60
+ /**
61
+ * Extract structured facts from narrative (bullet points).
62
+ */
63
+ function extractFacts(narrative: string): string[] {
64
+ const facts: string[] = [];
65
+ const lines = narrative.split("\n");
66
+
67
+ for (const line of lines) {
68
+ const bulletMatch = line.match(/^[-*]\s+(.+)$/);
69
+ if (bulletMatch) {
70
+ facts.push(bulletMatch[1].trim());
71
+ }
72
+ }
73
+
74
+ return facts;
75
+ }
76
+
77
+ /**
78
+ * Parse a markdown observation file into structured data.
79
+ */
80
+ function parseMarkdownObservation(
81
+ content: string,
82
+ filename: string,
83
+ ): ParsedObservation {
84
+ // Parse YAML frontmatter
85
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
86
+ if (!frontmatterMatch) {
87
+ throw new Error(`Invalid format: ${filename} - no YAML frontmatter`);
88
+ }
89
+
90
+ const yaml = parseYAML(frontmatterMatch[1]);
91
+ const narrative = frontmatterMatch[2].trim();
92
+
93
+ // Extract title from markdown heading
94
+ const titleMatch = narrative.match(/^#\s+.+?\s+(.+)$/m);
95
+ const title = titleMatch
96
+ ? titleMatch[1]
97
+ : (yaml.title as string) || "Untitled";
98
+
99
+ // Validate type
100
+ const validTypes: ObservationType[] = [
101
+ "decision",
102
+ "bugfix",
103
+ "feature",
104
+ "pattern",
105
+ "discovery",
106
+ "learning",
107
+ "warning",
108
+ ];
109
+ const type = (yaml.type as string)?.toLowerCase() as ObservationType;
110
+ if (!validTypes.includes(type)) {
111
+ throw new Error(`Invalid type '${yaml.type}' in ${filename}`);
112
+ }
113
+
114
+ // Validate confidence
115
+ const validConfidence: ConfidenceLevel[] = ["high", "medium", "low"];
116
+ const confidence = ((yaml.confidence as string)?.toLowerCase() ||
117
+ "high") as ConfidenceLevel;
118
+ if (!validConfidence.includes(confidence)) {
119
+ throw new Error(`Invalid confidence '${yaml.confidence}' in ${filename}`);
120
+ }
121
+
122
+ // Parse created date
123
+ const createdStr = yaml.created as string;
124
+ if (!createdStr) {
125
+ throw new Error(`Missing created date in ${filename}`);
126
+ }
127
+ const createdAt = new Date(createdStr);
128
+ if (Number.isNaN(createdAt.getTime())) {
129
+ throw new Error(`Invalid created date '${createdStr}' in ${filename}`);
130
+ }
131
+
132
+ // Extract facts from narrative
133
+ const facts = extractFacts(narrative);
134
+
135
+ // Parse files array
136
+ const files = (yaml.files as string[]) || [];
137
+
138
+ return {
139
+ type,
140
+ title,
141
+ subtitle: yaml.subtitle as string | undefined,
142
+ facts,
143
+ narrative,
144
+ concepts: (yaml.concepts as string[]) || [],
145
+ files_read: files, // Assume all files were read for migration
146
+ files_modified: files, // Assume all files were modified for migration
147
+ confidence,
148
+ bead_id: yaml.bead_id as string | undefined,
149
+ supersedes: yaml.supersedes as string | undefined,
150
+ markdown_file: filename,
151
+ created_at: createdAt.toISOString(),
152
+ created_at_epoch: createdAt.getTime(),
153
+ };
154
+ }
155
+
156
+ export default tool({
157
+ description: `Migrate existing markdown observations to SQLite database.
158
+
159
+ Purpose:
160
+ - Import existing .opencode/memory/observations/*.md files into SQLite
161
+ - Preserves all metadata and content
162
+ - Creates FTS5 index for fast search
163
+ - Safe to run multiple times (skips already migrated files)
164
+
165
+ Example:
166
+ memory-migrate({ dry_run: true }) // Preview migration
167
+ memory-migrate({}) // Run migration`,
168
+ args: {
169
+ dry_run: tool.schema
170
+ .boolean()
171
+ .optional()
172
+ .describe("Preview migration without writing to database"),
173
+ force: tool.schema
174
+ .boolean()
175
+ .optional()
176
+ .describe("Force re-migration of all files"),
177
+ },
178
+ execute: async (args: { dry_run?: boolean; force?: boolean }) => {
179
+ const obsDir = path.join(process.cwd(), ".opencode/memory/observations");
180
+ const migrationMarker = path.join(obsDir, ".migrated");
181
+
182
+ // Check for existing migration
183
+ if (!args.force) {
184
+ try {
185
+ await fs.access(migrationMarker);
186
+ const markerContent = await fs.readFile(migrationMarker, "utf-8");
187
+ return `Migration already complete.\n\n${markerContent}\n\nUse force: true to re-migrate.`;
188
+ } catch {
189
+ // No marker, proceed with migration
190
+ }
191
+ }
192
+
193
+ // Get all markdown files
194
+ let files: string[];
195
+ try {
196
+ const entries = await fs.readdir(obsDir);
197
+ files = entries.filter((f) => f.endsWith(".md") && !f.startsWith("."));
198
+ } catch {
199
+ return "No observations directory found at .opencode/memory/observations/";
200
+ }
201
+
202
+ if (files.length === 0) {
203
+ return "No markdown files found to migrate.";
204
+ }
205
+
206
+ // Initialize database
207
+ const db = getMemoryDB();
208
+
209
+ // Parse and migrate
210
+ const results: {
211
+ migrated: string[];
212
+ skipped: string[];
213
+ errors: { file: string; error: string }[];
214
+ } = {
215
+ migrated: [],
216
+ skipped: [],
217
+ errors: [],
218
+ };
219
+
220
+ // Sort by filename (which includes date) for chronological order
221
+ files.sort();
222
+
223
+ for (const file of files) {
224
+ try {
225
+ const content = await fs.readFile(path.join(obsDir, file), "utf-8");
226
+ const parsed = parseMarkdownObservation(content, file);
227
+
228
+ if (args.dry_run) {
229
+ results.migrated.push(
230
+ `${file} → ${parsed.type}: ${parsed.title.substring(0, 50)}`,
231
+ );
232
+ continue;
233
+ }
234
+
235
+ // Check if already migrated (by markdown_file)
236
+ const existing = db
237
+ .query("SELECT id FROM observations WHERE markdown_file = ?")
238
+ .get(file);
239
+
240
+ if (existing && !args.force) {
241
+ results.skipped.push(file);
242
+ continue;
243
+ }
244
+
245
+ // Store observation
246
+ const input: ObservationInput = {
247
+ type: parsed.type,
248
+ title: parsed.title,
249
+ subtitle: parsed.subtitle,
250
+ facts: parsed.facts,
251
+ narrative: parsed.narrative,
252
+ concepts: parsed.concepts,
253
+ files_read: parsed.files_read,
254
+ files_modified: parsed.files_modified,
255
+ confidence: parsed.confidence,
256
+ bead_id: parsed.bead_id,
257
+ markdown_file: file,
258
+ };
259
+
260
+ storeObservation(input);
261
+ results.migrated.push(file);
262
+ } catch (e) {
263
+ results.errors.push({
264
+ file,
265
+ error: e instanceof Error ? e.message : String(e),
266
+ });
267
+ }
268
+ }
269
+
270
+ // Rebuild FTS5 index after migration
271
+ if (!args.dry_run && results.migrated.length > 0) {
272
+ try {
273
+ rebuildFTS5();
274
+ } catch {
275
+ // FTS5 rebuild failed, continue
276
+ }
277
+ }
278
+
279
+ // Write migration marker
280
+ if (!args.dry_run) {
281
+ const markerContent = [
282
+ `Migrated ${results.migrated.length} observations on ${new Date().toISOString()}`,
283
+ `Skipped: ${results.skipped.length}`,
284
+ `Errors: ${results.errors.length}`,
285
+ ].join("\n");
286
+ await fs.writeFile(migrationMarker, markerContent, "utf-8");
287
+ }
288
+
289
+ // Format output
290
+ let output = args.dry_run
291
+ ? "# Migration Preview (Dry Run)\n\n"
292
+ : "# Migration Complete\n\n";
293
+
294
+ output += `**Total files**: ${files.length}\n`;
295
+ output += `**Migrated**: ${results.migrated.length}\n`;
296
+ output += `**Skipped**: ${results.skipped.length}\n`;
297
+ output += `**Errors**: ${results.errors.length}\n\n`;
298
+
299
+ if (results.errors.length > 0) {
300
+ output += "## Errors\n\n";
301
+ for (const { file, error } of results.errors) {
302
+ output += `- **${file}**: ${error}\n`;
303
+ }
304
+ output += "\n";
305
+ }
306
+
307
+ if (args.dry_run && results.migrated.length > 0) {
308
+ output += "## Files to migrate\n\n";
309
+ for (const item of results.migrated.slice(0, 20)) {
310
+ output += `- ${item}\n`;
311
+ }
312
+ if (results.migrated.length > 20) {
313
+ output += `- ... and ${results.migrated.length - 20} more\n`;
314
+ }
315
+ }
316
+
317
+ return output;
318
+ },
319
+ });
@@ -1,24 +1,23 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
1
  import { tool } from "@opencode-ai/plugin";
2
+ import { getMemoryFile } from "../plugin/lib/memory-db.js";
4
3
 
5
4
  export default tool({
6
5
  description: `Read memory files for persistent cross-session context.
7
6
 
8
7
  Purpose:
9
8
  - Retrieve project state, learnings, and active tasks
10
- - Checks locations in priority order: project → global → legacy
11
- - Supports subdirectories: handoffs/, research/, observations/, _templates/
9
+ - Reads from SQLite database
10
+ - Supports subdirectories: handoffs/, research/
12
11
 
13
12
  Example:
14
13
  memory-read({ file: "handoffs/2024-01-20-phase-1" })
15
- memory-read({ file: "_templates/task-prd" })`,
14
+ memory-read({ file: "research/2024-01-topic" })`,
16
15
  args: {
17
16
  file: tool.schema
18
17
  .string()
19
18
  .optional()
20
19
  .describe(
21
- "Memory file to read: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic, _templates/task-prd, _templates/task-spec, _templates/task-review, _templates/research, _templates/handoff",
20
+ "Memory file to read: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic",
22
21
  ),
23
22
  },
24
23
  execute: async (args: { file?: string }) => {
@@ -27,48 +26,20 @@ export default tool({
27
26
  // Normalize: strip .md extension if present
28
27
  const normalizedFile = fileName.replace(/\.md$/i, "");
29
28
 
30
- // Location priority: project > global > legacy
31
- const locations = [
32
- path.join(process.cwd(), ".opencode/memory", `${normalizedFile}.md`),
33
- path.join(
34
- process.env.HOME || "",
35
- ".config/opencode/memory",
36
- `${normalizedFile}.md`,
37
- ),
38
- path.join(
39
- process.cwd(),
40
- ".config/opencode/memory",
41
- `${normalizedFile}.md`,
42
- ),
43
- ];
44
-
45
- // Try each location in order
46
- for (const filePath of locations) {
47
- try {
48
- const content = await fs.readFile(filePath, "utf-8");
49
- const locationLabel = filePath.includes(".opencode/memory")
50
- ? "project"
51
- : filePath.includes(process.env.HOME || "")
52
- ? "global"
53
- : "legacy";
54
- return `[Read from ${locationLabel}: ${filePath}]\n\n${content}`;
55
- } catch (error) {
56
- // Continue to next location if file not found
57
- if (
58
- error instanceof Error &&
59
- "code" in error &&
60
- error.code === "ENOENT"
61
- ) {
62
- continue;
63
- }
64
- // Other errors should be reported
65
- if (error instanceof Error) {
66
- return `Error reading memory from ${filePath}: ${error.message}`;
67
- }
29
+ try {
30
+ const dbRecord = getMemoryFile(normalizedFile);
31
+ if (dbRecord) {
32
+ const updatedInfo = dbRecord.updated_at
33
+ ? ` (updated: ${dbRecord.updated_at})`
34
+ : "";
35
+ return `[${normalizedFile}${updatedInfo}]\n\n${dbRecord.content}`;
36
+ }
37
+ } catch (error) {
38
+ if (error instanceof Error) {
39
+ return `Error reading memory: ${error.message}`;
68
40
  }
69
41
  }
70
42
 
71
- // No file found in any location
72
- return `Memory file '${normalizedFile}.md' not found in any location.\nSearched:\n- ${locations.join("\n- ")}\n\nStructure:\n- handoffs/YYYY-MM-DD-phase (phase transitions)\n- research/YYYY-MM-DD-topic (research findings)\n- _templates/ (prd, spec, review, research, handoff)`;
43
+ return `Memory file '${normalizedFile}' not found.\n\nStructure:\n- handoffs/YYYY-MM-DD-phase (phase transitions)\n- research/YYYY-MM-DD-topic (research findings)`;
73
44
  },
74
45
  });