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.
Files changed (65) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +2756 -523
  3. package/dist/template/.opencode/AGENTS.md +35 -128
  4. package/dist/template/.opencode/README.md +4 -3
  5. package/dist/template/.opencode/agent/build.md +32 -21
  6. package/dist/template/.opencode/agent/explore.md +27 -16
  7. package/dist/template/.opencode/agent/planner.md +103 -63
  8. package/dist/template/.opencode/agent/review.md +31 -23
  9. package/dist/template/.opencode/agent/rush.md +27 -19
  10. package/dist/template/.opencode/agent/scout.md +27 -19
  11. package/dist/template/.opencode/agent/vision.md +29 -19
  12. package/dist/template/.opencode/command/accessibility-check.md +1 -0
  13. package/dist/template/.opencode/command/analyze-mockup.md +1 -0
  14. package/dist/template/.opencode/command/analyze-project.md +2 -1
  15. package/dist/template/.opencode/command/brainstorm.md +2 -1
  16. package/dist/template/.opencode/command/design-audit.md +1 -0
  17. package/dist/template/.opencode/command/design.md +1 -0
  18. package/dist/template/.opencode/command/finish.md +39 -4
  19. package/dist/template/.opencode/command/fix.md +28 -1
  20. package/dist/template/.opencode/command/implement.md +26 -6
  21. package/dist/template/.opencode/command/init.md +1 -0
  22. package/dist/template/.opencode/command/pr.md +28 -1
  23. package/dist/template/.opencode/command/research-ui.md +1 -0
  24. package/dist/template/.opencode/command/research.md +1 -4
  25. package/dist/template/.opencode/command/review-codebase.md +1 -0
  26. package/dist/template/.opencode/command/start.md +106 -0
  27. package/dist/template/.opencode/command/status.md +3 -2
  28. package/dist/template/.opencode/command/summarize.md +2 -1
  29. package/dist/template/.opencode/command/triage.md +66 -12
  30. package/dist/template/.opencode/command/ui-review.md +1 -0
  31. package/dist/template/.opencode/memory/project/architecture.md +59 -6
  32. package/dist/template/.opencode/memory/project/beads-workflow.md +278 -0
  33. package/dist/template/.opencode/memory/project/commands.md +20 -164
  34. package/dist/template/.opencode/memory/session-context.md +40 -0
  35. package/dist/template/.opencode/memory/user.md +24 -7
  36. package/dist/template/.opencode/opencode.json +77 -16
  37. package/dist/template/.opencode/package.json +1 -1
  38. package/dist/template/.opencode/plugin/compaction.ts +62 -18
  39. package/dist/template/.opencode/plugin/lib/notify.ts +2 -3
  40. package/dist/template/.opencode/plugin/sessions.ts +1 -1
  41. package/dist/template/.opencode/plugin/skill-mcp.ts +11 -12
  42. package/dist/template/.opencode/skill/beads/SKILL.md +44 -0
  43. package/dist/template/.opencode/skill/condition-based-waiting/example.ts +71 -65
  44. package/dist/template/.opencode/tool/ast-grep.ts +3 -3
  45. package/dist/template/.opencode/tool/bd-inbox.ts +7 -6
  46. package/dist/template/.opencode/tool/bd-msg.ts +3 -3
  47. package/dist/template/.opencode/tool/bd-release.ts +2 -2
  48. package/dist/template/.opencode/tool/bd-reserve.ts +5 -4
  49. package/dist/template/.opencode/tool/memory-read.ts +58 -58
  50. package/dist/template/.opencode/tool/memory-search.ts +2 -2
  51. package/dist/template/.opencode/tool/memory-update.ts +53 -54
  52. package/dist/template/.opencode/tool/observation.ts +6 -6
  53. package/dist/template/.opencode/tsconfig.json +19 -19
  54. package/package.json +8 -17
  55. package/dist/template/.opencode/command.backup/analyze-project.md +0 -465
  56. package/dist/template/.opencode/command.backup/finish.md +0 -167
  57. package/dist/template/.opencode/command.backup/implement.md +0 -143
  58. package/dist/template/.opencode/command.backup/pr.md +0 -252
  59. package/dist/template/.opencode/command.backup/status.md +0 -376
  60. package/dist/template/.opencode/lib/lsp/client.ts +0 -614
  61. package/dist/template/.opencode/lib/lsp/config.ts +0 -199
  62. package/dist/template/.opencode/lib/lsp/constants.ts +0 -339
  63. package/dist/template/.opencode/lib/lsp/types.ts +0 -138
  64. package/dist/template/.opencode/lib/lsp/utils.ts +0 -190
  65. 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 '~/threads/thread-manager';
