opencodekit 0.12.6 → 0.13.0
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/README.md +2 -2
- package/dist/index.js +2756 -523
- package/dist/template/.opencode/AGENTS.md +35 -128
- package/dist/template/.opencode/README.md +4 -3
- package/dist/template/.opencode/agent/build.md +32 -21
- package/dist/template/.opencode/agent/explore.md +27 -16
- package/dist/template/.opencode/agent/planner.md +103 -63
- package/dist/template/.opencode/agent/review.md +31 -23
- package/dist/template/.opencode/agent/rush.md +27 -19
- package/dist/template/.opencode/agent/scout.md +27 -19
- package/dist/template/.opencode/agent/vision.md +29 -19
- package/dist/template/.opencode/command/accessibility-check.md +1 -0
- package/dist/template/.opencode/command/analyze-mockup.md +1 -0
- package/dist/template/.opencode/command/analyze-project.md +2 -1
- package/dist/template/.opencode/command/brainstorm.md +2 -1
- package/dist/template/.opencode/command/design-audit.md +1 -0
- package/dist/template/.opencode/command/design.md +1 -0
- package/dist/template/.opencode/command/finish.md +39 -4
- package/dist/template/.opencode/command/fix.md +28 -1
- package/dist/template/.opencode/command/implement.md +26 -6
- package/dist/template/.opencode/command/init.md +1 -0
- package/dist/template/.opencode/command/pr.md +28 -1
- package/dist/template/.opencode/command/research-ui.md +1 -0
- package/dist/template/.opencode/command/research.md +1 -4
- package/dist/template/.opencode/command/review-codebase.md +1 -0
- package/dist/template/.opencode/command/start.md +106 -0
- package/dist/template/.opencode/command/status.md +3 -2
- package/dist/template/.opencode/command/summarize.md +2 -1
- package/dist/template/.opencode/command/triage.md +66 -12
- package/dist/template/.opencode/command/ui-review.md +1 -0
- package/dist/template/.opencode/memory/project/architecture.md +59 -6
- package/dist/template/.opencode/memory/project/beads-workflow.md +278 -0
- package/dist/template/.opencode/memory/project/commands.md +20 -164
- package/dist/template/.opencode/memory/session-context.md +40 -0
- package/dist/template/.opencode/memory/user.md +24 -7
- package/dist/template/.opencode/opencode.json +77 -16
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/compaction.ts +62 -18
- package/dist/template/.opencode/plugin/lib/notify.ts +2 -3
- package/dist/template/.opencode/plugin/sessions.ts +1 -1
- package/dist/template/.opencode/plugin/skill-mcp.ts +11 -12
- package/dist/template/.opencode/skill/beads/SKILL.md +44 -0
- package/dist/template/.opencode/skill/condition-based-waiting/example.ts +71 -65
- package/dist/template/.opencode/tool/ast-grep.ts +3 -3
- package/dist/template/.opencode/tool/bd-inbox.ts +7 -6
- package/dist/template/.opencode/tool/bd-msg.ts +3 -3
- package/dist/template/.opencode/tool/bd-release.ts +2 -2
- package/dist/template/.opencode/tool/bd-reserve.ts +5 -4
- package/dist/template/.opencode/tool/memory-read.ts +58 -58
- package/dist/template/.opencode/tool/memory-search.ts +2 -2
- package/dist/template/.opencode/tool/memory-update.ts +53 -54
- package/dist/template/.opencode/tool/observation.ts +6 -6
- package/dist/template/.opencode/tsconfig.json +19 -19
- package/package.json +8 -17
- package/dist/template/.opencode/command.backup/analyze-project.md +0 -465
- package/dist/template/.opencode/command.backup/finish.md +0 -167
- package/dist/template/.opencode/command.backup/implement.md +0 -143
- package/dist/template/.opencode/command.backup/pr.md +0 -252
- package/dist/template/.opencode/command.backup/status.md +0 -376
- package/dist/template/.opencode/lib/lsp/client.ts +0 -614
- package/dist/template/.opencode/lib/lsp/config.ts +0 -199
- package/dist/template/.opencode/lib/lsp/constants.ts +0 -339
- package/dist/template/.opencode/lib/lsp/types.ts +0 -138
- package/dist/template/.opencode/lib/lsp/utils.ts +0 -190
- package/dist/template/.opencode/memory/project/SHELL_OUTPUT_MIGRATION_PLAN.md +0 -551
|
@@ -506,6 +506,50 @@ bd_claim(); // Gets frontend task that was waiting on API
|
|
|
506
506
|
5. **Use `bd_msg(to="all")`** - For team-wide announcements
|
|
507
507
|
6. **Sync regularly** - `bd_sync()` at session end
|
|
508
508
|
|
|
509
|
+
## Best Practices (from Steve Yegge)
|
|
510
|
+
|
|
511
|
+
### Daily/Weekly Maintenance
|
|
512
|
+
|
|
513
|
+
| Task | Frequency | Command | Why |
|
|
514
|
+
| ------------ | -------------- | --------------------- | ---------------------------------------------- |
|
|
515
|
+
| Health check | Weekly | `bd doctor` | Repairs database issues, detects orphaned work |
|
|
516
|
+
| Cleanup | Every few days | `bd cleanup --days 7` | Keep DB under 200-500 issues for performance |
|
|
517
|
+
| Upgrade | Weekly | `bd upgrade` | Get latest features and fixes |
|
|
518
|
+
| Git hooks | Once per repo | `bd hooks install` | Auto-sync on commit/merge/checkout |
|
|
519
|
+
|
|
520
|
+
### Key Principles
|
|
521
|
+
|
|
522
|
+
1. **Plan outside Beads first** - Use planning tools, then import tasks to beads
|
|
523
|
+
2. **One task per session, then restart** - Fresh context prevents confusion
|
|
524
|
+
3. **File lots of issues** - Any work >2 minutes should be tracked
|
|
525
|
+
4. **Use short prefixes** - `bd-`, `vc-`, `wy-` etc.
|
|
526
|
+
5. **"Land the plane" = PUSH** - `bd sync` means git push, not "ready when you are"
|
|
527
|
+
6. **Include issue ID in commits** - `git commit -m "Fix bug (bd-abc)"`
|
|
528
|
+
|
|
529
|
+
### Database Health
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
# Check database size
|
|
533
|
+
bd list --status=all --json | wc -l
|
|
534
|
+
|
|
535
|
+
# Target: under 200-500 issues
|
|
536
|
+
# If over, run cleanup more aggressively:
|
|
537
|
+
bd cleanup --days 3
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Git Hooks (Essential)
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
bd hooks install
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Installs hooks for:
|
|
547
|
+
|
|
548
|
+
- **pre-commit**: Sync before commit
|
|
549
|
+
- **post-merge**: Import changes after merge
|
|
550
|
+
- **pre-push**: Ensure sync before push
|
|
551
|
+
- **post-checkout**: Refresh after branch switch
|
|
552
|
+
|
|
509
553
|
## Quick Reference
|
|
510
554
|
|
|
511
555
|
```
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// From: Lace test infrastructure improvements (2025-10-03)
|
|
3
3
|
// Context: Fixed 15 flaky tests by replacing arbitrary timeouts
|
|
4
4
|
|
|
5
|
-
import type { ThreadManager } from
|
|
6
|
-
import type { LaceEvent, LaceEventType } from
|
|
5
|
+
import type { ThreadManager } from "~/threads/thread-manager";
|
|
6
|
+
import type { LaceEvent, LaceEventType } from "~/threads/types";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Wait for a specific event type to appear in thread
|
|
@@ -18,29 +18,33 @@ import type { LaceEvent, LaceEventType } from '~/threads/types';
|
|
|
18
18
|
* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');
|
|
19
19
|
*/
|
|
20
20
|
export function waitForEvent(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
threadManager: ThreadManager,
|
|
22
|
+
threadId: string,
|
|
23
|
+
eventType: LaceEventType,
|
|
24
|
+
timeoutMs = 5000,
|
|
25
25
|
): Promise<LaceEvent> {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const startTime = Date.now();
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const check = () => {
|
|
30
|
+
const events = threadManager.getEvents(threadId);
|
|
31
|
+
const event = events.find((e) => e.type === eventType);
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
if (event) {
|
|
34
|
+
resolve(event);
|
|
35
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
36
|
+
reject(
|
|
37
|
+
new Error(
|
|
38
|
+
`Timeout waiting for ${eventType} event after ${timeoutMs}ms`,
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
} else {
|
|
42
|
+
setTimeout(check, 10); // Poll every 10ms for efficiency
|
|
43
|
+
}
|
|
44
|
+
};
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
check();
|
|
47
|
+
});
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/**
|
|
@@ -58,34 +62,34 @@ export function waitForEvent(
|
|
|
58
62
|
* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);
|
|
59
63
|
*/
|
|
60
64
|
export function waitForEventCount(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
threadManager: ThreadManager,
|
|
66
|
+
threadId: string,
|
|
67
|
+
eventType: LaceEventType,
|
|
68
|
+
count: number,
|
|
69
|
+
timeoutMs = 5000,
|
|
66
70
|
): Promise<LaceEvent[]> {
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const startTime = Date.now();
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
const check = () => {
|
|
75
|
+
const events = threadManager.getEvents(threadId);
|
|
76
|
+
const matchingEvents = events.filter((e) => e.type === eventType);
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
if (matchingEvents.length >= count) {
|
|
79
|
+
resolve(matchingEvents);
|
|
80
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
81
|
+
reject(
|
|
82
|
+
new Error(
|
|
83
|
+
`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
setTimeout(check, 10);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
check();
|
|
92
|
+
});
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
/**
|
|
@@ -109,30 +113,32 @@ export function waitForEventCount(
|
|
|
109
113
|
* );
|
|
110
114
|
*/
|
|
111
115
|
export function waitForEventMatch(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
threadManager: ThreadManager,
|
|
117
|
+
threadId: string,
|
|
118
|
+
predicate: (event: LaceEvent) => boolean,
|
|
119
|
+
description: string,
|
|
120
|
+
timeoutMs = 5000,
|
|
117
121
|
): Promise<LaceEvent> {
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
const startTime = Date.now();
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
const check = () => {
|
|
126
|
+
const events = threadManager.getEvents(threadId);
|
|
127
|
+
const event = events.find(predicate);
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
if (event) {
|
|
130
|
+
resolve(event);
|
|
131
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
132
|
+
reject(
|
|
133
|
+
new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`),
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
setTimeout(check, 10);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
133
139
|
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
check();
|
|
141
|
+
});
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
// Usage example from actual debugging session:
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Requires: npm install -g @ast-grep/cli (or brew install ast-grep)
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { exec } from "child_process";
|
|
10
|
-
import { promisify } from "util";
|
|
9
|
+
import { exec } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
11
|
import { tool } from "@opencode-ai/plugin";
|
|
12
12
|
|
|
13
13
|
const execAsync = promisify(exec);
|
|
@@ -224,7 +224,7 @@ Install via:
|
|
|
224
224
|
preview += `... and ${count - 10} more matches\n`;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
preview +=
|
|
227
|
+
preview += "\n**To apply:** Run again with dryRun: false";
|
|
228
228
|
return preview;
|
|
229
229
|
} catch {
|
|
230
230
|
return `Dry run preview:\n${stdout}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { tool } from "@opencode-ai/plugin";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
4
|
|
|
5
5
|
const RESERVATIONS_DIR = ".reservations";
|
|
6
6
|
const MESSAGES_FILE = "messages.jsonl";
|
|
@@ -84,7 +84,7 @@ export default tool({
|
|
|
84
84
|
}
|
|
85
85
|
await fs.writeFile(
|
|
86
86
|
messagesPath,
|
|
87
|
-
allMsgs.map((m) => JSON.stringify(m)).join("\n")
|
|
87
|
+
`${allMsgs.map((m) => JSON.stringify(m)).join("\n")}\n`,
|
|
88
88
|
"utf-8",
|
|
89
89
|
);
|
|
90
90
|
}
|
|
@@ -99,11 +99,12 @@ export default tool({
|
|
|
99
99
|
messages = messages.slice(-limit).reverse();
|
|
100
100
|
|
|
101
101
|
return JSON.stringify({ msgs: messages, count: messages.length });
|
|
102
|
-
} catch (e
|
|
103
|
-
|
|
102
|
+
} catch (e) {
|
|
103
|
+
const err = e as NodeJS.ErrnoException;
|
|
104
|
+
if (err.code === "ENOENT") {
|
|
104
105
|
return JSON.stringify({ msgs: [], count: 0 });
|
|
105
106
|
}
|
|
106
|
-
return JSON.stringify({ error: e.message });
|
|
107
|
+
return JSON.stringify({ error: (e as Error).message });
|
|
107
108
|
}
|
|
108
109
|
},
|
|
109
110
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { tool } from "@opencode-ai/plugin";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
4
|
|
|
5
5
|
const RESERVATIONS_DIR = ".reservations";
|
|
6
6
|
const MESSAGES_FILE = "messages.jsonl";
|
|
@@ -56,7 +56,7 @@ export default tool({
|
|
|
56
56
|
read: false,
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
await fs.appendFile(messagesPath, JSON.stringify(msg)
|
|
59
|
+
await fs.appendFile(messagesPath, `${JSON.stringify(msg)}\n`, "utf-8");
|
|
60
60
|
return JSON.stringify({ ok: 1, id: msg.id });
|
|
61
61
|
},
|
|
62
62
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { tool } from "@opencode-ai/plugin";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
4
|
|
|
5
5
|
const RESERVATIONS_DIR = ".reservations";
|
|
6
6
|
|
|
@@ -69,8 +69,9 @@ export default tool({
|
|
|
69
69
|
};
|
|
70
70
|
await fs.writeFile(metaPath, JSON.stringify(lockData), "utf-8");
|
|
71
71
|
granted.push(filePath);
|
|
72
|
-
} catch (e
|
|
73
|
-
|
|
72
|
+
} catch (e) {
|
|
73
|
+
const err = e as NodeJS.ErrnoException;
|
|
74
|
+
if (err.code === "EEXIST") {
|
|
74
75
|
// Lock exists - check if expired or ours
|
|
75
76
|
try {
|
|
76
77
|
const content = await fs.readFile(metaPath, "utf-8");
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import fs from "fs/promises";
|
|
3
|
-
import path from "path";
|
|
4
4
|
|
|
5
5
|
export default tool({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
description:
|
|
7
|
+
"Read memory files for persistent cross-session context. Returns current project state, learnings, and active tasks. Supports subdirectories (e.g., 'research/opencode-sessions').",
|
|
8
|
+
args: {
|
|
9
|
+
file: tool.schema
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe(
|
|
13
|
+
"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",
|
|
14
|
+
),
|
|
15
|
+
},
|
|
16
|
+
execute: async (args: { file?: string }) => {
|
|
17
|
+
const fileName = args.file || "memory";
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// Normalize: strip .md extension if present
|
|
20
|
+
const normalizedFile = fileName.replace(/\.md$/i, "");
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
// Location priority: project > global > legacy
|
|
23
|
+
const locations = [
|
|
24
|
+
path.join(process.cwd(), ".opencode/memory", `${normalizedFile}.md`),
|
|
25
|
+
path.join(
|
|
26
|
+
process.env.HOME || "",
|
|
27
|
+
".config/opencode/memory",
|
|
28
|
+
`${normalizedFile}.md`,
|
|
29
|
+
),
|
|
30
|
+
path.join(
|
|
31
|
+
process.cwd(),
|
|
32
|
+
".config/opencode/memory",
|
|
33
|
+
`${normalizedFile}.md`,
|
|
34
|
+
),
|
|
35
|
+
];
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
37
|
+
// Try each location in order
|
|
38
|
+
for (const filePath of locations) {
|
|
39
|
+
try {
|
|
40
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
41
|
+
const locationLabel = filePath.includes(".opencode/memory")
|
|
42
|
+
? "project"
|
|
43
|
+
: filePath.includes(process.env.HOME || "")
|
|
44
|
+
? "global"
|
|
45
|
+
: "legacy";
|
|
46
|
+
return `[Read from ${locationLabel}: ${filePath}]\n\n${content}`;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Continue to next location if file not found
|
|
49
|
+
if (
|
|
50
|
+
error instanceof Error &&
|
|
51
|
+
"code" in error &&
|
|
52
|
+
error.code === "ENOENT"
|
|
53
|
+
) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Other errors should be reported
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
return `Error reading memory from ${filePath}: ${error.message}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
// No file found in any location
|
|
64
|
+
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)`;
|
|
65
|
+
},
|
|
66
66
|
});
|
|
@@ -1,61 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import fs from "fs/promises";
|
|
3
|
-
import path from "path";
|
|
4
4
|
|
|
5
5
|
export default tool({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
6
|
+
description:
|
|
7
|
+
"Update memory files with new learnings, progress, or context. Appends or replaces content based on mode. Supports subdirectories for organization (e.g., 'research/opencode-sessions' creates .opencode/memory/research/opencode-sessions.md).",
|
|
8
|
+
args: {
|
|
9
|
+
file: tool.schema
|
|
10
|
+
.string()
|
|
11
|
+
.describe(
|
|
12
|
+
"Memory file to update: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic. Use _templates/ for reference only.",
|
|
13
|
+
),
|
|
14
|
+
content: tool.schema
|
|
15
|
+
.string()
|
|
16
|
+
.describe("Content to write or append to the memory file"),
|
|
17
|
+
mode: tool.schema
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.default("replace")
|
|
21
|
+
.describe(
|
|
22
|
+
"Update mode: 'replace' (overwrite file) or 'append' (add to end).",
|
|
23
|
+
),
|
|
24
|
+
},
|
|
25
|
+
execute: async (args: { file: string; content: string; mode?: string }) => {
|
|
26
|
+
// Always write to project memory (.opencode/memory/)
|
|
27
|
+
const memoryDir = path.join(process.cwd(), ".opencode/memory");
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
// Normalize file path: strip existing .md extension, handle subdirectories
|
|
30
|
+
const normalizedFile = args.file.replace(/\.md$/i, ""); // Remove .md if present
|
|
31
|
+
const filePath = path.join(memoryDir, `${normalizedFile}.md`);
|
|
32
|
+
const mode = args.mode || "replace";
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
try {
|
|
35
|
+
// Ensure parent directory exists (handles subdirectories)
|
|
36
|
+
const fileDir = path.dirname(filePath);
|
|
37
|
+
await fs.mkdir(fileDir, { recursive: true });
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
},
|
|
39
|
+
if (mode === "append") {
|
|
40
|
+
const timestamp = new Date().toISOString();
|
|
41
|
+
const appendContent = `\n\n---\n**Updated:** ${timestamp}\n\n${args.content}`;
|
|
42
|
+
await fs.appendFile(filePath, appendContent, "utf-8");
|
|
43
|
+
return `Successfully appended to ${normalizedFile}.md\n[Written to: ${filePath}]`;
|
|
44
|
+
}
|
|
45
|
+
// Replace mode - update timestamp
|
|
46
|
+
const timestamp = new Date().toISOString();
|
|
47
|
+
const updatedContent = args.content.replace(
|
|
48
|
+
/\*\*Last Updated:\*\* \[Timestamp\]/,
|
|
49
|
+
`**Last Updated:** ${timestamp}`,
|
|
50
|
+
);
|
|
51
|
+
await fs.writeFile(filePath, updatedContent, "utf-8");
|
|
52
|
+
return `Successfully updated ${normalizedFile}.md\n[Written to: ${filePath}]`;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
return `Error updating memory: ${error.message}`;
|
|
56
|
+
}
|
|
57
|
+
return "Unknown error updating memory file";
|
|
58
|
+
}
|
|
59
|
+
},
|
|
61
60
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { tool } from "@opencode-ai/plugin";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
4
|
|
|
5
5
|
// Observation types following claude-mem patterns
|
|
6
6
|
type ObservationType =
|
|
@@ -112,9 +112,9 @@ export default tool({
|
|
|
112
112
|
observation += `**Files:** ${files.map((f) => `\`${f}\``).join(", ")}\n`;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
observation +=
|
|
115
|
+
observation += "\n---\n\n";
|
|
116
116
|
observation += args.content;
|
|
117
|
-
observation +=
|
|
117
|
+
observation += "\n";
|
|
118
118
|
|
|
119
119
|
try {
|
|
120
120
|
// Ensure directory exists
|
|
@@ -128,7 +128,7 @@ export default tool({
|
|
|
128
128
|
// Update bead notes if bead_id provided
|
|
129
129
|
if (args.bead_id) {
|
|
130
130
|
try {
|
|
131
|
-
const { execSync } = await import("child_process");
|
|
131
|
+
const { execSync } = await import("node:child_process");
|
|
132
132
|
const noteContent = `${icon} ${obsType}: ${args.title}`;
|
|
133
133
|
execSync(
|
|
134
134
|
`bd edit ${args.bead_id} --note "${noteContent.replace(/"/g, '\\"')}"`,
|
|
@@ -149,7 +149,7 @@ export default tool({
|
|
|
149
149
|
if (error instanceof Error) {
|
|
150
150
|
return `Error saving observation: ${error.message}`;
|
|
151
151
|
}
|
|
152
|
-
return
|
|
152
|
+
return "Unknown error saving observation";
|
|
153
153
|
}
|
|
154
154
|
},
|
|
155
155
|
});
|