opencodekit 0.12.7 → 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 (28) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +2753 -508
  3. package/dist/template/.opencode/AGENTS.md +35 -128
  4. package/dist/template/.opencode/README.md +4 -3
  5. package/dist/template/.opencode/command/design.md +1 -0
  6. package/dist/template/.opencode/command/fix.md +28 -1
  7. package/dist/template/.opencode/command/research.md +0 -4
  8. package/dist/template/.opencode/command/start.md +106 -0
  9. package/dist/template/.opencode/command/triage.md +66 -12
  10. package/dist/template/.opencode/memory/project/beads-workflow.md +278 -0
  11. package/dist/template/.opencode/memory/session-context.md +40 -0
  12. package/dist/template/.opencode/opencode.json +557 -496
  13. package/dist/template/.opencode/package.json +1 -1
  14. package/dist/template/.opencode/plugin/compaction.ts +62 -18
  15. package/dist/template/.opencode/plugin/lib/notify.ts +2 -3
  16. package/dist/template/.opencode/plugin/sessions.ts +1 -1
  17. package/dist/template/.opencode/plugin/skill-mcp.ts +11 -12
  18. package/dist/template/.opencode/skill/beads/SKILL.md +44 -0
  19. package/dist/template/.opencode/tool/ast-grep.ts +3 -3
  20. package/dist/template/.opencode/tool/bd-inbox.ts +7 -6
  21. package/dist/template/.opencode/tool/bd-msg.ts +3 -3
  22. package/dist/template/.opencode/tool/bd-release.ts +2 -2
  23. package/dist/template/.opencode/tool/bd-reserve.ts +5 -4
  24. package/dist/template/.opencode/tool/memory-read.ts +2 -2
  25. package/dist/template/.opencode/tool/memory-search.ts +2 -2
  26. package/dist/template/.opencode/tool/memory-update.ts +11 -12
  27. package/dist/template/.opencode/tool/observation.ts +6 -6
  28. package/package.json +5 -2
@@ -11,7 +11,7 @@
11
11
  "author": "",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@opencode-ai/plugin": "1.1.4"
14
+ "@opencode-ai/plugin": "1.1.6"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/node": "^25.0.3",
@@ -1,10 +1,19 @@
1
1
  /**
2
- * Memory & Compaction Plugin
2
+ * Memory & Compaction Plugin (Codex-Inspired Continuity)
3
3
  *
4
- * Injects memory context and beads state into session compaction:
5
- * 1. Load memory files for context
6
- * 2. Inject beads in-progress state
7
- * 3. Append workflow-specific compaction rules
4
+ * Injects continuity context into session compaction:
5
+ * 1. Load session-context.md (CONTINUITY.md pattern)
6
+ * 2. Load project memory files
7
+ * 3. Inject beads in-progress state
8
+ * 4. Append workflow-specific compaction rules
9
+ *
10
+ * Session context format (agent-maintained via memory-update):
11
+ * - Goal: What we're trying to achieve + success criteria
12
+ * - Constraints: User-specified limits, discovered guardrails
13
+ * - Decisions: Key choices made this session
14
+ * - State: Done/Now/Next
15
+ * - Open Questions: Uncertainties marked UNCONFIRMED
16
+ * - Working Set: Files, bead IDs, branch
8
17
  */
9
18
 
10
19
  import type { Plugin } from "@opencode-ai/plugin";
