opencodekit 0.12.4 → 0.12.5

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 (60) hide show
  1. package/dist/index.js +2 -2
  2. package/dist/template/.opencode/command/accessibility-check.md +7 -10
  3. package/dist/template/.opencode/command/analyze-mockup.md +3 -16
  4. package/dist/template/.opencode/command/analyze-project.md +57 -69
  5. package/dist/template/.opencode/command/brainstorm.md +3 -11
  6. package/dist/template/.opencode/command/commit.md +10 -18
  7. package/dist/template/.opencode/command/create.md +4 -8
  8. package/dist/template/.opencode/command/design-audit.md +24 -51
  9. package/dist/template/.opencode/command/design.md +10 -17
  10. package/dist/template/.opencode/command/finish.md +9 -9
  11. package/dist/template/.opencode/command/fix-ci.md +7 -28
  12. package/dist/template/.opencode/command/fix-types.md +3 -7
  13. package/dist/template/.opencode/command/fix-ui.md +5 -11
  14. package/dist/template/.opencode/command/fix.md +4 -10
  15. package/dist/template/.opencode/command/handoff.md +8 -14
  16. package/dist/template/.opencode/command/implement.md +13 -16
  17. package/dist/template/.opencode/command/import-plan.md +20 -38
  18. package/dist/template/.opencode/command/init.md +9 -13
  19. package/dist/template/.opencode/command/integration-test.md +11 -13
  20. package/dist/template/.opencode/command/issue.md +4 -8
  21. package/dist/template/.opencode/command/new-feature.md +20 -40
  22. package/dist/template/.opencode/command/plan.md +8 -12
  23. package/dist/template/.opencode/command/pr.md +29 -38
  24. package/dist/template/.opencode/command/quick-build.md +3 -7
  25. package/dist/template/.opencode/command/research-and-implement.md +4 -6
  26. package/dist/template/.opencode/command/research.md +10 -7
  27. package/dist/template/.opencode/command/resume.md +12 -24
  28. package/dist/template/.opencode/command/revert-feature.md +21 -56
  29. package/dist/template/.opencode/command/review-codebase.md +21 -23
  30. package/dist/template/.opencode/command/skill-create.md +1 -5
  31. package/dist/template/.opencode/command/skill-optimize.md +3 -10
  32. package/dist/template/.opencode/command/status.md +28 -25
  33. package/dist/template/.opencode/command/triage.md +19 -31
  34. package/dist/template/.opencode/command/ui-review.md +6 -13
  35. package/dist/template/.opencode/command.backup/analyze-project.md +465 -0
  36. package/dist/template/.opencode/command.backup/finish.md +167 -0
  37. package/dist/template/.opencode/command.backup/implement.md +143 -0
  38. package/dist/template/.opencode/command.backup/pr.md +252 -0
  39. package/dist/template/.opencode/command.backup/status.md +376 -0
  40. package/dist/template/.opencode/memory/project/SHELL_OUTPUT_MIGRATION_PLAN.md +551 -0
  41. package/dist/template/.opencode/memory/project/gotchas.md +33 -28
  42. package/dist/template/.opencode/opencode.json +14 -28
  43. package/dist/template/.opencode/package.json +1 -3
  44. package/dist/template/.opencode/plugin/compaction.ts +51 -129
  45. package/dist/template/.opencode/plugin/handoff.ts +18 -163
  46. package/dist/template/.opencode/plugin/notification.ts +1 -1
  47. package/dist/template/.opencode/plugin/package.json +7 -0
  48. package/dist/template/.opencode/plugin/sessions.ts +185 -651
  49. package/dist/template/.opencode/plugin/skill-mcp.ts +2 -1
  50. package/dist/template/.opencode/plugin/truncator.ts +19 -41
  51. package/dist/template/.opencode/plugin/tsconfig.json +14 -13
  52. package/dist/template/.opencode/tool/bd-inbox.ts +109 -0
  53. package/dist/template/.opencode/tool/bd-msg.ts +62 -0
  54. package/dist/template/.opencode/tool/bd-release.ts +71 -0
  55. package/dist/template/.opencode/tool/bd-reserve.ts +120 -0
  56. package/package.json +2 -2
  57. package/dist/template/.opencode/plugin/beads.ts +0 -1419
  58. package/dist/template/.opencode/plugin/compactor.ts +0 -107
  59. package/dist/template/.opencode/plugin/enforcer.ts +0 -190
  60. package/dist/template/.opencode/plugin/injector.ts +0 -150