6
- import type { LaceEvent, LaceEventType } from '~/threads/types';
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
- threadManager: ThreadManager,
22
- threadId: string,
23
- eventType: LaceEventType,
24
- timeoutMs = 5000
21
+ threadManager: ThreadManager,
22
+ threadId: string,
23
+ eventType: LaceEventType,
24
+ timeoutMs = 5000,
25
25
  ): Promise<LaceEvent> {
26
- return new Promise((resolve, reject) => {
27
- const startTime = Date.now();
26
+ return new Promise((resolve, reject) => {
27
+ const startTime = Date.now();
28
28
 
29
- const check = () => {
30
- const events = threadManager.getEvents(threadId);
31
- const event = events.find((e) => e.type === eventType);
29
+ const check = () => {
30
+ const events = threadManager.getEvents(threadId);
31
+ const event = events.find((e) => e.type === eventType);
32
32
 
33
- if (event) {
34
- resolve(event);
35
- } else if (Date.now() - startTime > timeoutMs) {
36
- reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));
37
- } else {
38
- setTimeout(check, 10); // Poll every 10ms for efficiency
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
- check();
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
- threadManager: ThreadManager,
62
- threadId: string,
63
- eventType: LaceEventType,
64
- count: number,
65
- timeoutMs = 5000
65
+ threadManager: ThreadManager,
66
+ threadId: string,
67
+ eventType: LaceEventType,
68
+ count: number,
69
+ timeoutMs = 5000,
66
70
  ): Promise<LaceEvent[]> {
67
- return new Promise((resolve, reject) => {
68
- const startTime = Date.now();
71
+ return new Promise((resolve, reject) => {
72
+ const startTime = Date.now();
69
73
 
70
- const check = () => {
71
- const events = threadManager.getEvents(threadId);
72
- const matchingEvents = events.filter((e) => e.type === eventType);
74
+ const check = () => {
75
+ const events = threadManager.getEvents(threadId);
76
+ const matchingEvents = events.filter((e) => e.type === eventType);
73
77
 
74
- if (matchingEvents.length >= count) {
75
- resolve(matchingEvents);
76
- } else if (Date.now() - startTime > timeoutMs) {
77
- reject(
78
- new Error(
79
- `Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`
80
- )
81
- );
82
- } else {
83
- setTimeout(check, 10);
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
- check();
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
- threadManager: ThreadManager,
113
- threadId: string,
114
- predicate: (event: LaceEvent) => boolean,
115
- description: string,
116
- timeoutMs = 5000
116
+ threadManager: ThreadManager,
117
+ threadId: string,
118
+ predicate: (event: LaceEvent) => boolean,
119
+ description: string,
120
+ timeoutMs = 5000,
117
121
  ): Promise<LaceEvent> {
118
- return new Promise((resolve, reject) => {
119
- const startTime = Date.now();
122
+ return new Promise((resolve, reject) => {
123
+ const startTime = Date.now();
120
124
 
121
- const check = () => {
122
- const events = threadManager.getEvents(threadId);
123
- const event = events.find(predicate);
125
+ const check = () => {
126
+ const events = threadManager.getEvents(threadId);
127
+ const event = events.find(predicate);
124
128
 
125
- if (event) {
126
- resolve(event);
127
- } else if (Date.now() - startTime > timeoutMs) {
128
- reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));
129
- } else {
130
- setTimeout(check, 10);
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
- check();
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 += `\n**To apply:** Run again with dryRun: false`;
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 path from "path";
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") + "\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: any) {
103
- if (e.code === "ENOENT") {
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 path from "path";
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) + "\n", "utf-8");
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 path from "path";
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
 
@@ -1,6 +1,6 @@
1
- import path from "path";
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: any) {
73
- if (e.code === "EEXIST") {
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
- 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";
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
- // Normalize: strip .md extension if present
20
- const normalizedFile = fileName.replace(/\.md$/i, "");
19
+ // Normalize: strip .md extension if present
20
+ const normalizedFile = fileName.replace(/\.md$/i, "");
21
21
 
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
- ];
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
- // 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
- }
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
- // 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
- },
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,6 +1,6 @@
1
- import path from "path";
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
  interface SearchResult {
6
6
  file: string;
@@ -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
- 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");
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
- // Normalize file path: strip existing .md extension, handle subdirectories
30
- let normalizedFile = args.file.replace(/\.md$/i, ""); // Remove .md if present
31
- const filePath = path.join(memoryDir, `${normalizedFile}.md`);
32
- const mode = args.mode || "replace";
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
- try {
35
- // Ensure parent directory exists (handles subdirectories)
36
- const fileDir = path.dirname(filePath);
37
- await fs.mkdir(fileDir, { recursive: true });
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
- 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
- } else {
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
- }
54
- } catch (error) {
55
- if (error instanceof Error) {
56
- return `Error updating memory: ${error.message}`;
57
- }
58
- return `Unknown error updating memory file`;
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 path from "path";
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 += `\n---\n\n`;
115
+ observation += "\n---\n\n";
116
116
  observation += args.content;
117
- observation += `\n`;
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 `Unknown error saving observation`;
152
+ return "Unknown error saving observation";
153
153
  }
154
154
  },
155
155
  });