@@ -14,7 +23,19 @@ export const CompactionPlugin: Plugin = async ({ $, directory }) => {
14
23
 
15
24
  return {
16
25
  "experimental.session.compacting": async (input, output) => {
17
- // Load memory context
26
+ // 1. Load session context (CONTINUITY.md pattern) - HIGHEST PRIORITY
27
+ let sessionContext = "";
28
+ try {
29
+ const sessionFile = `${MEMORY_DIR}/session-context.md`;
30
+ const content = await $`cat ${sessionFile} 2>/dev/null`.text();
31
+ if (content.trim()) {
32
+ sessionContext = `\n## Session Continuity (Compaction-Safe)\n${content}`;
33
+ }
34
+ } catch {
35
+ // No session context yet
36
+ }
37
+
38
+ // 2. Load project memory files
18
39
  let memoryContext = "";
19
40
  try {
20
41
  const memoryFiles =
@@ -33,7 +54,7 @@ export const CompactionPlugin: Plugin = async ({ $, directory }) => {
33
54
  // Memory dir doesn't exist or other error
34
55
  }
35
56
 
36
- // Inject beads in-progress state
57
+ // 3. Inject beads in-progress state
37
58
  let beadsContext = "";
38
59
  try {
39
60
  const result =
@@ -48,12 +69,13 @@ export const CompactionPlugin: Plugin = async ({ $, directory }) => {
48
69
  // Beads not available, skip
49
70
  }
50
71
 
51
- // Inject memory and beads context into compaction context
52
- if (memoryContext || beadsContext) {
53
- output.context.push(`## Session Context
54
- ${beadsContext}
55
- ${memoryContext}
56
- `);
72
+ // Inject all context - session context FIRST (most important)
73
+ const allContext = [sessionContext, beadsContext, memoryContext]
74
+ .filter(Boolean)
75
+ .join("\n");
76
+
77
+ if (allContext) {
78
+ output.context.push(`## Session Context\n${allContext}\n`);
57
79
  }
58
80
 
59
81
  // Append workflow-specific rules to OpenCode's default prompt
@@ -61,6 +83,28 @@ ${memoryContext}
61
83
 
62
84
  ## Additional Rules for This Workflow
63
85
 
86
+ ### Session Continuity
87
+ Maintain session-context.md via memory-update tool. Format:
88
+ \`\`\`
89
+ Goal: [What + success criteria]
90
+ Constraints: [Limits, mark UNCONFIRMED if inferred]
91
+ Decisions: [Key choices this session]
92
+ State:
93
+ Done: [Completed]
94
+ Now: [Current focus - ONE thing]
95
+ Next: [Queued]
96
+ Open Questions: [Uncertainties - mark UNCONFIRMED]
97
+ Working Set: [Files, bead ID, branch]
98
+ \`\`\`
99
+
100
+ Update session-context.md when:
101
+ - Goal changes or clarifies
102
+ - Key decision made
103
+ - State shifts (Done/Now/Next)
104
+ - Uncertainty discovered
105
+
106
+ After compaction: Check session-context.md, ask 1-3 targeted questions if gaps exist.
107
+
64
108
  ### Beads Workflow
65
109
  - PRESERVE: Bead IDs (bd-xxx format), bead states, in-progress task IDs
66
110
  - DROP: Closed/completed beads (already tracked in git)
@@ -71,13 +115,13 @@ ${memoryContext}
71
115
 
72
116
  ### Memory System
73
117
  If you discover:
74
- - Gotchas/edge cases → Mention they should be saved to .opencode/memory/project/gotchas.md
75
- - Build/test commands → Mention they should be saved to .opencode/memory/project/commands.md
76
- - Code patterns/conventions → Mention they should be saved to .opencode/memory/project/conventions.md
77
- - Architecture insights → Mention they should be saved to .opencode/memory/project/architecture.md
118
+ - Gotchas/edge cases → Save to .opencode/memory/project/gotchas.md
119
+ - Build/test commands → Save to .opencode/memory/project/commands.md
120
+ - Code patterns/conventions → Save to .opencode/memory/project/conventions.md
121
+ - Architecture insights → Save to .opencode/memory/project/architecture.md
78
122
 
79
123
  ### Preservation Priorities
80
- - PRESERVE: File paths with exact locations (file:line_number), user constraints, technical decisions
124
+ - PRESERVE: File paths (file:line_number), user constraints, decisions, UNCONFIRMED items
81
125
  - DROP: Failed attempts, superseded info, verbose tool outputs, exploration dead-ends
82
126
  `;
83
127
  },
@@ -12,7 +12,7 @@ let _isWSL: boolean | null = null;
12
12
  export function isWSL(): boolean {
13
13
  if (_isWSL !== null) return _isWSL;
14
14
  try {
15
- const fs = require("fs");
15
+ const fs = require("node:fs");
16
16
  const release = fs.readFileSync("/proc/version", "utf8").toLowerCase();
17
17
  _isWSL = release.includes("microsoft") || release.includes("wsl");
18
18
  } catch {
@@ -28,7 +28,6 @@ export function isWSL(): boolean {
28
28
  * @param message - Notification body
29
29
  */
30
30
  export async function notify(
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
31
  $: any,
33
32
  title: string,
34
33
  message: string,
@@ -39,7 +38,7 @@ export async function notify(
39
38
 
40
39
  try {
41
40
  if (platform === "darwin") {
42
- await $`osascript -e ${'display notification "' + safeMessage + '" with title "' + safeTitle + '"'}`;
41
+ await $`osascript -e ${`display notification "${safeMessage}" with title "${safeTitle}"`}`;
43
42
  } else if (platform === "linux") {
44
43
  if (isWSL()) {
45
44
  // WSL: try notify-send, fail silently
@@ -116,7 +116,7 @@ export const SessionsPlugin: Plugin = async ({ client }) => {
116
116
  const userMessages = messageData.filter(
117
117
  (m) => m.info?.role === "user",
118
118
  );
119
- summary += `## Recent User Messages\n\n`;
119
+ summary += "## Recent User Messages\n\n";
120
120
  for (let i = 0; i < Math.min(userMessages.length, 5); i++) {
121
121
  const m = userMessages[i];
122
122
  const content = extractContent(m.info);
@@ -1,7 +1,7 @@
1
- import { type ChildProcess, spawn } from "child_process";
2
- import { existsSync, readFileSync } from "fs";
3
- import { homedir } from "os";
4
- import { join } from "path";
1
+ import { type ChildProcess, spawn } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
5
  import type { Plugin } from "@opencode-ai/plugin";
6
6
  import { tool } from "@opencode-ai/plugin/tool";
7
7
 
@@ -155,7 +155,7 @@ export const SkillMcpPlugin: Plugin = async ({ directory }) => {
155
155
  },
156
156
  });
157
157
 
158
- client.process.stdin?.write(JSON.stringify(request) + "\n");
158
+ client.process.stdin?.write(`${JSON.stringify(request)}\n`);
159
159
  });
160
160
  }
161
161
 
@@ -232,10 +232,10 @@ export const SkillMcpPlugin: Plugin = async ({ directory }) => {
232
232
 
233
233
  // Send initialized notification
234
234
  proc.stdin?.write(
235
- JSON.stringify({
235
+ `${JSON.stringify({
236
236
  jsonrpc: "2.0",
237
237
  method: "notifications/initialized",
238
- }) + "\n",
238
+ })}\n`,
239
239
  );
240
240
 
241
241
  // Discover capabilities
@@ -430,7 +430,7 @@ The skill must be loaded first via the skill() tool to register its MCP config.`
430
430
  if (args.skill_name) {
431
431
  const toDisconnect: string[] = [];
432
432
  for (const key of state.clients.keys()) {
433
- if (key.startsWith(args.skill_name + ":")) {
433
+ if (key.startsWith(`${args.skill_name}:`)) {
434
434
  toDisconnect.push(key);
435
435
  }
436
436
  }
@@ -440,11 +440,10 @@ The skill must be loaded first via the skill() tool to register its MCP config.`
440
440
  state.clients.delete(key);
441
441
  }
442
442
  return JSON.stringify({ disconnected: toDisconnect });
443
- } else {
444
- const count = state.clients.size;
445
- disconnectAll();
446
- return JSON.stringify({ disconnected: "all", count });
447
443
  }
444
+ const count = state.clients.size;
445
+ disconnectAll();
446
+ return JSON.stringify({ disconnected: "all", count });
448
447
  },
449
448
  }),
450
449
  },
@@ -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
  ```
@@ -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,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
  export default tool({
6
6
  description:
@@ -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,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
  export default tool({
6
6
  description:
@@ -41,21 +41,20 @@ export default tool({
41
41
  const appendContent = `\n\n---\n**Updated:** ${timestamp}\n\n${args.content}`;
42
42
  await fs.appendFile(filePath, appendContent, "utf-8");
43
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
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}]`;
54
53
  } catch (error) {
55
54
  if (error instanceof Error) {
56
55
  return `Error updating memory: ${error.message}`;
57
56
  }
58
- return `Unknown error updating memory file`;
57
+ return "Unknown error updating memory file";
59
58
  }
60
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.12.7",
3
+ "version": "0.13.0",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "type": "module",
6
6
  "repository": {
@@ -17,7 +17,7 @@
17
17
  "files": ["dist", "README.md"],
18
18
  "scripts": {
19
19
  "dev": "bun run src/index.ts",
20
- "build": "bun build src/index.ts --outdir dist --target node && mkdir -p dist/template && rsync -av --exclude=node_modules --exclude=dist --exclude=.git --exclude=coverage --exclude=.next --exclude=.turbo --exclude=logs --exclude=package-lock.json .opencode/ dist/template/.opencode/",
20
+ "build": "bun run build.ts && mkdir -p dist/template && rsync -av --exclude=node_modules --exclude=dist --exclude=.git --exclude=coverage --exclude=.next --exclude=.turbo --exclude=logs --exclude=package-lock.json .opencode/ dist/template/.opencode/",
21
21
  "compile": "bun build src/index.ts --compile --outfile ock",
22
22
  "compile:binary": "bun build src/index.ts --compile --outfile bin/ock",
23
23
  "typecheck": "tsc --noEmit",
@@ -35,11 +35,14 @@
35
35
  "dependencies": {
36
36
  "@clack/prompts": "^0.7.0",
37
37
  "@opencode-ai/plugin": "^1.1.2",
38
+ "@opentui/core": "^0.1.69",
39
+ "@opentui/solid": "^0.1.69",
38
40
  "beads-village": "^1.3.3",
39
41
  "cac": "^6.7.14",
40
42
  "cli-table3": "^0.6.5",
41
43
  "ora": "^9.0.0",
42
44
  "picocolors": "^1.1.1",
45
+ "solid-js": "^1.9.10",
43
46
  "zod": "^3.23.8"
44
47
  },
45
48
  "devDependencies": {