@@ -1,107 +0,0 @@
1
- /**
2
- * OpenCode Compactor Plugin
3
- * Warns at token thresholds before hitting limits
4
- */
5
-
6
- import type { Plugin } from "@opencode-ai/plugin";
7
- import {
8
- THRESHOLDS,
9
- type TokenStats,
10
- getContextPercentage,
11
- notify,
12
- } from "./lib/notify";
13
-
14
- type LogLevel = "debug" | "info" | "warn" | "error";
15
-
16
- interface WarningLevel {
17
- level: LogLevel;
18
- emoji: string;
19
- action: string;
20
- }
21
-
22
- function getWarningLevel(percentage: number): WarningLevel | null {
23
- if (percentage >= THRESHOLDS.CRITICAL) {
24
- return {
25
- level: "error",
26
- emoji: "🚨",
27
- action: "CRITICAL: Prune immediately or start new session.",
28
- };
29
- }
30
- if (percentage >= THRESHOLDS.URGENT) {
31
- return {
32
- level: "warn",
33
- emoji: "⚠️",
34
- action: "Consider pruning completed tool outputs.",
35
- };
36
- }
37
- if (percentage >= THRESHOLDS.MODERATE) {
38
- return {
39
- level: "info",
40
- emoji: "📈",
41
- action: "Context at 70%. Consider consolidating.",
42
- };
43
- }
44
- return null;
45
- }
46
-
47
- export const CompactorPlugin: Plugin = async ({ client, $ }) => {
48
- const warnedSessions = new Map<string, number>();
49
-
50
- return {
51
- event: async ({ event }) => {
52
- const props = event.properties as Record<string, unknown>;
53
-
54
- if (event.type === "session.updated") {
55
- const info = props?.info as Record<string, unknown> | undefined;
56
- const tokenStats = (info?.tokens || props?.tokens) as
57
- | TokenStats
58
- | undefined;
59
- const sessionId = (info?.id || props?.sessionID) as string | undefined;
60
-
61
- if (!sessionId || !tokenStats?.used || !tokenStats?.limit) return;
62
-
63
- const pct = getContextPercentage(tokenStats);
64
- const lastWarned = warnedSessions.get(sessionId) || 0;
65
-
66
- const warning = getWarningLevel(pct);
67
- if (!warning) return;
68
-
69
- let currentThreshold = 0;
70
- if (pct >= THRESHOLDS.CRITICAL) currentThreshold = THRESHOLDS.CRITICAL;
71
- else if (pct >= THRESHOLDS.URGENT) currentThreshold = THRESHOLDS.URGENT;
72
- else if (pct >= THRESHOLDS.MODERATE)
73
- currentThreshold = THRESHOLDS.MODERATE;
74
-
75
- if (lastWarned >= currentThreshold) return;
76
-
77
- warnedSessions.set(sessionId, currentThreshold);
78
-
79
- const message = `${warning.emoji} Context: ${pct}% (${tokenStats.used.toLocaleString()}/${tokenStats.limit.toLocaleString()} tokens). ${warning.action}`;
80
-
81
- client.app
82
- .log({
83
- body: { service: "compactor", level: warning.level, message },
84
- })
85
- .catch(() => {});
86
-
87
- if (pct >= THRESHOLDS.URGENT) {
88
- await notify($, `Context ${pct}%`, warning.action);
89
- }
90
- }
91
-
92
- if (event.type === "session.compacted") {
93
- const sessionId = props?.sessionID as string | undefined;
94
- if (sessionId) {
95
- warnedSessions.set(sessionId, 0);
96
- }
97
- }
98
-
99
- if (event.type === "session.deleted") {
100
- const sessionId = props?.sessionID as string | undefined;
101
- if (sessionId) {
102
- warnedSessions.delete(sessionId);
103
- }
104
- }
105
- },
106
- };
107
- };
@@ -1,190 +0,0 @@
1
- /**
2
- * OpenCode Enforcer Plugin
3
- * ENFORCES continuation when session idles with incomplete TODOs
4
- *
5
- * Upgrade from notification-only to actual enforcement:
6
- * Uses client.session.promptAsync() to inject continuation prompt
7
- */
8
-
9
- import type { Plugin } from "@opencode-ai/plugin";
10
- import { notify } from "./lib/notify";
11
-
12
- interface TodoItem {
13
- id: string;
14
- content: string;
15
- status: "pending" | "in_progress" | "completed" | "cancelled";
16
- priority: "high" | "medium" | "low";
17
- }
18
-
19
- // Cooldown to prevent rapid-fire enforcement (5 minutes)
20
- const ENFORCEMENT_COOLDOWN_MS = 5 * 60 * 1000;
21
-
22
- // Track last enforcement time per session
23
- const lastEnforcement = new Map<string, number>();
24
-
25
- // Track todos per session
26
- const sessionTodos = new Map<string, TodoItem[]>();
27
-
28
- /**
29
- * Build continuation prompt based on incomplete TODOs
30
- */
31
- function buildContinuationPrompt(incomplete: TodoItem[]): string {
32
- const highPriority = incomplete.filter((t) => t.priority === "high");
33
- const inProgress = incomplete.filter((t) => t.status === "in_progress");
34
-
35
- let prompt = "You stopped with incomplete work. Continue immediately.\n\n";
36
-
37
- if (inProgress.length > 0) {
38
- prompt += "**In Progress (finish these first):**\n";
39
- inProgress.forEach((t) => {
40
- prompt += `- ${t.content}\n`;
41
- });
42
- prompt += "\n";
43
- }
44
-
45
- if (highPriority.length > 0) {
46
- const remaining = highPriority.filter((t) => t.status !== "in_progress");
47
- if (remaining.length > 0) {
48
- prompt += "**High Priority:**\n";
49
- remaining.forEach((t) => {
50
- prompt += `- ${t.content}\n`;
51
- });
52
- prompt += "\n";
53
- }
54
- }
55
-
56
- const others = incomplete.filter(
57
- (t) => t.priority !== "high" && t.status !== "in_progress",
58
- );
59
- if (others.length > 0) {
60
- prompt += "**Remaining:**\n";
61
- others.slice(0, 5).forEach((t) => {
62
- prompt += `- ${t.content}\n`;
63
- });
64
- if (others.length > 5) {
65
- prompt += `- ... and ${others.length - 5} more\n`;
66
- }
67
- }
68
-
69
- prompt += "\nPick up where you left off. Do not ask for confirmation.";
70
-
71
- return prompt;
72
- }
73
-
74
- export const EnforcerPlugin: Plugin = async ({ client, $ }) => {
75
- return {
76
- event: async ({ event }) => {
77
- const props = event.properties as Record<string, unknown>;
78
-
79
- // Track TODO updates
80
- if (event.type === "todo.updated") {
81
- const sessionId = props?.sessionID as string | undefined;
82
- const todos = props?.todos as TodoItem[] | undefined;
83
-
84
- if (sessionId && todos) {
85
- sessionTodos.set(sessionId, todos);
86
- }
87
- }
88
-
89
- // Enforce on session idle
90
- if (event.type === "session.idle") {
91
- const sessionId = props?.sessionID as string | undefined;
92
- if (!sessionId) return;
93
-
94
- const todos = sessionTodos.get(sessionId) || [];
95
- const incomplete = todos.filter(
96
- (t) => t.status === "pending" || t.status === "in_progress",
97
- );
98
-
99
- // No incomplete TODOs - nothing to enforce
100
- if (incomplete.length === 0) return;
101
-
102
- // Check cooldown to prevent spam
103
- const now = Date.now();
104
- const lastTime = lastEnforcement.get(sessionId) || 0;
105
- if (now - lastTime < ENFORCEMENT_COOLDOWN_MS) {
106
- return; // Still in cooldown
107
- }
108
-
109
- // Count high priority and in-progress
110
- const highPriority = incomplete.filter((t) => t.priority === "high");
111
- const inProgress = incomplete.filter((t) => t.status === "in_progress");
112
-
113
- // Only enforce for high-priority or in-progress items
114
- // Low priority pending items don't warrant forced continuation
115
- if (highPriority.length === 0 && inProgress.length === 0) {
116
- // Just notify for low-priority items
117
- await notify(
118
- $,
119
- "Session Idle",
120
- `📋 ${incomplete.length} TODO${incomplete.length > 1 ? "s" : ""} remaining`,
121
- );
122
- return;
123
- }
124
-
125
- // Update cooldown
126
- lastEnforcement.set(sessionId, now);
127
-
128
- // Build and send continuation prompt
129
- const continuationPrompt = buildContinuationPrompt(incomplete);
130
-
131
- try {
132
- await client.session.promptAsync({
133
- path: { id: sessionId },
134
- body: {
135
- parts: [
136
- {
137
- type: "text",
138
- text: continuationPrompt,
139
- },
140
- ],
141
- },
142
- });
143
-
144
- client.app
145
- .log({
146
- body: {
147
- service: "enforcer",
148
- level: "info",
149
- message: `Enforced continuation: ${inProgress.length} in-progress, ${highPriority.length} high-priority TODOs`,
150
- },
151
- })
152
- .catch(() => {});
153
-
154
- await notify(
155
- $,
156
- "Enforcer",
157
- `Continuing ${inProgress.length + highPriority.length} incomplete TODOs`,
158
- );
159
- } catch (error) {
160
- // Fallback to notification if prompt fails
161
- client.app
162
- .log({
163
- body: {
164
- service: "enforcer",
165
- level: "warn",
166
- message: `Enforcement failed: ${(error as Error).message}`,
167
- },
168
- })
169
- .catch(() => {});
170
-
171
- const message =
172
- highPriority.length > 0
173
- ? `🔴 ${highPriority.length} high-priority TODO${highPriority.length > 1 ? "s" : ""} incomplete`
174
- : `🟡 ${inProgress.length} TODO${inProgress.length > 1 ? "s" : ""} still in progress`;
175
-
176
- await notify($, "Session Idle", message);
177
- }
178
- }
179
-
180
- // Cleanup on session delete
181
- if (event.type === "session.deleted") {
182
- const sessionId = props?.sessionID as string | undefined;
183
- if (sessionId) {
184
- sessionTodos.delete(sessionId);
185
- lastEnforcement.delete(sessionId);
186
- }
187
- }
188
- },
189
- };
190
- };
@@ -1,150 +0,0 @@
1
- /**
2
- * OpenCode AGENTS.md Hierarchy Injector Plugin
3
- *
4
- * Walks up from read file's directory to project root, collecting ALL AGENTS.md
5
- * files and injecting them into context. Solves the limitation where OpenCode's
6
- * findUp only finds the first match.
7
- *
8
- * Injection order: root → specific (T-shaped context loading)
9
- */
10
-
11
- import { existsSync, readFileSync } from "fs";
12
- import { dirname, join, resolve } from "path";
13
- import type { Plugin } from "@opencode-ai/plugin";
14
-
15
- // Cache injected directories per session to avoid duplicates
16
- const sessionInjectedDirs = new Map<string, Set<string>>();
17
-
18
- /**
19
- * Walk up from a directory to root, collecting all AGENTS.md files
20
- */
21
- function collectAgentsMdFiles(
22
- startDir: string,
23
- projectRoot: string,
24
- ): { path: string; content: string }[] {
25
- const files: { path: string; content: string }[] = [];
26
- let currentDir = resolve(startDir);
27
- const root = resolve(projectRoot);
28
-
29
- // Walk up until we hit project root or filesystem root
30
- while (currentDir.startsWith(root) || currentDir === root) {
31
- const agentsMdPath = join(currentDir, "AGENTS.md");
32
-
33
- if (existsSync(agentsMdPath)) {
34
- try {
35
- const content = readFileSync(agentsMdPath, "utf-8");
36
- files.push({ path: agentsMdPath, content });
37
- } catch {
38
- // Skip unreadable files
39
- }
40
- }
41
-
42
- // Move up one directory
43
- const parentDir = dirname(currentDir);
44
- if (parentDir === currentDir) break; // Hit filesystem root
45
- currentDir = parentDir;
46
- }
47
-
48
- // Reverse so root comes first (T-shaped: general → specific)
49
- return files.reverse();
50
- }
51
-
52
- /**
53
- * Check if a file path is a code file (not AGENTS.md itself)
54
- */
55
- function isCodeFile(filePath: string): boolean {
56
- const lowerPath = filePath.toLowerCase();
57
-
58
- // Skip AGENTS.md files themselves
59
- if (lowerPath.endsWith("agents.md")) return false;
60
-
61
- // Skip common non-code files
62
- if (lowerPath.includes("node_modules/")) return false;
63
- if (lowerPath.includes("/.git/")) return false;
64
-
65
- return true;
66
- }
67
-
68
- /**
69
- * Format injected AGENTS.md content
70
- */
71
- function formatInjection(
72
- files: { path: string; content: string }[],
73
- projectRoot: string,
74
- ): string {
75
- if (files.length === 0) return "";
76
-
77
- const sections = files.map((f) => {
78
- const relativePath = f.path.replace(projectRoot, "").replace(/^\//, "");
79
- return `<!-- AGENTS.md from: ${relativePath} -->\n${f.content}`;
80
- });
81
-
82
- return (
83
- "\n\n---\n## Injected AGENTS.md Hierarchy\n\n" +
84
- sections.join("\n\n---\n\n") +
85
- "\n---\n\n"
86
- );
87
- }
88
-
89
- export const InjectorPlugin: Plugin = async ({ directory, worktree }) => {
90
- // Use worktree if available (git worktree), otherwise use directory
91
- const projectRoot = worktree || directory;
92
-
93
- return {
94
- "tool.execute.after": async (input, output) => {
95
- // Only process read tool
96
- if (input.tool !== "read") return;
97
-
98
- // Get the file path from metadata
99
- const filePath = output.metadata?.filePath as string | undefined;
100
- if (!filePath) return;
101
-
102
- // Skip non-code files
103
- if (!isCodeFile(filePath)) return;
104
-
105
- // Get or create session cache
106
- let injectedDirs = sessionInjectedDirs.get(input.sessionID);
107
- if (!injectedDirs) {
108
- injectedDirs = new Set();
109
- sessionInjectedDirs.set(input.sessionID, injectedDirs);
110
- }
111
-
112
- // Get the directory of the read file
113
- const fileDir = dirname(resolve(filePath));
114
-
115
- // Check if we've already injected for this directory chain
116
- if (injectedDirs.has(fileDir)) return;
117
-
118
- // Collect AGENTS.md files
119
- const agentsFiles = collectAgentsMdFiles(fileDir, projectRoot);
120
-
121
- // Skip if no AGENTS.md files found
122
- if (agentsFiles.length === 0) return;
123
-
124
- // Mark this directory as injected
125
- injectedDirs.add(fileDir);
126
-
127
- // Also mark parent directories to avoid redundant injections
128
- let dir = fileDir;
129
- while (dir.startsWith(projectRoot) && dir !== projectRoot) {
130
- injectedDirs.add(dir);
131
- dir = dirname(dir);
132
- }
133
-
134
- // Inject at the beginning of output
135
- const injection = formatInjection(agentsFiles, projectRoot);
136
- output.output = injection + output.output;
137
- },
138
-
139
- event: async ({ event }) => {
140
- // Clean up cache when session is deleted
141
- if (event.type === "session.deleted") {
142
- const props = event.properties as Record<string, unknown>;
143
- const sessionId = props?.sessionID as string | undefined;
144
- if (sessionId) {
145
- sessionInjectedDirs.delete(sessionId);
146
- }
147
- }
148
- },
149
- };
150
- };