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.
- package/dist/index.js +1 -1
- package/dist/template/.opencode/AGENTS.md +64 -3
- package/dist/template/.opencode/command/create.md +34 -0
- package/dist/template/.opencode/command/design.md +35 -0
- package/dist/template/.opencode/command/handoff.md +15 -0
- package/dist/template/.opencode/command/init.md +40 -47
- package/dist/template/.opencode/command/plan.md +1 -0
- package/dist/template/.opencode/command/pr.md +15 -0
- package/dist/template/.opencode/command/research.md +3 -0
- package/dist/template/.opencode/command/resume.md +1 -0
- package/dist/template/.opencode/command/review-codebase.md +30 -0
- package/dist/template/.opencode/command/ship.md +43 -0
- package/dist/template/.opencode/command/start.md +1 -0
- package/dist/template/.opencode/command/status.md +24 -1
- package/dist/template/.opencode/command/ui-review.md +31 -0
- package/dist/template/.opencode/command/verify.md +35 -7
- package/dist/template/.opencode/memory/project/tech-stack.md +25 -22
- package/dist/template/.opencode/memory.db +0 -0
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +817 -916
- package/dist/template/.opencode/package.json +1 -0
- package/dist/template/.opencode/plans/1770006237537-mighty-otter.md +418 -0
- package/dist/template/.opencode/plans/1770006913647-glowing-forest.md +170 -0
- package/dist/template/.opencode/plans/1770013678126-witty-planet.md +278 -0
- package/dist/template/.opencode/plugin/lib/memory-db.ts +828 -0
- package/dist/template/.opencode/plugin/memory.ts +38 -1
- package/dist/template/.opencode/skill/index-knowledge/SKILL.md +76 -31
- package/dist/template/.opencode/skill/memory-system/SKILL.md +110 -55
- package/dist/template/.opencode/tool/memory-get.ts +143 -0
- package/dist/template/.opencode/tool/memory-maintain.ts +167 -0
- package/dist/template/.opencode/tool/memory-migrate.ts +319 -0
- package/dist/template/.opencode/tool/memory-read.ts +17 -46
- package/dist/template/.opencode/tool/memory-search.ts +131 -28
- package/dist/template/.opencode/tool/memory-timeline.ts +105 -0
- package/dist/template/.opencode/tool/memory-update.ts +21 -26
- package/dist/template/.opencode/tool/observation.ts +112 -100
- package/dist/template/.opencode/tsconfig.json +19 -19
- package/package.json +1 -1
- package/dist/template/.opencode/memory/_templates/README.md +0 -73
- package/dist/template/.opencode/memory/_templates/observation.md +0 -39
- package/dist/template/.opencode/memory/_templates/prompt-engineering.md +0 -333
- package/dist/template/.opencode/memory/observations/2026-01-22-decision-agents-md-prompt-engineering-improvement.md +0 -29
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-agent-roles-build-orchestrates-general-e.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-simplified-swarm-helper-tool-to-fix-type.md +0 -20
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-use-beads-as-swarm-board-source-of-truth.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-01-25-learning-user-wants-real-swarm-coordination-guida.md +0 -15
- package/dist/template/.opencode/memory/observations/2026-01-28-decision-created-deep-research-skill-for-thorough.md +0 -29
- package/dist/template/.opencode/memory/observations/2026-01-28-decision-gh-grep-mcp-wrapper-vs-native-grep-searc.md +0 -21
- package/dist/template/.opencode/memory/observations/2026-01-28-decision-oracle-tool-optimal-usage-patterns.md +0 -32
- package/dist/template/.opencode/memory/observations/2026-01-28-learning-ampcode-deep-mode-research-integration-w.md +0 -42
- package/dist/template/.opencode/memory/observations/2026-01-28-pattern-research-delegation-pattern-explore-for-.md +0 -32
- package/dist/template/.opencode/memory/observations/2026-01-29-decision-copilot-auth-plugin-rate-limit-handling.md +0 -27
- package/dist/template/.opencode/memory/observations/2026-01-29-decision-spec-driven-approach-for-opencodekit.md +0 -21
- package/dist/template/.opencode/memory/observations/2026-01-29-learning-karpathy-llm-coding-insights-dec-2025.md +0 -44
- package/dist/template/.opencode/memory/observations/2026-01-30-decision-github-copilot-claude-routing-keep-disab.md +0 -32
- package/dist/template/.opencode/memory/observations/2026-01-30-discovery-context-management-research-critical-gap.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-01-30-discovery-kimi-k2-5-agent-swarm-architecture-patte.md +0 -45
- package/dist/template/.opencode/memory/observations/2026-01-30-pattern-swarm-tools-architecture.md +0 -28
- package/dist/template/.opencode/memory/observations/2026-01-31-decision-copilot-auth-plugin-updated-with-baseurl.md +0 -63
- package/dist/template/.opencode/memory/observations/2026-01-31-decision-created-dedicated-worker-agent-for-swarm.md +0 -20
- package/dist/template/.opencode/memory/observations/2026-01-31-decision-rollback-to-v1-1-47-for-copilot-claude-r.md +0 -21
- package/dist/template/.opencode/memory/observations/2026-01-31-decision-simplified-swarm-to-task-tool-pattern.md +0 -44
- package/dist/template/.opencode/memory/observations/2026-01-31-decision-swarm-architecture-task-tool-over-tmux.md +0 -33
- package/dist/template/.opencode/memory/observations/2026-01-31-decision-worker-skills-defined-for-swarm-delegati.md +0 -30
- package/dist/template/.opencode/memory/observations/2026-01-31-learning-gpt-reasoning-config-for-github-copilot.md +0 -51
- package/dist/template/.opencode/memory/observations/2026-01-31-learning-opencode-copilot-auth-comparison-finding.md +0 -61
- package/dist/template/.opencode/memory/observations/2026-01-31-learning-opencode-copilot-reasoning-architecture-.md +0 -66
- package/dist/template/.opencode/memory/observations/2026-01-31-learning-opencode-custom-tools-api.md +0 -48
- package/dist/template/.opencode/memory/observations/2026-01-31-learning-opencode-v1-1-48-skills-as-slash-command.md +0 -21
- package/dist/template/.opencode/memory/observations/2026-01-31-learning-swarm-system-simplified-removed-mailbox-.md +0 -30
- package/dist/template/.opencode/memory/observations/2026-01-31-learning-v1-1-48-native-copilot-reasoning-via-pr-.md +0 -45
- package/dist/template/.opencode/memory/observations/2026-01-31-warning-cannot-add-custom-config-to-opencode-jso.md +0 -18
- package/dist/template/.opencode/memory/observations/2026-01-31-warning-copilot-claude-v1-endpoint-returns-404-c.md +0 -48
- package/dist/template/.opencode/memory/observations/2026-01-31-warning-opencode-v1-1-48-claude-thinking-block-s.md +0 -51
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-add-skills-vs-commands-to-global-agents-.md +0 -15
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-build-agent-auto-loads-skills-contextual.md +0 -31
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-fixed-agent-configuration-for-opencodeki.md +0 -25
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-focused-agents-md-upgrade-for-opencode-k.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-implement-tier-1-permission-upgrades.md +0 -15
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-instructions-config-explicit-paths-not-w.md +0 -40
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-merged-context-into-memory-project-singl.md +0 -42
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-oracle-tool-should-use-review-agent-not-.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-plan-agent-auto-loads-skills-contextuall.md +0 -31
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-plan-phased-oracle-command-merge-into-ne.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-prd-workflow-uses-prd-and-prd-task-skill.md +0 -23
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-prefer-review-agent-via-opencode-cli-ove.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-remove-oracle-tool-add-ship-command-with.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-remove-oracle-tool-and-add-ship-command-.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-remove-oracle-tool-and-add-ship-command.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-remove-skills-vs-commands-section-from-a.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-replace-oracle-tool-with-ship-command-fl.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-replace-oracle-with-ship-command-workflo.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-replace-proxypal-oracle-with-cli-review-.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-simplified-dist-template-only-tech-stack.md +0 -50
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-simplified-templates-only-tech-stack-md.md +0 -26
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-subagents-load-minimal-skills-stay-lean.md +0 -29
- package/dist/template/.opencode/memory/observations/2026-02-01-decision-user-approved-permission-upgrades-in-ope.md +0 -15
- package/dist/template/.opencode/memory/observations/2026-02-01-discovery-verify-command-already-implemented.md +0 -28
- package/dist/template/.opencode/memory/observations/2026-02-01-feature-openspec-phase-b-complete-template-upgra.md +0 -43
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-build-agent-should-use-dynamic-lsp-not-f.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-kimi-k2-5-model-requires-temperature-1-0.md +0 -22
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-opencode-context-injection-already-imple.md +0 -27
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-opencode-context-injection-uses-instruct.md +0 -35
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-update-build-agent-prompt-to-use-context.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-upgrade-agents-md-using-opencode-expert-.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-upgrade-agents-md-with-opencode-expert-g.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-upgrade-agents-md-with-opencode-expert-r.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-user-prefers-copilot-gpt-5-2-codex-mediu.md +0 -14
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-user-wants-general-agent-prompt-contextu.md +0 -15
- package/dist/template/.opencode/memory/observations/2026-02-01-learning-user-wants-general-agent-prompt-reviewed.md +0 -15
- package/dist/template/.opencode/memory/project/architecture.md +0 -60
- package/dist/template/.opencode/memory/project/command-rules.md +0 -122
- package/dist/template/.opencode/memory/project/commands.md +0 -72
- package/dist/template/.opencode/memory/project/conventions.md +0 -68
- package/dist/template/.opencode/memory/project/gotchas.md +0 -41
- /package/dist/template/.opencode/memory/_templates/{project/tech-stack.md → tech-stack.md} +0 -0
- /package/dist/template/.opencode/memory/{user.example.md → _templates/user.md} +0 -0
- /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
|
-
-
|
|
11
|
-
- Supports subdirectories: handoffs/, research
|
|
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: "
|
|
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
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
});
|